From dfa3c0d129d553761374caaea2e8ce20e1191afc 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 | 11 ++++++++++- collector_test.go | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ad734c5f..097b8b9b 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-snmp.wrap-large-counters`. diff --git a/collector.go b/collector.go index 824931be..6020b3af 100644 --- a/collector.go +++ b/collector.go @@ -24,6 +24,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/log" "github.com/soniah/gosnmp" + "gopkg.in/alecthomas/kingpin.v2" "github.com/prometheus/snmp_exporter/config" ) @@ -35,6 +36,9 @@ var ( Help: "Unexpected Go types in a PDU.", }, ) + // 64-bit float mantissa: https://en.wikipedia.org/wiki/Double-precision_floating-point_format + float64Mantissa uint64 = 9007199254740992 + wrapCounters = kingpin.Flag("snmp.wrap-large-counters", "Wrap 64-bit counters to avoid floating point rounding.").Default("true").Bool() ) func init() { @@ -252,7 +256,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() % float64Mantissa) + } 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 aded49a0..0510920d 100644 --- a/collector_test.go +++ b/collector_test.go @@ -20,7 +20,9 @@ import ( "testing" "github.com/prometheus/client_model/go" + "github.com/prometheus/common/log" "github.com/soniah/gosnmp" + 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-snmp.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