From 3d47144f9b98d932ddb6f5e2b246fe74d5b31995 Mon Sep 17 00:00:00 2001 From: Ben Kochie Date: Fri, 2 Nov 2018 10:38:15 +0100 Subject: [PATCH] Add automatic Conter64 wrapping Automatically wrap Counter64 PDU values every 2^53 to avoid float64 precision loss. Add a command line flag to enable/disable this feature, enable by default. Signed-off-by: Ben Kochie --- README.md | 7 +++++++ collector.go | 7 ++++++- collector_test.go | 34 ++++++++++++++++++++++++++++++++++ main.go | 1 + 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ad734c5f4..4957a1e73 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,10 @@ scrape_configs: This setup allows Prometheus to provide scheduling and service discovery, as unlike all other exporters running an exporter on the machine from which we are getting the metrics from is not possible. + +## Large counter value handling + +In order to provide accurate counters for large Counter64 values, the exporter will automatically +wrap the value every 2^53 to avoid 64-bit float rounding. + +To disable this feature, use the command line flag `--no-wrap-large-counters`. diff --git a/collector.go b/collector.go index 824931bec..f574cb4f0 100644 --- a/collector.go +++ b/collector.go @@ -252,7 +252,12 @@ PduLoop: func getPduValue(pdu *gosnmp.SnmpPDU) float64 { switch pdu.Type { case gosnmp.Counter64: - return float64(gosnmp.ToBigInt(pdu.Value).Uint64()) + if *wrapCounters { + // Wrap by 2^53 + return float64(gosnmp.ToBigInt(pdu.Value).Uint64() % 9007199254740992) + } else { + return float64(gosnmp.ToBigInt(pdu.Value).Uint64()) + } case gosnmp.OpaqueFloat: return float64(pdu.Value.(float32)) case gosnmp.OpaqueDouble: diff --git a/collector_test.go b/collector_test.go index aded49a06..fd85dca14 100644 --- a/collector_test.go +++ b/collector_test.go @@ -21,6 +21,8 @@ import ( "github.com/prometheus/client_model/go" "github.com/soniah/gosnmp" + "github.com/prometheus/common/log" + kingpin "gopkg.in/alecthomas/kingpin.v2" "github.com/prometheus/snmp_exporter/config" ) @@ -462,6 +464,38 @@ func TestGetPduValue(t *testing.T) { } } +func TestGetPduLargeValue(t *testing.T) { + // Setup default flags and suppress logging. + log.AddFlags(kingpin.CommandLine) + _, err := kingpin.CommandLine.Parse([]string{"--log.level", "fatal"}) + if err != nil { + t.Fatal(err) + } + + pdu := &gosnmp.SnmpPDU{ + Value: uint64(19007199254740992), + Type: gosnmp.Counter64, + } + value := getPduValue(pdu) + if value != 992800745259008.0 { + t.Fatalf("Got incorrect counter wrapping for Counter64: %v", value) + } + + _, err = kingpin.CommandLine.Parse([]string{"--log.level", "fatal", "--no-wrap-large-counters"}) + if err != nil { + t.Fatal(err) + } + + pdu = &gosnmp.SnmpPDU{ + Value: uint64(19007199254740992), + Type: gosnmp.Counter64, + } + value = getPduValue(pdu) + if value != 19007199254740990.0 { + t.Fatalf("Got incorrect rounded float for Counter64: %v", value) + } +} + func TestOidToList(t *testing.T) { cases := []struct { oid string diff --git a/main.go b/main.go index 9f5d16e3d..d601f9a86 100644 --- a/main.go +++ b/main.go @@ -37,6 +37,7 @@ import ( var ( configFile = kingpin.Flag("config.file", "Path to configuration file.").Default("snmp.yml").String() listenAddress = kingpin.Flag("web.listen-address", "Address to listen on for web interface and telemetry.").Default(":9116").String() + wrapCounters = kingpin.Flag("wrap-large-counters", "Wrap 64-bit counters to avoid floating point rounding.").Default("true").Bool() // Metrics about the SNMP exporter itself. snmpDuration = prometheus.NewSummaryVec(