diff --git a/README.md b/README.md index ad734c5f4..097b8b9be 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 824931bec..6020b3aff 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 aded49a06..0510920d6 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