Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse traffic request / response size histograms #203

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions bind/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,38 @@ 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.
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.
Expand Down Expand Up @@ -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
}
87 changes: 87 additions & 0 deletions bind/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"net/url"
"path"
"strconv"
"strings"
"time"

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