github.com/netdata/go.d.plugin@v0.58.1/modules/powerdns/collect.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package powerdns
     4  
     5  import (
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"strconv"
    12  
    13  	"github.com/netdata/go.d.plugin/pkg/web"
    14  )
    15  
    16  const (
    17  	urlPathLocalStatistics = "/api/v1/servers/localhost/statistics"
    18  )
    19  
    20  func (ns *AuthoritativeNS) collect() (map[string]int64, error) {
    21  	statistics, err := ns.scrapeStatistics()
    22  	if err != nil {
    23  		return nil, err
    24  	}
    25  
    26  	collected := make(map[string]int64)
    27  
    28  	ns.collectStatistics(collected, statistics)
    29  
    30  	if !isPowerDNSAuthoritativeNSMetrics(collected) {
    31  		return nil, errors.New("returned metrics aren't PowerDNS Authoritative Server metrics")
    32  	}
    33  
    34  	return collected, nil
    35  }
    36  
    37  func isPowerDNSAuthoritativeNSMetrics(collected map[string]int64) bool {
    38  	// PowerDNS Recursor has same endpoint and returns data in the same format.
    39  	_, ok1 := collected["over-capacity-drops"]
    40  	_, ok2 := collected["tcp-questions"]
    41  	return !ok1 && !ok2
    42  }
    43  
    44  func (ns *AuthoritativeNS) collectStatistics(collected map[string]int64, statistics statisticMetrics) {
    45  	for _, s := range statistics {
    46  		// https://doc.powerdns.com/authoritative/http-api/statistics.html#statisticitem
    47  		if s.Type != "StatisticItem" {
    48  			continue
    49  		}
    50  
    51  		value, ok := s.Value.(string)
    52  		if !ok {
    53  			ns.Debugf("%s value (%v) unexpected type: want=string, got=%T.", s.Name, s.Value, s.Value)
    54  			continue
    55  		}
    56  
    57  		v, err := strconv.ParseInt(value, 10, 64)
    58  		if err != nil {
    59  			ns.Debugf("%s value (%v) parse error: %v", s.Name, s.Value, err)
    60  			continue
    61  		}
    62  
    63  		collected[s.Name] = v
    64  	}
    65  }
    66  
    67  func (ns *AuthoritativeNS) scrapeStatistics() ([]statisticMetric, error) {
    68  	req, _ := web.NewHTTPRequest(ns.Request)
    69  	req.URL.Path = urlPathLocalStatistics
    70  
    71  	var statistics statisticMetrics
    72  	if err := ns.doOKDecode(req, &statistics); err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	return statistics, nil
    77  }
    78  
    79  func (ns *AuthoritativeNS) doOKDecode(req *http.Request, in interface{}) error {
    80  	resp, err := ns.httpClient.Do(req)
    81  	if err != nil {
    82  		return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
    83  	}
    84  	defer closeBody(resp)
    85  
    86  	if resp.StatusCode != http.StatusOK {
    87  		return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
    88  	}
    89  
    90  	if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
    91  		return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
    92  	}
    93  	return nil
    94  }
    95  
    96  func closeBody(resp *http.Response) {
    97  	if resp != nil && resp.Body != nil {
    98  		_, _ = io.Copy(io.Discard, resp.Body)
    99  		_ = resp.Body.Close()
   100  	}
   101  }