Skip to content

Commit

Permalink
Add type to parse date and time (fixes: #1232)
Browse files Browse the repository at this point in the history
Add new type ParseDateAndTime to parse timestamps from DisplayString
and report it as unix timestamp.

Signed-off-by: PhiBo <[email protected]>
  • Loading branch information
phibos committed Aug 13, 2024
1 parent 5477ec9 commit 0740933
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 17 deletions.
16 changes: 16 additions & 0 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,15 @@ func parseDateAndTime(pdu *gosnmp.SnmpPDU) (float64, error) {
return float64(t.Unix()), nil
}

func parseDateAndTimeWithPattern(metric *config.Metric, pdu *gosnmp.SnmpPDU, metrics Metrics) (float64, error) {
pduValue := pduValueAsString(pdu, "DisplayString", metrics)
t, err := time.Parse(metric.DateTimePattern, pduValue)
if err != nil {
return 0, fmt.Errorf("error parsing date and time %q", err)
}
return float64(t.Unix()), nil
}

func pduToSamples(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, oidToPdu map[string]gosnmp.SnmpPDU, logger log.Logger, metrics Metrics) []prometheus.Metric {
var err error
// The part of the OID that is the indexes.
Expand Down Expand Up @@ -573,6 +582,13 @@ func pduToSamples(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, o
level.Debug(logger).Log("msg", "Error parsing DateAndTime", "err", err)
return []prometheus.Metric{}
}
case "ParseDateAndTime":
t = prometheus.GaugeValue
value, err = parseDateAndTimeWithPattern(metric, pdu, metrics)
if err != nil {
level.Debug(logger).Log("msg", "Error parsing ParseDateAndTime", "err", err)
return []prometheus.Metric{}
}
case "EnumAsInfo":
return enumAsInfo(metric, int(value), labelnames, labelvalues)
case "EnumAsStateSet":
Expand Down
34 changes: 34 additions & 0 deletions collector/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,40 @@ func TestParseDateAndTime(t *testing.T) {
}
}

func TestParseDateAndTimeWithPattern(t *testing.T) {
cases := []struct {
pdu *gosnmp.SnmpPDU
metric config.Metric
result float64
shouldErr bool
}{
{
pdu: &gosnmp.SnmpPDU{Value: "Apr 01 2025"},
metric: config.Metric{DateTimePattern: "Jan 02 2006"},
result: 1.7434656e+09,
shouldErr: false,
},
{
pdu: &gosnmp.SnmpPDU{Value: "ABC"},
metric: config.Metric{DateTimePattern: "Jan 02 2006"},
result: 0,
shouldErr: true,
},
}
for _, c := range cases {
got, err := parseDateAndTimeWithPattern(&c.metric, c.pdu, Metrics{})
if c.shouldErr && err == nil {
t.Fatalf("Was expecting error, but none returned.")
}
if !c.shouldErr && err != nil {
t.Fatalf("Was expecting no error, but one returned.")
}
if !reflect.DeepEqual(got, c.result) {
t.Errorf("parseDateAndTime(%v) result: got %v, want %v", c.pdu, got, c.result)
}
}
}

func TestIndexesToLabels(t *testing.T) {
cases := []struct {
oid []int
Expand Down
21 changes: 11 additions & 10 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,16 +216,17 @@ type DynamicFilter struct {
}

type Metric struct {
Name string `yaml:"name"`
Oid string `yaml:"oid"`
Type string `yaml:"type"`
Help string `yaml:"help"`
Indexes []*Index `yaml:"indexes,omitempty"`
Lookups []*Lookup `yaml:"lookups,omitempty"`
RegexpExtracts map[string][]RegexpExtract `yaml:"regex_extracts,omitempty"`
EnumValues map[int]string `yaml:"enum_values,omitempty"`
Offset float64 `yaml:"offset,omitempty"`
Scale float64 `yaml:"scale,omitempty"`
Name string `yaml:"name"`
Oid string `yaml:"oid"`
Type string `yaml:"type"`
Help string `yaml:"help"`
Indexes []*Index `yaml:"indexes,omitempty"`
Lookups []*Lookup `yaml:"lookups,omitempty"`
RegexpExtracts map[string][]RegexpExtract `yaml:"regex_extracts,omitempty"`
DateTimePattern string `yaml:"datetime_pattern,omitempty"`
EnumValues map[int]string `yaml:"enum_values,omitempty"`
Offset float64 `yaml:"offset,omitempty"`
Scale float64 `yaml:"scale,omitempty"`
}

type Index struct {
Expand Down
2 changes: 2 additions & 0 deletions generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,15 @@ modules:
value: '1' # The first entry whose regex matches and whose value parses wins.
- regex: '.*'
value: '0'
datetime_pattern: # Used if type = ParseDateAndTime. Uses the go time.Parse() format https://pkg.go.dev/time#pkg-constants
offset: 1.0 # Add the value to the same. Applied after scale.
scale: 1.0 # Scale the value of the sample by this value.
type: DisplayString # Override the metric type, possible types are:
# gauge: An integer with type gauge.
# counter: An integer with type counter.
# OctetString: A bit string, rendered as 0xff34.
# DateAndTime: An RFC 2579 DateAndTime byte sequence. If the device has no time zone data, UTC is used.
# ParseDateAndTime: Parse a DisplayString and return the timestamp. See datetime_pattern config option
# DisplayString: An ASCII or UTF-8 string.
# PhysAddress48: A 48 bit MAC address, rendered as 00:01:02:03:04:ff.
# Float: A 32 bit floating-point value with type gauge.
Expand Down
16 changes: 9 additions & 7 deletions generator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ package main

import (
"fmt"
"github.com/prometheus/snmp_exporter/config"
"strconv"

"github.com/prometheus/snmp_exporter/config"
)

// The generator config.
Expand All @@ -27,12 +28,13 @@ type Config struct {
}

type MetricOverrides struct {
Ignore bool `yaml:"ignore,omitempty"`
RegexpExtracts map[string][]config.RegexpExtract `yaml:"regex_extracts,omitempty"`
Offset float64 `yaml:"offset,omitempty"`
Scale float64 `yaml:"scale,omitempty"`
Type string `yaml:"type,omitempty"`
Help string `yaml:"help,omitempty"`
Ignore bool `yaml:"ignore,omitempty"`
RegexpExtracts map[string][]config.RegexpExtract `yaml:"regex_extracts,omitempty"`
DateTimePattern string `yaml:"datetime_pattern,omitempty"`
Offset float64 `yaml:"offset,omitempty"`
Scale float64 `yaml:"scale,omitempty"`
Type string `yaml:"type,omitempty"`
Help string `yaml:"help,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
Expand Down
6 changes: 6 additions & 0 deletions generator/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ func prepareTree(nodes *Node, logger log.Logger) map[string]*Node {
if n.TextualConvention == "DateAndTime" {
n.Type = "DateAndTime"
}
if n.TextualConvention == "ParseDateAndTime" {
n.Type = "ParseDateAndTime"
}
// Convert RFC 4001 InetAddress types textual convention to type.
if n.TextualConvention == "InetAddressIPv4" || n.TextualConvention == "InetAddressIPv6" || n.TextualConvention == "InetAddress" {
n.Type = n.TextualConvention
Expand Down Expand Up @@ -167,6 +170,8 @@ func metricType(t string) (string, bool) {
return t, true
case "DateAndTime":
return t, true
case "ParseDateAndTime":
return t, true
case "EnumAsInfo", "EnumAsStateSet":
return t, true
default:
Expand Down Expand Up @@ -528,6 +533,7 @@ func generateConfigModule(cfg *ModuleConfig, node *Node, nameToNode map[string]*
for _, metric := range out.Metrics {
if name == metric.Name || name == metric.Oid {
metric.RegexpExtracts = params.RegexpExtracts
metric.DateTimePattern = params.DateTimePattern
metric.Offset = params.Offset
metric.Scale = params.Scale
if params.Help != "" {
Expand Down
12 changes: 12 additions & 0 deletions generator/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ func TestTreePrepare(t *testing.T) {
in: &Node{Oid: "1", Type: "DisplayString", TextualConvention: "DateAndTime"},
out: &Node{Oid: "1", Type: "DateAndTime", TextualConvention: "DateAndTime"},
},
// ParseDateAndTime
{
in: &Node{Oid: "1", Type: "DisplayString", TextualConvention: "ParseDateAndTime"},
out: &Node{Oid: "1", Type: "ParseDateAndTime", TextualConvention: "ParseDateAndTime"},
},
// RFC 4100 InetAddress conventions.
{
in: &Node{Oid: "1", Type: "OctectString", TextualConvention: "InetAddressIPv4"},
Expand Down Expand Up @@ -340,6 +345,7 @@ func TestGenerateConfigModule(t *testing.T) {
{Oid: "1.202", Access: "ACCESS_READONLY", Label: "DateAndTime", Type: "DisplayString", TextualConvention: "DateAndTime"},
{Oid: "1.203", Access: "ACCESS_READONLY", Label: "InetAddressIPv4", Type: "OCTETSTR", TextualConvention: "InetAddressIPv4"},
{Oid: "1.204", Access: "ACCESS_READONLY", Label: "InetAddressIPv6", Type: "OCTETSTR", TextualConvention: "InetAddressIPv6"},
{Oid: "1.205", Access: "ACCESS_READONLY", Label: "ParseDateAndTime", Type: "DisplayString", TextualConvention: "ParseDateAndTime"},
}},
cfg: &ModuleConfig{
Walk: []string{"root", "1.3"},
Expand Down Expand Up @@ -461,6 +467,12 @@ func TestGenerateConfigModule(t *testing.T) {
Type: "InetAddressIPv6",
Help: " - 1.204",
},
{
Name: "ParseDateAndTime",
Oid: "1.205",
Type: "ParseDateAndTime",
Help: " - 1.205",
},
},
},
},
Expand Down

0 comments on commit 0740933

Please sign in to comment.