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

Allow custom registry for initialization of collector metrics #885

Merged
merged 4 commits into from
Jun 23, 2023
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
126 changes: 72 additions & 54 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,36 +38,6 @@ const (
)

var (
buckets = prometheus.ExponentialBuckets(0.0001, 2, 15)
snmpUnexpectedPduType = promauto.NewCounter(
prometheus.CounterOpts{
Namespace: namespace,
Name: "unexpected_pdu_type_total",
Help: "Unexpected Go types in a PDU.",
},
)
snmpDuration = promauto.NewHistogram(
prometheus.HistogramOpts{
Namespace: namespace,
Name: "packet_duration_seconds",
Help: "A histogram of latencies for SNMP packets.",
Buckets: buckets,
},
)
snmpPackets = promauto.NewCounter(
prometheus.CounterOpts{
Namespace: namespace,
Name: "packets_total",
Help: "Number of SNMP packet sent, including retries.",
},
)
snmpRetries = promauto.NewCounter(
prometheus.CounterOpts{
Namespace: namespace,
Name: "packet_retries_total",
Help: "Number of SNMP packet retries.",
},
)
// 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()
Expand Down Expand Up @@ -116,7 +86,7 @@ type ScrapeResults struct {
retries uint64
}

func ScrapeTarget(ctx context.Context, target string, auth *config.Auth, module *config.Module, logger log.Logger) (ScrapeResults, error) {
func ScrapeTarget(ctx context.Context, target string, auth *config.Auth, module *config.Module, logger log.Logger, metrics internalMetrics) (ScrapeResults, error) {
results := ScrapeResults{}
// Set the options.
snmp := gosnmp.GoSNMP{}
Expand All @@ -136,14 +106,14 @@ func ScrapeTarget(ctx context.Context, target string, auth *config.Auth, module
var sent time.Time
snmp.OnSent = func(x *gosnmp.GoSNMP) {
sent = time.Now()
snmpPackets.Inc()
metrics.snmpPackets.Inc()
results.packets++
}
snmp.OnRecv = func(x *gosnmp.GoSNMP) {
snmpDuration.Observe(time.Since(sent).Seconds())
metrics.snmpDuration.Observe(time.Since(sent).Seconds())
}
snmp.OnRetry = func(x *gosnmp.GoSNMP) {
snmpRetries.Inc()
metrics.snmpRetries.Inc()
results.retries++
}

Expand Down Expand Up @@ -191,7 +161,7 @@ func ScrapeTarget(ctx context.Context, target string, auth *config.Auth, module
continue
}

allowedList = filterAllowedIndices(logger, filter, pdus, allowedList)
allowedList = filterAllowedIndices(logger, filter, pdus, allowedList, metrics)

// Update config to get only index and not walk them.
newWalk = updateWalkConfig(newWalk, filter, logger)
Expand Down Expand Up @@ -272,12 +242,12 @@ func ScrapeTarget(ctx context.Context, target string, auth *config.Auth, module
return results, nil
}

func filterAllowedIndices(logger log.Logger, filter config.DynamicFilter, pdus []gosnmp.SnmpPDU, allowedList []string) []string {
func filterAllowedIndices(logger log.Logger, filter config.DynamicFilter, pdus []gosnmp.SnmpPDU, allowedList []string, metrics internalMetrics) []string {
level.Debug(logger).Log("msg", "Evaluating rule for oid", "oid", filter.Oid)
for _, pdu := range pdus {
found := false
for _, val := range filter.Values {
snmpval := pduValueAsString(&pdu, "DisplayString")
snmpval := pduValueAsString(&pdu, "DisplayString", metrics)
level.Debug(logger).Log("config value", val, "snmp value", snmpval)

if regexp.MustCompile(val).MatchString(snmpval) {
Expand Down Expand Up @@ -366,16 +336,64 @@ func buildMetricTree(metrics []*config.Metric) *MetricNode {
return metricTree
}

type internalMetrics struct {
snmpUnexpectedPduType prometheus.Counter
snmpDuration prometheus.Histogram
snmpPackets prometheus.Counter
snmpRetries prometheus.Counter
}

type collector struct {
ctx context.Context
target string
auth *config.Auth
module *config.Module
logger log.Logger
ctx context.Context
target string
auth *config.Auth
module *config.Module
logger log.Logger
metrics internalMetrics
}

func newInternalMetrics(reg prometheus.Registerer) internalMetrics {
buckets := prometheus.ExponentialBuckets(0.0001, 2, 15)
snmpUnexpectedPduType := promauto.With(reg).NewCounter(
prometheus.CounterOpts{
Namespace: namespace,
Name: "unexpected_pdu_type_total",
Help: "Unexpected Go types in a PDU.",
},
)
snmpDuration := promauto.With(reg).NewHistogram(
prometheus.HistogramOpts{
Namespace: namespace,
Name: "packet_duration_seconds",
Help: "A histogram of latencies for SNMP packets.",
Buckets: buckets,
},
)
snmpPackets := promauto.With(reg).NewCounter(
prometheus.CounterOpts{
Namespace: namespace,
Name: "packets_total",
Help: "Number of SNMP packet sent, including retries.",
},
)
snmpRetries := promauto.With(reg).NewCounter(
prometheus.CounterOpts{
Namespace: namespace,
Name: "packet_retries_total",
Help: "Number of SNMP packet retries.",
},
)
return internalMetrics{
snmpUnexpectedPduType: snmpUnexpectedPduType,
snmpDuration: snmpDuration,
snmpPackets: snmpPackets,
snmpRetries: snmpRetries,
}
}

func New(ctx context.Context, target string, auth *config.Auth, module *config.Module, logger log.Logger) *collector {
return &collector{ctx: ctx, target: target, auth: auth, module: module, logger: logger}
func New(ctx context.Context, target string, auth *config.Auth, module *config.Module, logger log.Logger, reg prometheus.Registerer) *collector {
internalMetrics := newInternalMetrics(reg)
return &collector{ctx: ctx, target: target, auth: auth, module: module, logger: logger, metrics: internalMetrics}
}

// Describe implements Prometheus.Collector.
Expand All @@ -386,7 +404,7 @@ func (c collector) Describe(ch chan<- *prometheus.Desc) {
// Collect implements Prometheus.Collector.
func (c collector) Collect(ch chan<- prometheus.Metric) {
start := time.Now()
results, err := ScrapeTarget(c.ctx, c.target, c.auth, c.module, c.logger)
results, err := ScrapeTarget(c.ctx, c.target, c.auth, c.module, c.logger, c.metrics)
if err != nil {
level.Info(c.logger).Log("msg", "Error scraping target", "err", err)
ch <- prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error scraping target", nil, nil), err)
Expand Down Expand Up @@ -427,7 +445,7 @@ PduLoop:
}
if head.metric != nil {
// Found a match.
samples := pduToSamples(oidList[i+1:], &pdu, head.metric, oidToPdu, c.logger)
samples := pduToSamples(oidList[i+1:], &pdu, head.metric, oidToPdu, c.logger, c.metrics)
for _, sample := range samples {
ch <- sample
}
Expand Down Expand Up @@ -506,10 +524,10 @@ func parseDateAndTime(pdu *gosnmp.SnmpPDU) (float64, error) {
return float64(t.Unix()), nil
}

func pduToSamples(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, oidToPdu map[string]gosnmp.SnmpPDU, logger log.Logger) []prometheus.Metric {
func pduToSamples(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, oidToPdu map[string]gosnmp.SnmpPDU, logger log.Logger, metrics internalMetrics) []prometheus.Metric {
var err error
// The part of the OID that is the indexes.
labels := indexesToLabels(indexOids, metric, oidToPdu)
labels := indexesToLabels(indexOids, metric, oidToPdu, metrics)

value := getPduValue(pdu)
t := prometheus.UntypedValue
Expand Down Expand Up @@ -568,13 +586,13 @@ func pduToSamples(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, o
}

if len(metric.RegexpExtracts) > 0 {
return applyRegexExtracts(metric, pduValueAsString(pdu, metricType), labelnames, labelvalues, logger)
return applyRegexExtracts(metric, pduValueAsString(pdu, metricType, metrics), labelnames, labelvalues, logger)
}
// 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, metricType))
labelvalues = append(labelvalues, pduValueAsString(pdu, metricType, metrics))
}
}

Expand Down Expand Up @@ -710,7 +728,7 @@ func splitOid(oid []int, count int) ([]int, []int) {
}

// This mirrors decodeValue in gosnmp's helper.go.
func pduValueAsString(pdu *gosnmp.SnmpPDU, typ string) string {
func pduValueAsString(pdu *gosnmp.SnmpPDU, typ string, metrics internalMetrics) string {
switch pdu.Value.(type) {
case int:
return strconv.Itoa(pdu.Value.(int))
Expand Down Expand Up @@ -748,7 +766,7 @@ func pduValueAsString(pdu *gosnmp.SnmpPDU, typ string) string {
return ""
default:
// This shouldn't happen.
snmpUnexpectedPduType.Inc()
metrics.snmpUnexpectedPduType.Inc()
return fmt.Sprintf("%s", pdu.Value)
}
}
Expand Down Expand Up @@ -858,7 +876,7 @@ func indexOidsAsString(indexOids []int, typ string, fixedSize int, implied bool,
}
}

func indexesToLabels(indexOids []int, metric *config.Metric, oidToPdu map[string]gosnmp.SnmpPDU) map[string]string {
func indexesToLabels(indexOids []int, metric *config.Metric, oidToPdu map[string]gosnmp.SnmpPDU, metrics internalMetrics) map[string]string {
labels := map[string]string{}
labelOids := map[string][]int{}

Expand All @@ -884,7 +902,7 @@ func indexesToLabels(indexOids []int, metric *config.Metric, oidToPdu map[string
oid = fmt.Sprintf("%s.%s", oid, listToOid(labelOids[label]))
}
if pdu, ok := oidToPdu[oid]; ok {
labels[lookup.Labelname] = pduValueAsString(&pdu, lookup.Type)
labels[lookup.Labelname] = pduValueAsString(&pdu, lookup.Type, metrics)
labelOids[lookup.Labelname] = []int{int(gosnmp.ToBigInt(pdu.Value).Int64())}
} else {
labels[lookup.Labelname] = ""
Expand Down
10 changes: 5 additions & 5 deletions collector/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
kingpin "github.com/alecthomas/kingpin/v2"
"github.com/go-kit/log"
"github.com/gosnmp/gosnmp"
"github.com/prometheus/client_model/go"
io_prometheus_client "github.com/prometheus/client_model/go"

"github.com/prometheus/snmp_exporter/config"
)
Expand Down Expand Up @@ -518,7 +518,7 @@ func TestPduToSample(t *testing.T) {
}

for _, c := range cases {
metrics := pduToSamples(c.indexOids, c.pdu, c.metric, c.oidToPdu, log.NewNopLogger())
metrics := pduToSamples(c.indexOids, c.pdu, c.metric, c.oidToPdu, log.NewNopLogger(), internalMetrics{})
metric := &io_prometheus_client.Metric{}
expected := map[string]struct{}{}
for _, e := range c.expectedMetrics {
Expand Down Expand Up @@ -721,7 +721,7 @@ func TestPduValueAsString(t *testing.T) {
},
}
for _, c := range cases {
got := pduValueAsString(c.pdu, c.typ)
got := pduValueAsString(c.pdu, c.typ, internalMetrics{})
if !reflect.DeepEqual(got, c.result) {
t.Errorf("pduValueAsString(%v, %q): got %q, want %q", c.pdu, c.typ, got, c.result)
}
Expand Down Expand Up @@ -1008,7 +1008,7 @@ func TestIndexesToLabels(t *testing.T) {
},
}
for _, c := range cases {
got := indexesToLabels(c.oid, &c.metric, c.oidToPdu)
got := indexesToLabels(c.oid, &c.metric, c.oidToPdu, internalMetrics{})
if !reflect.DeepEqual(got, c.result) {
t.Errorf("indexesToLabels(%v, %v, %v): got %v, want %v", c.oid, c.metric, c.oidToPdu, got, c.result)
}
Expand Down Expand Up @@ -1059,7 +1059,7 @@ func TestFilterAllowedIndices(t *testing.T) {
},
}
for _, c := range cases {
got := filterAllowedIndices(log.NewNopLogger(), c.filter, pdus, c.allowedList)
got := filterAllowedIndices(log.NewNopLogger(), c.filter, pdus, c.allowedList, internalMetrics{})
if !reflect.DeepEqual(got, c.result) {
t.Errorf("filterAllowedIndices(%v): got %v, want %v", c.filter, got, c.result)
}
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func handler(w http.ResponseWriter, r *http.Request, logger log.Logger) {

start := time.Now()
registry := prometheus.NewRegistry()
c := collector.New(r.Context(), target, auth, module, logger)
c := collector.New(r.Context(), target, auth, module, logger, registry)
registry.MustRegister(c)
// Delegate http serving to Prometheus client library, which will call collector.Collect.
h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
Expand Down