1
0
Fork 0
telegraf/plugins/inputs/bind/xml_stats_v3.go

205 lines
5.1 KiB
Go
Raw Permalink Normal View History

package bind
import (
"encoding/xml"
"fmt"
"math"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
)
// XML path: //statistics
// Omitted branches: socketmgr, taskmgr
type v3Stats struct {
Server v3Server `xml:"server"`
Views []v3View `xml:"views>view"`
Memory v3Memory `xml:"memory"`
}
// XML path: //statistics/memory
type v3Memory struct {
Contexts []struct {
// Omitted nodes: references, maxinuse, blocksize, pools, hiwater, lowater
ID string `xml:"id"`
Name string `xml:"name"`
Total uint64 `xml:"total"`
InUse uint64 `xml:"inuse"`
} `xml:"contexts>context"`
Summary struct {
TotalUse uint64
InUse uint64
BlockSize uint64
ContextSize uint64
Lost uint64
} `xml:"summary"`
}
// XML path: //statistics/server
type v3Server struct {
CounterGroups []v3CounterGroup `xml:"counters"`
}
// XML path: //statistics/views/view
type v3View struct {
// Omitted branches: zones
Name string `xml:"name,attr"`
CounterGroups []v3CounterGroup `xml:"counters"`
Caches []struct {
Name string `xml:"name,attr"`
RRSets []struct {
Name string `xml:"name"`
Value uint64 `xml:"counter"`
} `xml:"rrset"`
} `xml:"cache"`
}
// Generic XML v3 doc fragment used in multiple places
type v3CounterGroup struct {
Type string `xml:"type,attr"`
Counters []struct {
Name string `xml:"name,attr"`
Value uint64 `xml:",chardata"`
} `xml:"counter"`
}
// addStatsXMLv3 walks a v3Stats struct and adds the values to the telegraf.Accumulator.
func (b *Bind) addStatsXMLv3(stats v3Stats, acc telegraf.Accumulator, hostPort string) {
grouper := metric.NewSeriesGrouper()
ts := time.Now()
host, port, err := net.SplitHostPort(hostPort)
if err != nil {
acc.AddError(err)
}
// Counter groups
for _, cg := range stats.Server.CounterGroups {
for _, c := range cg.Counters {
if cg.Type == "opcode" && strings.HasPrefix(c.Name, "RESERVED") {
continue
}
tags := map[string]string{"url": hostPort, "source": host, "port": port, "type": cg.Type}
var v interface{} = c.Value
if b.CountersAsInt {
if c.Value < math.MaxInt64 {
v = int64(c.Value)
} else {
v = int64(math.MaxInt64)
}
}
grouper.Add("bind_counter", tags, ts, c.Name, v)
}
}
// Memory stats
fields := map[string]interface{}{
"total_use": stats.Memory.Summary.TotalUse,
"in_use": stats.Memory.Summary.InUse,
"block_size": stats.Memory.Summary.BlockSize,
"context_size": stats.Memory.Summary.ContextSize,
"lost": stats.Memory.Summary.Lost,
}
b.postProcessFields(fields)
acc.AddGauge("bind_memory", fields, map[string]string{"url": hostPort, "source": host, "port": port})
// Detailed, per-context memory stats
if b.GatherMemoryContexts {
for _, c := range stats.Memory.Contexts {
tags := map[string]string{"url": hostPort, "source": host, "port": port, "id": c.ID, "name": c.Name}
fields := map[string]interface{}{"total": c.Total, "in_use": c.InUse}
b.postProcessFields(fields)
acc.AddGauge("bind_memory_context", fields, tags)
}
}
// Detailed, per-view stats
if b.GatherViews {
for _, v := range stats.Views {
for _, cg := range v.CounterGroups {
for _, c := range cg.Counters {
tags := map[string]string{
"url": hostPort,
"source": host,
"port": port,
"view": v.Name,
"type": cg.Type,
}
var v interface{} = c.Value
if b.CountersAsInt {
if c.Value < math.MaxInt64 {
v = int64(c.Value)
} else {
v = int64(math.MaxInt64)
}
}
grouper.Add("bind_counter", tags, ts, c.Name, v)
}
}
}
}
// Add grouped metrics
for _, groupedMetric := range grouper.Metrics() {
acc.AddMetric(groupedMetric)
}
}
// readStatsXMLv3 takes a base URL to probe, and requests the individual statistics documents that
// we are interested in. These individual documents have a combined size which is significantly
// smaller than if we requested everything at once (e.g. taskmgr and socketmgr can be omitted).
func (b *Bind) readStatsXMLv3(addr *url.URL, acc telegraf.Accumulator) error {
var stats v3Stats
// Progressively build up full v3Stats struct by parsing the individual HTTP responses
for _, suffix := range [...]string{"/server", "/net", "/mem"} {
err := func() error {
scrapeURL := addr.String() + suffix
resp, err := b.client.Get(scrapeURL)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%s returned HTTP status: %s", scrapeURL, resp.Status)
}
if err := xml.NewDecoder(resp.Body).Decode(&stats); err != nil {
return fmt.Errorf("unable to decode XML document: %w", err)
}
return nil
}()
if err != nil {
return err
}
}
b.addStatsXMLv3(stats, acc, addr.Host)
return nil
}
func (b *Bind) postProcessFields(fields map[string]interface{}) {
if !b.CountersAsInt {
return
}
for k, val := range fields {
if v, ok := val.(uint64); ok {
if v < math.MaxInt64 {
fields[k] = int64(v)
} else {
fields[k] = int64(math.MaxInt64)
}
}
}
}