Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract numeric time series from string values #212

Merged
merged 1 commit into from
Aug 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 49 additions & 17 deletions collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ PduLoop:
}
if head.metric != nil {
// Found a match.
ch <- pduToSample(oidList[i+1:], &pdu, head.metric, oidToPdu)
samples := pduToSamples(oidList[i+1:], &pdu, head.metric, oidToPdu)
for _, sample := range samples {
ch <- sample
}
break
}
}
Expand All @@ -174,13 +177,19 @@ func getPduValue(pdu *gosnmp.SnmpPDU) float64 {
}
}

func pduToSample(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, oidToPdu map[string]gosnmp.SnmpPDU) prometheus.Metric {
func pduToSamples(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, oidToPdu map[string]gosnmp.SnmpPDU) []prometheus.Metric {
// The part of the OID that is the indexes.
labels := indexesToLabels(indexOids, metric, oidToPdu)

value := getPduValue(pdu)
t := prometheus.UntypedValue
stringType := false

labelnames := make([]string, 0, len(labels)+1)
labelvalues := make([]string, 0, len(labels)+1)
for k, v := range labels {
labelnames = append(labelnames, k)
labelvalues = append(labelvalues, v)
}

switch metric.Type {
case "counter":
Expand All @@ -191,25 +200,48 @@ func pduToSample(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, oi
// It's some form of string.
t = prometheus.GaugeValue
value = 1.0
stringType = true
}

labelnames := make([]string, 0, len(labels)+1)
labelvalues := make([]string, 0, len(labels)+1)
for k, v := range labels {
labelnames = append(labelnames, k)
labelvalues = append(labelvalues, v)
}
// For strings we put the value as a label with the same name as the metric.
// If the name is already an index, we do not need to set it again.
if stringType {
if len(metric.RegexpExtracts) > 0 {
v, ok := pdu.Value.(string)
if !ok {
log.Errorf("Invalid PDU value type: got %T, want string for metric: %v", pdu.Value, metric.Name)
return nil
}
return applyRegexExtracts(metric, v, labelnames, labelvalues)
}
// For strings we put the value as a label with the same name as the metric.
// If the name is already an index, we do not need to set it again.
if _, ok := labels[metric.Name]; !ok {
labelnames = append(labelnames, metric.Name)
labelvalues = append(labelvalues, pduValueAsString(pdu, metric.Type))
}
}
return prometheus.MustNewConstMetric(prometheus.NewDesc(metric.Name, metric.Help, labelnames, nil),
t, value, labelvalues...)

return []prometheus.Metric{prometheus.MustNewConstMetric(prometheus.NewDesc(metric.Name, metric.Help, labelnames, nil),
t, value, labelvalues...)}
}

func applyRegexExtracts(metric *config.Metric, pduValue string, labelnames, labelvalues []string) []prometheus.Metric {
results := []prometheus.Metric{}
for name, strMetricSlice := range metric.RegexpExtracts {
for _, strMetric := range strMetricSlice {
indexes := strMetric.Regex.FindStringSubmatchIndex(pduValue)
if indexes == nil {
log.Debugf("No match found for regexp: %v against value: %v for metric %v", strMetric.Regex.String(), pduValue, metric.Name)
continue
}
res := strMetric.Regex.ExpandString([]byte{}, strMetric.Value, pduValue, indexes)
v, err := strconv.ParseFloat(string(res), 64)
if err != nil {
log.Debugf("Error parsing float64 from value: %v for metric: %v", res, metric.Name)
continue
}
newMetric := prometheus.MustNewConstMetric(prometheus.NewDesc(metric.Name+name, metric.Help+" (regex extracted)", labelnames, nil),
prometheus.GaugeValue, v, labelvalues...)
results = append(results, newMetric)
break
}
}
return results
}

// Right pad oid with zeros, and split at the given point.
Expand Down
216 changes: 189 additions & 27 deletions collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,183 @@ package main

import (
"reflect"
"regexp"
"testing"

"github.com/prometheus/client_model/go"
"github.com/soniah/gosnmp"

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

func TestPduToSample(t *testing.T) {

cases := []struct {
pdu *gosnmp.SnmpPDU
indexOids []int
metric *config.Metric
oidToPdu map[string]gosnmp.SnmpPDU
expectedMetric string
expectedMetricDesc string
pdu *gosnmp.SnmpPDU
indexOids []int
metric *config.Metric
oidToPdu map[string]gosnmp.SnmpPDU
expectedMetrics map[string]string
}{
{
pdu: &gosnmp.SnmpPDU{
Name: "1.1.1.1.1",
Value: "SomeStringValue",
},
indexOids: []int{},
metric: &config.Metric{
Name: "TestMetricName",
Oid: "1.1.1.1.1",
Help: "HelpText",
RegexpExtracts: map[string][]config.RegexpExtract{
"Extension": []config.RegexpExtract{
{
Regex: config.Regexp{
regexp.MustCompile(".*"),
},
Value: "5",
},
},
},
},
oidToPdu: make(map[string]gosnmp.SnmpPDU),
expectedMetrics: map[string]string{
`gauge:<value:5 > `: `Desc{fqName: "TestMetricNameExtension", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []}`,
},
},
{
pdu: &gosnmp.SnmpPDU{
Name: "1.1.1.1.1",
Value: "SomeStringValue",
},
indexOids: []int{},
metric: &config.Metric{
Name: "TestMetricName",
Oid: "1.1.1.1.1",
Help: "HelpText",
RegexpExtracts: map[string][]config.RegexpExtract{
"Extension": []config.RegexpExtract{
{
Regex: config.Regexp{
regexp.MustCompile(".*"),
},
Value: "",
},
},
},
},
expectedMetrics: map[string]string{},
},
{
pdu: &gosnmp.SnmpPDU{
Name: "1.1.1.1.1",
Value: "SomeStringValue",
},
indexOids: []int{},
metric: &config.Metric{
Name: "TestMetricName",
Oid: "1.1.1.1.1",
Help: "HelpText",
RegexpExtracts: map[string][]config.RegexpExtract{
"Extension": []config.RegexpExtract{
{
Regex: config.Regexp{
regexp.MustCompile("(will_not_match)"),
},
Value: "",
},
},
},
},
expectedMetrics: map[string]string{},
},
{
pdu: &gosnmp.SnmpPDU{
Name: "1.1.1.1.1",
Value: 2,
},
indexOids: []int{},
metric: &config.Metric{
Name: "TestMetricName",
Oid: "1.1.1.1.1",
Help: "HelpText",
RegexpExtracts: map[string][]config.RegexpExtract{
"Status": []config.RegexpExtract{
{
Regex: config.Regexp{
regexp.MustCompile(".*"),
},
Value: "5",
},
},
},
},
expectedMetrics: map[string]string{},
},
{
pdu: &gosnmp.SnmpPDU{
Name: "1.1.1.1.1",
Value: "Test value 4.42 123 999",
},
indexOids: []int{},
metric: &config.Metric{
Name: "TestMetricName",
Oid: "1.1.1.1.1",
Help: "HelpText",
RegexpExtracts: map[string][]config.RegexpExtract{
"Blank": []config.RegexpExtract{
{
Regex: config.Regexp{
regexp.MustCompile("XXXX"),
},
Value: "4",
},
},
"Extension": []config.RegexpExtract{
{
Regex: config.Regexp{
regexp.MustCompile(".*"),
},
Value: "5",
},
},
"MultipleRegexes": []config.RegexpExtract{
{
Regex: config.Regexp{
regexp.MustCompile("XXXX"),
},
Value: "123",
},
{
Regex: config.Regexp{
regexp.MustCompile("123"),
},
Value: "999",
},
{
Regex: config.Regexp{
regexp.MustCompile(".*"),
},
Value: "777",
},
},
"Template": []config.RegexpExtract{
{
Regex: config.Regexp{
regexp.MustCompile("([0-9].[0-9]+)"),
},
Value: "$1",
},
},
},
},
oidToPdu: make(map[string]gosnmp.SnmpPDU),
expectedMetrics: map[string]string{
`gauge:<value:5 > `: `Desc{fqName: "TestMetricNameExtension", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []}`,
`gauge:<value:999 > `: `Desc{fqName: "TestMetricNameMultipleRegexes", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []}`,
`gauge:<value:4.42 > `: `Desc{fqName: "TestMetricNameTemplate", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []}`,
},
},
{
pdu: &gosnmp.SnmpPDU{
Name: "1.1.1.1.1",
Expand All @@ -32,9 +192,8 @@ func TestPduToSample(t *testing.T) {
Type: "counter",
Help: "Help string",
},
oidToPdu: make(map[string]gosnmp.SnmpPDU),
expectedMetric: "counter:<value:2 > ",
expectedMetricDesc: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []}`,
oidToPdu: make(map[string]gosnmp.SnmpPDU),
expectedMetrics: map[string]string{"counter:<value:2 > ": `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []}`},
},
{
pdu: &gosnmp.SnmpPDU{
Expand All @@ -49,9 +208,8 @@ func TestPduToSample(t *testing.T) {
Type: "gauge",
Help: "Help string",
},
oidToPdu: make(map[string]gosnmp.SnmpPDU),
expectedMetric: "gauge:<value:2 > ",
expectedMetricDesc: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []}`,
oidToPdu: make(map[string]gosnmp.SnmpPDU),
expectedMetrics: map[string]string{"gauge:<value:2 > ": `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []}`},
},
{
pdu: &gosnmp.SnmpPDU{
Expand All @@ -65,24 +223,28 @@ func TestPduToSample(t *testing.T) {
Oid: "1.1.1.1.1",
Help: "Help string",
},
oidToPdu: make(map[string]gosnmp.SnmpPDU),
expectedMetric: `label:<name:"test_metric" value:"-2" > gauge:<value:1 > `,
expectedMetricDesc: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]}`,
oidToPdu: make(map[string]gosnmp.SnmpPDU),
expectedMetrics: map[string]string{`label:<name:"test_metric" value:"-2" > gauge:<value:1 > `: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]}`},
},
}

for _, c := range cases {
m := pduToSample(c.indexOids, c.pdu, c.metric, c.oidToPdu)
metric := &io_prometheus_client.Metric{}
err := m.Write(metric)
if err != nil {
t.Fatalf("Error writing metric: %v", err)
}
if metric.String() != c.expectedMetric {
t.Fatalf("Unexpected metric: got %v, want %v", metric.String(), c.expectedMetric)
for i, c := range cases {
metrics := pduToSamples(c.indexOids, c.pdu, c.metric, c.oidToPdu)
if len(metrics) != len(c.expectedMetrics) {
t.Fatalf("Unexpected number of metrics returned for case %v: want %v, got %v", i, len(c.expectedMetrics), len(metrics))
}
if m.Desc().String() != c.expectedMetricDesc {
t.Fatalf("Unexpected metric description: got %v, want %v", m.Desc().String(), c.expectedMetricDesc)
metric := &io_prometheus_client.Metric{}
for _, m := range metrics {
err := m.Write(metric)
if err != nil {
t.Fatalf("Error writing metric: %v", err)
}
if _, ok := c.expectedMetrics[metric.String()]; !ok {
t.Fatalf("Unexpected metric: got %v", metric.String())
}
if c.expectedMetrics[metric.String()] != m.Desc().String() {
t.Fatalf("Unexpected metric: got %v , want %v", m.Desc().String(), c.expectedMetrics[metric.String()])
}
}
}
}
Expand Down
Loading