github.com/netdata/go.d.plugin@v0.58.1/modules/powerdns_recursor/collect.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package powerdns_recursor 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 (r *Recursor) collect() (map[string]int64, error) { 21 statistics, err := r.scrapeStatistics() 22 if err != nil { 23 return nil, err 24 } 25 26 collected := make(map[string]int64) 27 28 r.collectStatistics(collected, statistics) 29 30 if !isPowerDNSRecursorMetrics(collected) { 31 return nil, errors.New("returned metrics aren't PowerDNS Recursor metrics") 32 } 33 34 return collected, nil 35 } 36 37 func isPowerDNSRecursorMetrics(collected map[string]int64) bool { 38 // PowerDNS Authoritative Server 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 (r *Recursor) 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 r.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 r.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 (r *Recursor) scrapeStatistics() ([]statisticMetric, error) { 68 req, _ := web.NewHTTPRequest(r.Request) 69 req.URL.Path = urlPathLocalStatistics 70 71 var statistics statisticMetrics 72 if err := r.doOKDecode(req, &statistics); err != nil { 73 return nil, err 74 } 75 76 return statistics, nil 77 } 78 79 func (r *Recursor) doOKDecode(req *http.Request, in interface{}) error { 80 resp, err := r.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 }