Skip to content

Commit

Permalink
Parse request / response size histograms (via XMLv3 stats)
Browse files Browse the repository at this point in the history
This implements parsing of the DNS request / response traffic size
histograms, for the JSON statistics channel.

Refs: prometheus-community#64

Signed-off-by: Daniel Swarbrick <[email protected]>
  • Loading branch information
dswarbrick committed Feb 17, 2024
1 parent c6985a2 commit 3b22822
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 22 deletions.
20 changes: 10 additions & 10 deletions bind/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ 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-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"`
Expand Down Expand Up @@ -216,38 +216,38 @@ func (c *Client) Stats(groups ...bind.StatisticGroup) (bind.Statistics, error) {
var err error

// Make IPv4 traffic histograms.
if s.TrafficHistograms.ReceivedUDPv4, err = parseTrafficHist(trafficStats.Traffic.ReceivedUDPv4, bind.TrafficInMaxSize); err != nil {
if s.TrafficHistograms.ReceivedUDPv4, err = processTrafficCounters(trafficStats.Traffic.ReceivedUDPv4, bind.TrafficInMaxSize); err != nil {
return s, err
}
if s.TrafficHistograms.SentUDPv4, err = parseTrafficHist(trafficStats.Traffic.SentUDPv4, bind.TrafficOutMaxSize); err != nil {
if s.TrafficHistograms.SentUDPv4, err = processTrafficCounters(trafficStats.Traffic.SentUDPv4, bind.TrafficOutMaxSize); err != nil {
return s, err
}
if s.TrafficHistograms.ReceivedTCPv4, err = parseTrafficHist(trafficStats.Traffic.ReceivedTCPv4, bind.TrafficInMaxSize); err != nil {
if s.TrafficHistograms.ReceivedTCPv4, err = processTrafficCounters(trafficStats.Traffic.ReceivedTCPv4, bind.TrafficInMaxSize); err != nil {
return s, err
}
if s.TrafficHistograms.SentTCPv4, err = parseTrafficHist(trafficStats.Traffic.SentTCPv4, bind.TrafficOutMaxSize); err != nil {
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 = parseTrafficHist(trafficStats.Traffic.ReceivedUDPv6, bind.TrafficInMaxSize); err != nil {
if s.TrafficHistograms.ReceivedUDPv6, err = processTrafficCounters(trafficStats.Traffic.ReceivedUDPv6, bind.TrafficInMaxSize); err != nil {
return s, err
}
if s.TrafficHistograms.SentUDPv6, err = parseTrafficHist(trafficStats.Traffic.SentUDPv6, bind.TrafficOutMaxSize); err != nil {
if s.TrafficHistograms.SentUDPv6, err = processTrafficCounters(trafficStats.Traffic.SentUDPv6, bind.TrafficOutMaxSize); err != nil {
return s, err
}
if s.TrafficHistograms.ReceivedTCPv6, err = parseTrafficHist(trafficStats.Traffic.ReceivedTCPv6, bind.TrafficInMaxSize); err != nil {
if s.TrafficHistograms.ReceivedTCPv6, err = processTrafficCounters(trafficStats.Traffic.ReceivedTCPv6, bind.TrafficInMaxSize); err != nil {
return s, err
}
if s.TrafficHistograms.SentTCPv6, err = parseTrafficHist(trafficStats.Traffic.SentTCPv6, bind.TrafficOutMaxSize); err != nil {
if s.TrafficHistograms.SentTCPv6, err = processTrafficCounters(trafficStats.Traffic.SentTCPv6, bind.TrafficOutMaxSize); err != nil {
return s, err
}
}

return s, nil
}

func parseTrafficHist(traffic map[string]uint64, maxBucket uint) ([]uint64, error) {
func processTrafficCounters(traffic map[string]uint64, maxBucket uint) ([]uint64, error) {
trafficHist := make([]uint64, maxBucket/bind.TrafficBucketSize)

for k, v := range traffic {
Expand Down
108 changes: 107 additions & 1 deletion bind/xml/xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"

"github.com/prometheus-community/bind_exporter/bind"
Expand All @@ -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"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
25 changes: 14 additions & 11 deletions bind_exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,14 @@ var (
`bind_worker_threads 16`,
}
trafficStats = []string{
`bind_traffic_received_size_bucket{transport="tcpv4",le="+Inf"} 0`,
`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"} 0`,
`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`,
Expand Down Expand Up @@ -144,9 +149,6 @@ var (
`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`,
`bind_traffic_sent_size_bucket{transport="udpv6",le="+Inf"} 0`,
`bind_traffic_sent_size_sum{transport="udpv6"} NaN`,
`bind_traffic_sent_size_count{transport="udpv6"} 0`,
}
)

Expand All @@ -162,9 +164,9 @@ func TestBindExporterJSONClient(t *testing.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)
}

Expand Down Expand Up @@ -234,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 {
Expand Down
103 changes: 103 additions & 0 deletions fixtures/xml/traffic.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/bind9.xsl"?>
<statistics version="3.14">
<server>
<boot-time>2024-02-04T17:26:28.754Z</boot-time>
<config-time>2024-02-04T17:26:28.846Z</config-time>
<current-time>2024-02-16T23:35:08.782Z</current-time>
<version>9.19.19-1-Debian</version>
</server>
<traffic>
<ipv4>
<udp>
<counters type="request-size">
<counter name="16-31">9992</counter>
<counter name="32-47">72214</counter>
<counter name="48-63">26413</counter>
<counter name="64-79">1011</counter>
<counter name="80-95">53</counter>
<counter name="96-111">5</counter>
<counter name="112-127">3</counter>
</counters>
<counters type="response-size">
<counter name="16-31">11</counter>
<counter name="32-47">655</counter>
<counter name="48-63">9372</counter>
<counter name="64-79">8509</counter>
<counter name="80-95">15889</counter>
<counter name="96-111">18710</counter>
<counter name="112-127">6369</counter>
<counter name="128-143">7446</counter>
<counter name="144-159">5097</counter>
<counter name="160-175">6053</counter>
<counter name="176-191">4761</counter>
<counter name="192-207">3082</counter>
<counter name="208-223">3275</counter>
<counter name="224-239">4232</counter>
<counter name="240-255">1326</counter>
<counter name="256-271">1439</counter>
<counter name="272-287">353</counter>
<counter name="288-303">441</counter>
<counter name="304-319">633</counter>
<counter name="320-335">32</counter>
<counter name="336-351">16</counter>
<counter name="352-367">16</counter>
<counter name="368-383">76</counter>
<counter name="384-399">27</counter>
<counter name="400-415">40</counter>
<counter name="416-431">39</counter>
<counter name="432-447">19</counter>
<counter name="464-479">33</counter>
</counters>
</udp>
<tcp>
<counters type="request-size">
<counter name="16-31">18600</counter>
<counter name="32-47">93199</counter>
<counter name="48-63">22292</counter>
<counter name="64-79">707</counter>
<counter name="80-95">3</counter>
</counters>
<counters type="response-size">
<counter name="16-31">28</counter>
<counter name="32-47">203</counter>
<counter name="48-63">16502</counter>
<counter name="64-79">13754</counter>
<counter name="80-95">19264</counter>
<counter name="96-111">13504</counter>
<counter name="112-127">8981</counter>
<counter name="128-143">14979</counter>
<counter name="144-159">7757</counter>
<counter name="160-175">9189</counter>
<counter name="176-191">7643</counter>
<counter name="192-207">6973</counter>
<counter name="208-223">2783</counter>
<counter name="224-239">5374</counter>
<counter name="240-255">2496</counter>
<counter name="256-271">1731</counter>
<counter name="272-287">462</counter>
<counter name="288-303">512</counter>
<counter name="304-319">1077</counter>
<counter name="320-335">953</counter>
<counter name="336-351">11</counter>
<counter name="352-367">309</counter>
<counter name="368-383">106</counter>
<counter name="384-399">80</counter>
<counter name="400-415">9</counter>
<counter name="432-447">121</counter>
</counters>
</tcp>
</ipv4>
<ipv6>
<udp>
<counters type="request-size"/>
<counters type="response-size"/>
</udp>
<tcp>
<counters type="request-size"/>
<counters type="response-size"/>
</tcp>
</ipv6>
</traffic>
<views/>
</statistics>

0 comments on commit 3b22822

Please sign in to comment.