diff --git a/bind/bind.go b/bind/bind.go index c2379b4..c0e043f 100644 --- a/bind/bind.go +++ b/bind/bind.go @@ -26,6 +26,18 @@ type Client interface { const ( // QryRTT is the common prefix of query round-trip histogram counters. QryRTT = "QryRTT" + + // trafficBucketSize is the size of one traffic histogram bucket, defined as + // DNS_SIZEHISTO_QUANTUM in BIND source code. + TrafficBucketSize = 16 + + // trafficInMaxSize is the maximum size inbound request reported by BIND, referred to by + // DNS_SIZEHISTO_MAXIN in BIND source code. + TrafficInMaxSize = 288 + + // trafficOutMaxSize is the maximum size outbound response reported by BIND, referred to by + // DNS_SIZEHISTO_MAXOUT in BIND source code. + TrafficOutMaxSize = 4096 ) // StatisticGroup describes a sub-group of BIND statistics. @@ -33,17 +45,19 @@ type StatisticGroup string // Available statistic groups. const ( - ServerStats StatisticGroup = "server" - ViewStats StatisticGroup = "view" - TaskStats StatisticGroup = "tasks" + ServerStats StatisticGroup = "server" + TaskStats StatisticGroup = "tasks" + TrafficStats StatisticGroup = "traffic" + ViewStats StatisticGroup = "view" ) // Statistics is a generic representation of BIND statistics. type Statistics struct { - Server Server - Views []View - ZoneViews []ZoneView - TaskManager TaskManager + Server Server + Views []View + ZoneViews []ZoneView + TaskManager TaskManager + TrafficHistograms TrafficHistograms } // Server represents BIND server statistics. @@ -111,3 +125,16 @@ type ThreadModel struct { DefaultQuantum uint64 `xml:"default-quantum"` TasksRunning uint64 `xml:"tasks-running"` } + +// TrafficHistograms contains slices representing sent / received traffic, with each slice element +// corresponding to a `TrafficBucketSize` range. The last slice element represents the +Inf bucket. +type TrafficHistograms struct { + ReceivedUDPv4 []uint64 + SentUDPv4 []uint64 + ReceivedTCPv4 []uint64 + SentTCPv4 []uint64 + ReceivedUDPv6 []uint64 + SentUDPv6 []uint64 + ReceivedTCPv6 []uint64 + SentTCPv6 []uint64 +} diff --git a/bind/json/json.go b/bind/json/json.go index d20ecd2..add154c 100644 --- a/bind/json/json.go +++ b/bind/json/json.go @@ -20,6 +20,7 @@ import ( "net/url" "path" "strconv" + "strings" "time" "github.com/prometheus-community/bind_exporter/bind" @@ -30,6 +31,8 @@ const ( ServerPath = "/json/v1/server" // TasksPath is the HTTP path of the JSON v1 tasks resource. TasksPath = "/json/v1/tasks" + // TrafficPath is the HTTP path of the JSON v1 traffic resource. + TrafficPath = "/json/v1/traffic" // ZonesPath is the HTTP path of the JSON v1 zones resource. ZonesPath = "/json/v1/zones" ) @@ -71,6 +74,19 @@ type TaskStatistics struct { } `json:"taskmgr"` } +type TrafficStatistics struct { + Traffic struct { + ReceivedUDPv4 map[string]uint64 `json:"dns-udp-requests-sizes-received-ipv4"` + SentUDPv4 map[string]uint64 `json:"dns-udp-responses-sizes-sent-ipv4"` + ReceivedTCPv4 map[string]uint64 `json:"dns-tcp-requests-sizes-received-ipv4"` + SentTCPv4 map[string]uint64 `json:"dns-tcp-responses-sizes-sent-ipv4"` + ReceivedUDPv6 map[string]uint64 `json:"dns-udp-requests-sizes-received-ipv6"` + SentUDPv6 map[string]uint64 `json:"dns-udp-responses-sizes-sent-ipv6"` + ReceivedTCPv6 map[string]uint64 `json:"dns-tcp-requests-sizes-sent-ipv6"` + SentTCPv6 map[string]uint64 `json:"dns-tcp-responses-sizes-sent-ipv6"` + } `json:"traffic"` +} + // Client implements bind.Client and can be used to query a BIND JSON v1 API. type Client struct { url string @@ -191,5 +207,76 @@ func (c *Client) Stats(groups ...bind.StatisticGroup) (bind.Statistics, error) { s.TaskManager.ThreadModel.WorkerThreads = taskstats.TaskMgr.WorkerThreads } + if m[bind.TrafficStats] { + var trafficStats TrafficStatistics + if err := c.Get(TrafficPath, &trafficStats); err != nil { + return s, err + } + + var err error + + // Make IPv4 traffic histograms. + if s.TrafficHistograms.ReceivedUDPv4, err = processTrafficCounters(trafficStats.Traffic.ReceivedUDPv4, bind.TrafficInMaxSize); err != nil { + return s, err + } + if s.TrafficHistograms.SentUDPv4, err = processTrafficCounters(trafficStats.Traffic.SentUDPv4, bind.TrafficOutMaxSize); err != nil { + return s, err + } + if s.TrafficHistograms.ReceivedTCPv4, err = processTrafficCounters(trafficStats.Traffic.ReceivedTCPv4, bind.TrafficInMaxSize); err != nil { + return s, err + } + if s.TrafficHistograms.SentTCPv4, err = processTrafficCounters(trafficStats.Traffic.SentTCPv4, bind.TrafficOutMaxSize); err != nil { + return s, err + } + + // Make IPv6 traffic histograms. + if s.TrafficHistograms.ReceivedUDPv6, err = processTrafficCounters(trafficStats.Traffic.ReceivedUDPv6, bind.TrafficInMaxSize); err != nil { + return s, err + } + if s.TrafficHistograms.SentUDPv6, err = processTrafficCounters(trafficStats.Traffic.SentUDPv6, bind.TrafficOutMaxSize); err != nil { + return s, err + } + if s.TrafficHistograms.ReceivedTCPv6, err = processTrafficCounters(trafficStats.Traffic.ReceivedTCPv6, bind.TrafficInMaxSize); err != nil { + return s, err + } + if s.TrafficHistograms.SentTCPv6, err = processTrafficCounters(trafficStats.Traffic.SentTCPv6, bind.TrafficOutMaxSize); err != nil { + return s, err + } + } + return s, nil } + +func processTrafficCounters(traffic map[string]uint64, maxBucket uint) ([]uint64, error) { + trafficHist := make([]uint64, maxBucket/bind.TrafficBucketSize) + + for k, v := range traffic { + // Keys are in the format "lowerBound-upperBound". We are only interested in the upper + // bound. + parts := strings.Split(k, "-") + if len(parts) != 2 { + return nil, fmt.Errorf("malformed traffic bucket range: %q", k) + } + + upperBound, err := strconv.ParseUint(parts[1], 10, 16) + if err != nil { + return nil, fmt.Errorf("cannot convert bucket upper bound to uint: %w", err) + } + + if (upperBound+1)%bind.TrafficBucketSize != 0 { + return nil, fmt.Errorf("upper bucket bound is not a multiple of %d minus one: %d", + bind.TrafficBucketSize, upperBound) + } + + if upperBound < uint64(maxBucket) { + // idx is offset, since there is no 0-16 bucket reported by BIND. + idx := (upperBound+1)/bind.TrafficBucketSize - 2 + trafficHist[idx] = v + } else { + // Final slice element aggregates packet sizes from maxBucket to +Inf. + trafficHist[len(trafficHist)-1] += v + } + } + + return trafficHist, nil +} diff --git a/bind/xml/xml.go b/bind/xml/xml.go index cfa357c..a1ee5df 100644 --- a/bind/xml/xml.go +++ b/bind/xml/xml.go @@ -19,6 +19,8 @@ import ( "net/http" "net/url" "path" + "strconv" + "strings" "time" "github.com/prometheus-community/bind_exporter/bind" @@ -31,6 +33,8 @@ const ( StatusPath = "/xml/v3/status" // TasksPath is the HTTP path of the v3 tasks resource. TasksPath = "/xml/v3/tasks" + // TrafficPath is the HTTP path of the v3 traffic resource. + TrafficPath = "/xml/v3/traffic" // ZonesPath is the HTTP path of the v3 zones resource. ZonesPath = "/xml/v3/zones" @@ -86,6 +90,13 @@ type ZoneCounter struct { Serial string `xml:"serial"` } +type TrafficStatistics struct { + UDPv4 []Counters `xml:"traffic>ipv4>udp>counters"` + TCPv4 []Counters `xml:"traffic>ipv4>tcp>counters"` + UDPv6 []Counters `xml:"traffic>ipv6>udp>counters"` + TCPv6 []Counters `xml:"traffic>ipv6>tcp>counters"` +} + // Client implements bind.Client and can be used to query a BIND XML v3 API. type Client struct { url string @@ -136,7 +147,6 @@ func (c *Client) Stats(groups ...bind.StatisticGroup) (bind.Statistics, error) { } var stats Statistics - var zonestats ZoneStatistics if m[bind.ServerStats] || m[bind.ViewStats] { if err := c.Get(ServerPath, &stats); err != nil { return s, err @@ -176,6 +186,7 @@ func (c *Client) Stats(groups ...bind.StatisticGroup) (bind.Statistics, error) { } } + var zonestats ZoneStatistics if err := c.Get(ZonesPath, &zonestats); err != nil { return s, err } @@ -204,5 +215,100 @@ func (c *Client) Stats(groups ...bind.StatisticGroup) (bind.Statistics, error) { s.TaskManager = stats.Taskmgr } + if m[bind.TrafficStats] { + var trafficStats TrafficStatistics + if err := c.Get(TrafficPath, &trafficStats); err != nil { + return s, err + } + + var err error + + // Make IPv4 traffic histograms. + for _, cGroup := range trafficStats.UDPv4 { + switch cGroup.Type { + case "request-size": + if s.TrafficHistograms.ReceivedUDPv4, err = processTrafficCounters(cGroup.Counters, bind.TrafficInMaxSize); err != nil { + return s, err + } + case "response-size": + if s.TrafficHistograms.SentUDPv4, err = processTrafficCounters(cGroup.Counters, bind.TrafficOutMaxSize); err != nil { + return s, err + } + } + } + for _, cGroup := range trafficStats.TCPv4 { + switch cGroup.Type { + case "request-size": + if s.TrafficHistograms.ReceivedTCPv4, err = processTrafficCounters(cGroup.Counters, bind.TrafficInMaxSize); err != nil { + return s, err + } + case "response-size": + if s.TrafficHistograms.SentTCPv4, err = processTrafficCounters(cGroup.Counters, bind.TrafficOutMaxSize); err != nil { + return s, err + } + } + } + + // Make IPv6 traffic histograms. + for _, cGroup := range trafficStats.UDPv6 { + switch cGroup.Type { + case "request-size": + if s.TrafficHistograms.ReceivedUDPv6, err = processTrafficCounters(cGroup.Counters, bind.TrafficInMaxSize); err != nil { + return s, err + } + case "response-size": + if s.TrafficHistograms.SentUDPv6, err = processTrafficCounters(cGroup.Counters, bind.TrafficOutMaxSize); err != nil { + return s, err + } + } + } + for _, cGroup := range trafficStats.TCPv6 { + switch cGroup.Type { + case "request-size": + if s.TrafficHistograms.ReceivedTCPv6, err = processTrafficCounters(cGroup.Counters, bind.TrafficInMaxSize); err != nil { + return s, err + } + case "response-size": + if s.TrafficHistograms.SentTCPv6, err = processTrafficCounters(cGroup.Counters, bind.TrafficOutMaxSize); err != nil { + return s, err + } + } + } + } + return s, nil } + +func processTrafficCounters(traffic []bind.Counter, maxBucket uint) ([]uint64, error) { + trafficHist := make([]uint64, maxBucket/bind.TrafficBucketSize) + + for _, c := range traffic { + // Keys are in the format "lowerBound-upperBound". We are only interested in the upper + // bound. + parts := strings.Split(c.Name, "-") + if len(parts) != 2 { + return nil, fmt.Errorf("malformed traffic bucket range: %q", c.Name) + } + + upperBound, err := strconv.ParseUint(parts[1], 10, 16) + if err != nil { + return nil, fmt.Errorf("cannot convert bucket upper bound to uint: %w", err) + } + + if (upperBound+1)%bind.TrafficBucketSize != 0 { + return nil, fmt.Errorf("upper bucket bound is not a multiple of %d minus one: %d", + bind.TrafficBucketSize, upperBound) + } + + if upperBound < uint64(maxBucket) { + // idx is offset, since there is no 0-16 bucket reported by BIND. + idx := (upperBound+1)/bind.TrafficBucketSize - 2 + trafficHist[idx] = c.Counter + } else { + // Final slice element aggregates packet sizes from maxBucket to +Inf. + trafficHist[len(trafficHist)-1] += c.Counter + } + } + + return trafficHist, nil +} diff --git a/bind_exporter.go b/bind_exporter.go index bac37ff..4aa38be 100644 --- a/bind_exporter.go +++ b/bind_exporter.go @@ -218,6 +218,16 @@ var ( "Zone serial number.", []string{"view", "zone_name"}, nil, ) + trafficReceived = prometheus.NewDesc( + prometheus.BuildFQName(namespace, "traffic", "received_size"), + "Received traffic packet sizes.", + []string{"transport"}, nil, + ) + trafficSent = prometheus.NewDesc( + prometheus.BuildFQName(namespace, "traffic", "sent_size"), + "Sent traffic packet sizes.", + []string{"transport"}, nil, + ) ) type collectorConstructor func(log.Logger, *bind.Statistics) prometheus.Collector @@ -387,6 +397,87 @@ func (c *taskCollector) Collect(ch chan<- prometheus.Metric) { ) } +type trafficCollector struct { + logger log.Logger + stats *bind.Statistics +} + +// newTrafficCollector implements collectorConstructor. +func newTrafficCollector(logger log.Logger, s *bind.Statistics) prometheus.Collector { + return &trafficCollector{logger: logger, stats: s} +} + +// Describe implements prometheus.Collector. +func (c *trafficCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- trafficReceived + ch <- trafficSent +} + +// Collect implements prometheus.Collector. +func (c *trafficCollector) Collect(ch chan<- prometheus.Metric) { + // IPv4 traffic histograms. + buckets, count := c.makeHistogram(c.stats.TrafficHistograms.ReceivedUDPv4) + ch <- prometheus.MustNewConstHistogram( + trafficReceived, count, math.NaN(), buckets, "udpv4", + ) + buckets, count = c.makeHistogram(c.stats.TrafficHistograms.SentUDPv4) + ch <- prometheus.MustNewConstHistogram( + trafficSent, count, math.NaN(), buckets, "udpv4", + ) + buckets, count = c.makeHistogram(c.stats.TrafficHistograms.ReceivedTCPv4) + ch <- prometheus.MustNewConstHistogram( + trafficReceived, count, math.NaN(), buckets, "tcpv4", + ) + buckets, count = c.makeHistogram(c.stats.TrafficHistograms.SentTCPv4) + ch <- prometheus.MustNewConstHistogram( + trafficSent, count, math.NaN(), buckets, "tcpv4", + ) + + // IPv6 traffic histograms. + buckets, count = c.makeHistogram(c.stats.TrafficHistograms.ReceivedUDPv6) + ch <- prometheus.MustNewConstHistogram( + trafficReceived, count, math.NaN(), buckets, "udpv6", + ) + buckets, count = c.makeHistogram(c.stats.TrafficHistograms.SentUDPv6) + ch <- prometheus.MustNewConstHistogram( + trafficSent, count, math.NaN(), buckets, "udpv6", + ) + buckets, count = c.makeHistogram(c.stats.TrafficHistograms.ReceivedTCPv6) + ch <- prometheus.MustNewConstHistogram( + trafficReceived, count, math.NaN(), buckets, "tcpv6", + ) + buckets, count = c.makeHistogram(c.stats.TrafficHistograms.SentTCPv6) + ch <- prometheus.MustNewConstHistogram( + trafficSent, count, math.NaN(), buckets, "tcpv6", + ) +} + +// makeHistogram translates the non-aggregated bucket slice into an aggregated map, suitable for +// use by prometheus.MustNewConstHistogram(). +func (c *trafficCollector) makeHistogram(rawBuckets []uint64) (map[float64]uint64, uint64) { + var ( + buckets = map[float64]uint64{} + count uint64 + ) + + for i, v := range rawBuckets { + if v > 0 { + var idx float64 + + if i == len(rawBuckets)-1 { + idx = math.Inf(1) + } else { + idx = float64((i+2)*bind.TrafficBucketSize) - 1 + } + + count += v + buckets[idx] = count + } + } + + return buckets, count +} + // Exporter collects Binds stats from the given server and exports them using // the prometheus metrics package. type Exporter struct { @@ -411,10 +502,12 @@ func NewExporter(logger log.Logger, version, url string, timeout time.Duration, switch g { case bind.ServerStats: cs = append(cs, newServerCollector) - case bind.ViewStats: - cs = append(cs, newViewCollector) case bind.TaskStats: cs = append(cs, newTaskCollector) + case bind.TrafficStats: + cs = append(cs, newTrafficCollector) + case bind.ViewStats: + cs = append(cs, newViewCollector) } } @@ -503,10 +596,12 @@ func (s *statisticGroups) Set(value string) error { switch dt { case string(bind.ServerStats): sg = bind.ServerStats - case string(bind.ViewStats): - sg = bind.ViewStats case string(bind.TaskStats): sg = bind.TaskStats + case string(bind.TrafficStats): + sg = bind.TrafficStats + case string(bind.ViewStats): + sg = bind.ViewStats default: return fmt.Errorf("unknown stats group %q", dt) } @@ -544,7 +639,8 @@ func main() { toolkitFlags := webflag.AddFlags(kingpin.CommandLine, ":9119") kingpin.Flag("bind.stats-groups", - "Comma-separated list of statistics to collect", + "Comma-separated list of statistics to collect. "+ + "One or more of: [server, tasks, traffic, view]", ).Default((&statisticGroups{ bind.ServerStats, bind.ViewStats, }).String()).SetValue(&groups) diff --git a/bind_exporter_test.go b/bind_exporter_test.go index 98f09bb..9811dab 100644 --- a/bind_exporter_test.go +++ b/bind_exporter_test.go @@ -70,23 +70,103 @@ var ( `bind_tasks_running 8`, `bind_worker_threads 16`, } + trafficStats = []string{ + `bind_traffic_received_size_bucket{transport="tcpv4",le="31"} 18600`, + `bind_traffic_received_size_bucket{transport="tcpv4",le="47"} 111799`, + `bind_traffic_received_size_bucket{transport="tcpv4",le="63"} 134091`, + `bind_traffic_received_size_bucket{transport="tcpv4",le="79"} 134798`, + `bind_traffic_received_size_bucket{transport="tcpv4",le="95"} 134801`, + `bind_traffic_received_size_bucket{transport="tcpv4",le="+Inf"} 134801`, + `bind_traffic_received_size_sum{transport="tcpv4"} NaN`, + `bind_traffic_received_size_count{transport="tcpv4"} 134801`, + `bind_traffic_received_size_bucket{transport="udpv4",le="31"} 9992`, + `bind_traffic_received_size_bucket{transport="udpv4",le="47"} 82206`, + `bind_traffic_received_size_bucket{transport="udpv4",le="63"} 108619`, + `bind_traffic_received_size_bucket{transport="udpv4",le="79"} 109630`, + `bind_traffic_received_size_bucket{transport="udpv4",le="95"} 109683`, + `bind_traffic_received_size_bucket{transport="udpv4",le="111"} 109688`, + `bind_traffic_received_size_bucket{transport="udpv4",le="127"} 109691`, + `bind_traffic_received_size_bucket{transport="udpv4",le="+Inf"} 109691`, + `bind_traffic_received_size_sum{transport="udpv4"} NaN`, + `bind_traffic_received_size_count{transport="udpv4"} 109691`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="31"} 28`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="47"} 231`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="63"} 16733`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="79"} 30487`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="95"} 49751`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="111"} 63255`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="127"} 72236`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="143"} 87215`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="159"} 94972`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="175"} 104161`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="191"} 111804`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="207"} 118777`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="223"} 121560`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="239"} 126934`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="255"} 129430`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="271"} 131161`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="287"} 131623`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="303"} 132135`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="319"} 133212`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="335"} 134165`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="351"} 134176`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="367"} 134485`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="383"} 134591`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="399"} 134671`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="415"} 134680`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="447"} 134801`, + `bind_traffic_sent_size_bucket{transport="tcpv4",le="+Inf"} 134801`, + `bind_traffic_sent_size_sum{transport="tcpv4"} NaN`, + `bind_traffic_sent_size_count{transport="tcpv4"} 134801`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="31"} 11`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="47"} 666`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="63"} 10038`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="79"} 18547`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="95"} 34436`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="111"} 53146`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="127"} 59515`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="143"} 66961`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="159"} 72058`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="175"} 78111`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="191"} 82872`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="207"} 85954`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="223"} 89229`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="239"} 93461`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="255"} 94787`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="271"} 96226`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="287"} 96579`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="303"} 97020`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="319"} 97653`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="335"} 97685`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="351"} 97701`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="367"} 97717`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="383"} 97793`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="399"} 97820`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="415"} 97860`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="431"} 97899`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="447"} 97918`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="479"} 97951`, + `bind_traffic_sent_size_bucket{transport="udpv4",le="+Inf"} 97951`, + `bind_traffic_sent_size_sum{transport="udpv4"} NaN`, + `bind_traffic_sent_size_count{transport="udpv4"} 97951`, + } ) func TestBindExporterJSONClient(t *testing.T) { bindExporterTest{ server: newJSONServer(), - groups: []bind.StatisticGroup{bind.ServerStats, bind.ViewStats, bind.TaskStats}, + groups: []bind.StatisticGroup{bind.ServerStats, bind.ViewStats, bind.TaskStats, bind.TrafficStats}, version: "json", - include: combine([]string{`bind_up 1`}, serverStats, viewStats, taskStats), + include: combine([]string{`bind_up 1`}, serverStats, viewStats, taskStats, trafficStats), }.run(t) } func TestBindExporterV3Client(t *testing.T) { bindExporterTest{ server: newV3Server(), - groups: []bind.StatisticGroup{bind.ServerStats, bind.ViewStats, bind.TaskStats}, + groups: []bind.StatisticGroup{bind.ServerStats, bind.ViewStats, bind.TaskStats, bind.TrafficStats}, version: "xml.v3", - include: combine([]string{`bind_up 1`}, serverStats, viewStats, taskStats), + include: combine([]string{`bind_up 1`}, serverStats, viewStats, taskStats, trafficStats), }.run(t) } @@ -156,10 +236,11 @@ func collect(c prometheus.Collector) ([]byte, error) { func newV3Server() *httptest.Server { m := map[string]string{ - "/xml/v3/server": "fixtures/xml/server.xml", - "/xml/v3/status": "fixtures/xml/status.xml", - "/xml/v3/tasks": "fixtures/xml/tasks.xml", - "/xml/v3/zones": "fixtures/xml/zones.xml", + "/xml/v3/server": "fixtures/xml/server.xml", + "/xml/v3/status": "fixtures/xml/status.xml", + "/xml/v3/tasks": "fixtures/xml/tasks.xml", + "/xml/v3/traffic": "fixtures/xml/traffic.xml", + "/xml/v3/zones": "fixtures/xml/zones.xml", } return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if f, ok := m[r.RequestURI]; ok { @@ -172,9 +253,10 @@ func newV3Server() *httptest.Server { func newJSONServer() *httptest.Server { m := map[string]string{ - "/json/v1/server": "fixtures/json/server.json", - "/json/v1/tasks": "fixtures/json/tasks.json", - "/json/v1/zones": "fixtures/json/zones.json", + "/json/v1/server": "fixtures/json/server.json", + "/json/v1/tasks": "fixtures/json/tasks.json", + "/json/v1/traffic": "fixtures/json/traffic.json", + "/json/v1/zones": "fixtures/json/zones.json", } return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if f, ok := m[r.RequestURI]; ok { diff --git a/fixtures/json/traffic.json b/fixtures/json/traffic.json new file mode 100644 index 0000000..6c43100 --- /dev/null +++ b/fixtures/json/traffic.json @@ -0,0 +1,87 @@ +{ + "json-stats-version":"1.8", + "boot-time":"2024-02-04T17:26:28.754Z", + "config-time":"2024-02-04T17:26:28.846Z", + "current-time":"2024-02-16T23:34:43.797Z", + "version":"9.19.19-1-Debian", + "traffic":{ + "dns-udp-requests-sizes-received-ipv4":{ + "16-31":9992, + "32-47":72214, + "48-63":26413, + "64-79":1011, + "80-95":53, + "96-111":5, + "112-127":3 + }, + "dns-udp-responses-sizes-sent-ipv4":{ + "16-31":11, + "32-47":655, + "48-63":9372, + "64-79":8509, + "80-95":15889, + "96-111":18710, + "112-127":6369, + "128-143":7446, + "144-159":5097, + "160-175":6053, + "176-191":4761, + "192-207":3082, + "208-223":3275, + "224-239":4232, + "240-255":1326, + "256-271":1439, + "272-287":353, + "288-303":441, + "304-319":633, + "320-335":32, + "336-351":16, + "352-367":16, + "368-383":76, + "384-399":27, + "400-415":40, + "416-431":39, + "432-447":19, + "464-479":33 + }, + "dns-tcp-requests-sizes-received-ipv4":{ + "16-31":18600, + "32-47":93199, + "48-63":22292, + "64-79":707, + "80-95":3 + }, + "dns-tcp-responses-sizes-sent-ipv4":{ + "16-31":28, + "32-47":203, + "48-63":16502, + "64-79":13754, + "80-95":19264, + "96-111":13504, + "112-127":8981, + "128-143":14979, + "144-159":7757, + "160-175":9189, + "176-191":7643, + "192-207":6973, + "208-223":2783, + "224-239":5374, + "240-255":2496, + "256-271":1731, + "272-287":462, + "288-303":512, + "304-319":1077, + "320-335":953, + "336-351":11, + "352-367":309, + "368-383":106, + "384-399":80, + "400-415":9, + "432-447":121 + }, + "dns-udp-requests-sizes-received-ipv6":{}, + "dns-udp-responses-sizes-sent-ipv6":{}, + "dns-tcp-requests-sizes-received-ipv6":{}, + "dns-tcp-responses-sizes-sent-ipv6":{} + } +} \ No newline at end of file diff --git a/fixtures/xml/traffic.xml b/fixtures/xml/traffic.xml new file mode 100644 index 0000000..a611c87 --- /dev/null +++ b/fixtures/xml/traffic.xml @@ -0,0 +1,103 @@ + + + + + 2024-02-04T17:26:28.754Z + 2024-02-04T17:26:28.846Z + 2024-02-16T23:35:08.782Z + 9.19.19-1-Debian + + + + + + 9992 + 72214 + 26413 + 1011 + 53 + 5 + 3 + + + 11 + 655 + 9372 + 8509 + 15889 + 18710 + 6369 + 7446 + 5097 + 6053 + 4761 + 3082 + 3275 + 4232 + 1326 + 1439 + 353 + 441 + 633 + 32 + 16 + 16 + 76 + 27 + 40 + 39 + 19 + 33 + + + + + 18600 + 93199 + 22292 + 707 + 3 + + + 28 + 203 + 16502 + 13754 + 19264 + 13504 + 8981 + 14979 + 7757 + 9189 + 7643 + 6973 + 2783 + 5374 + 2496 + 1731 + 462 + 512 + 1077 + 953 + 11 + 309 + 106 + 80 + 9 + 121 + + + + + + + + + + + + + + + +