Skip to content

Commit 51377a6

Browse files
Merge branch 'prometheus:main' into main
2 parents 6f5935a + a1e24f1 commit 51377a6

File tree

22 files changed

+856
-393
lines changed

22 files changed

+856
-393
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ executors:
99
# should also be updated.
1010
golang:
1111
docker:
12-
- image: cimg/go:1.24
12+
- image: cimg/go:1.25
1313
parameters:
1414
working_dir:
1515
type: string

.github/workflows/golangci-lint.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ jobs:
3030
- name: Install Go
3131
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
3232
with:
33-
go-version: 1.24.x
33+
go-version: 1.25.x
3434
- name: Install snmp_exporter/generator dependencies
3535
run: sudo apt-get update && sudo apt-get -y install libsnmp-dev
3636
if: github.repository == 'prometheus/snmp_exporter'
37+
- name: Get golangci-lint version
38+
id: golangci-lint-version
39+
run: echo "version=$(make print-golangci-lint-version)" >> $GITHUB_OUTPUT
3740
- name: Lint
3841
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
3942
with:
4043
args: --verbose
41-
version: v2.2.1
44+
version: ${{ steps.golangci-lint-version.outputs.version }}

.golangci.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
version: "2"
22
linters:
33
enable:
4+
- errorlint
5+
- gocritic
46
- misspell
57
- revive
68
- sloglint
@@ -10,6 +12,11 @@ linters:
1012
errcheck:
1113
exclude-functions:
1214
- (net/http.ResponseWriter).Write
15+
gocritic:
16+
enable-all: true
17+
disabled-checks:
18+
- hugeParam
19+
- unnamedResult
1320
revive:
1421
rules:
1522
- name: unused-parameter
@@ -26,3 +33,19 @@ linters:
2633
- errcheck
2734
- govet
2835
path: _test.go
36+
formatters:
37+
enable:
38+
- gci
39+
- gofumpt
40+
- goimports
41+
settings:
42+
gci:
43+
sections:
44+
- standard
45+
- default
46+
- prefix(github.com/prometheus/snmp_exporter)
47+
gofumpt:
48+
extra-rules: true
49+
goimports:
50+
local-prefixes:
51+
- github.com/prometheus/snmp_exporter

.promu.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
go:
22
# Whenever the Go version is updated here,
33
# .circle/config.yml should also be updated.
4-
version: 1.24
4+
version: 1.25
55
repository:
66
path: github.com/prometheus/snmp_exporter
77
build:

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ ARG ARCH="amd64"
77
ARG OS="linux"
88
COPY .build/${OS}-${ARCH}/snmp_exporter /bin/snmp_exporter
99
COPY snmp.yml /etc/snmp_exporter/snmp.yml
10+
COPY LICENSE /LICENSE
11+
COPY NOTICE /NOTICE
12+
1013

1114
EXPOSE 9116
1215
ENTRYPOINT [ "/bin/snmp_exporter" ]

Makefile.common

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_
6161
SKIP_GOLANGCI_LINT :=
6262
GOLANGCI_LINT :=
6363
GOLANGCI_LINT_OPTS ?=
64-
GOLANGCI_LINT_VERSION ?= v2.2.1
64+
GOLANGCI_LINT_VERSION ?= v2.4.0
6565
GOLANGCI_FMT_OPTS ?=
6666
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64.
6767
# windows isn't included here because of the path separator being different.
@@ -266,6 +266,10 @@ $(GOLANGCI_LINT):
266266
| sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
267267
endif
268268

269+
.PHONY: common-print-golangci-lint-version
270+
common-print-golangci-lint-version:
271+
@echo $(GOLANGCI_LINT_VERSION)
272+
269273
.PHONY: precheck
270274
precheck::
271275

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ It is possible to supply an optional `snmp_context` parameter in the URL, like t
9696
<http://localhost:9116/snmp?auth=my_secure_v3&module=ddwrt&target=192.0.0.8&snmp_context=vrf-mgmt>
9797
The `snmp_context` parameter in the URL would override the `context_name` parameter in the `snmp.yml` file.
9898

99+
It is also possible when using SNMPv3 to supply an optional `snmp_engineid` parameter in the URL, like this:
100+
<http://localhost:9116/snmp?auth=my_secure_v3&module=ddwrt&target=192.0.0.8&snmp_engineid=800004f7059c7a0307400529>
101+
102+
99103
## Multi-Module Handling
100104
The multi-module functionality allows you to specify multiple modules, enabling the retrieval of information from several modules in a single scrape.
101105
The concurrency can be specified using the snmp-exporter option `--snmp.module-concurrency` (the default is 1).

collector/collector.go

Lines changed: 62 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package collector
1616
import (
1717
"context"
1818
"encoding/binary"
19+
"encoding/hex"
1920
"fmt"
2021
"log/slog"
2122
"net"
@@ -62,7 +63,7 @@ var combinedTypeMapping = map[string]map[int]string{
6263

6364
func oidToList(oid string) []int {
6465
result := []int{}
65-
for _, x := range strings.Split(oid, ".") {
66+
for x := range strings.SplitSeq(oid, ".") {
6667
o, _ := strconv.Atoi(x)
6768
result = append(result, o)
6869
}
@@ -117,10 +118,7 @@ func ScrapeTarget(snmp scraper.SNMPScraper, target string, auth *config.Auth, mo
117118
maxOids = 1
118119
}
119120
for len(getOids) > 0 {
120-
oids := len(getOids)
121-
if oids > maxOids {
122-
oids = maxOids
123-
}
121+
oids := min(len(getOids), maxOids)
124122

125123
packet, err := snmp.Get(getOids[:oids])
126124
if err != nil {
@@ -291,30 +289,32 @@ func NewNamedModule(name string, module *config.Module) *NamedModule {
291289
}
292290

293291
type Collector struct {
294-
ctx context.Context
295-
target string
296-
auth *config.Auth
297-
authName string
298-
modules []*NamedModule
299-
logger *slog.Logger
300-
metrics Metrics
301-
concurrency int
302-
snmpContext string
303-
debugSNMP bool
304-
}
305-
306-
func New(ctx context.Context, target, authName, snmpContext string, auth *config.Auth, modules []*NamedModule, logger *slog.Logger, metrics Metrics, conc int, debugSNMP bool) *Collector {
292+
ctx context.Context
293+
target string
294+
auth *config.Auth
295+
authName string
296+
modules []*NamedModule
297+
logger *slog.Logger
298+
metrics Metrics
299+
concurrency int
300+
snmpContext string
301+
snmpEngineID string
302+
debugSNMP bool
303+
}
304+
305+
func New(ctx context.Context, target, authName, snmpContext, snmpEngineID string, auth *config.Auth, modules []*NamedModule, logger *slog.Logger, metrics Metrics, conc int, debugSNMP bool) *Collector {
307306
return &Collector{
308-
ctx: ctx,
309-
target: target,
310-
authName: authName,
311-
auth: auth,
312-
modules: modules,
313-
snmpContext: snmpContext,
314-
logger: logger.With("source_address", *srcAddress),
315-
metrics: metrics,
316-
concurrency: conc,
317-
debugSNMP: debugSNMP,
307+
ctx: ctx,
308+
target: target,
309+
authName: authName,
310+
auth: auth,
311+
modules: modules,
312+
snmpContext: snmpContext,
313+
snmpEngineID: snmpEngineID,
314+
logger: logger.With("source_address", *srcAddress),
315+
metrics: metrics,
316+
concurrency: conc,
317+
debugSNMP: debugSNMP,
318318
}
319319
}
320320

@@ -352,7 +352,7 @@ func (c Collector) collect(ch chan<- prometheus.Metric, logger *slog.Logger, cli
352352
g.MaxRepetitions = module.WalkParams.MaxRepetitions
353353
g.UseUnconnectedUDPSocket = module.WalkParams.UseUnconnectedUDPSocket
354354
if module.WalkParams.AllowNonIncreasingOIDs {
355-
g.AppOpts = map[string]interface{}{
355+
g.AppOpts = map[string]any{
356356
"c": true,
357357
}
358358
}
@@ -420,10 +420,7 @@ func (c Collector) collect(ch chan<- prometheus.Metric, logger *slog.Logger, cli
420420
// Collect implements Prometheus.Collector.
421421
func (c Collector) Collect(ch chan<- prometheus.Metric) {
422422
wg := sync.WaitGroup{}
423-
workerCount := c.concurrency
424-
if workerCount < 1 {
425-
workerCount = 1
426-
}
423+
workerCount := max(c.concurrency, 1)
427424
ctx, cancel := context.WithCancel(c.ctx)
428425
defer cancel()
429426
workerChan := make(chan *NamedModule)
@@ -447,6 +444,15 @@ func (c Collector) Collect(ch chan<- prometheus.Metric) {
447444
break
448445
}
449446
}
447+
// Set EngineID option if one is configured and we're using SNMPv3
448+
if c.snmpEngineID != "" && c.auth.Version == 3 {
449+
// Convert the SNMP Engine ID to a byte string
450+
sEID, _ := hex.DecodeString(c.snmpEngineID)
451+
// Set the options.
452+
client.SetOptions(func(g *gosnmp.GoSNMP) {
453+
g.ContextEngineID = string(sEID)
454+
})
455+
}
450456
// Set the options.
451457
client.SetOptions(func(g *gosnmp.GoSNMP) {
452458
g.Context = ctx
@@ -531,14 +537,14 @@ func parseDateAndTime(pdu *gosnmp.SnmpPDU) (float64, error) {
531537
locString := fmt.Sprintf("%s%02d%02d", string(v[8]), v[9], v[10])
532538
loc, err := time.Parse("-0700", locString)
533539
if err != nil {
534-
return 0, fmt.Errorf("error parsing location string: %q, error: %s", locString, err)
540+
return 0, fmt.Errorf("error parsing location string: %q, error: %w", locString, err)
535541
}
536542
tz = loc.Location()
537543
default:
538544
return 0, fmt.Errorf("invalid DateAndTime length %v", pduLength)
539545
}
540546
if err != nil {
541-
return 0, fmt.Errorf("unable to parse DateAndTime %q, error: %s", v, err)
547+
return 0, fmt.Errorf("unable to parse DateAndTime %q, error: %w", v, err)
542548
}
543549
// Build the date from the various fields and time zone.
544550
t := time.Date(
@@ -557,13 +563,13 @@ func parseDateAndTimeWithPattern(metric *config.Metric, pdu *gosnmp.SnmpPDU, met
557563
pduValue := pduValueAsString(pdu, "DisplayString", metrics)
558564
t, err := timefmt.Parse(pduValue, metric.DateTimePattern)
559565
if err != nil {
560-
return 0, fmt.Errorf("error parsing date and time %q", err)
566+
return 0, fmt.Errorf("error parsing date and time %w", err)
561567
}
562568
return float64(t.Unix()), nil
563569
}
564570

565571
func parseNtpTimestamp(pdu *gosnmp.SnmpPDU) (float64, error) {
566-
var data = pdu.Value.([]byte)
572+
data := pdu.Value.([]byte)
567573

568574
// Prometheus uses the Unix time epoch (seconds since 1970).
569575
// NTP seconds are counted since 1900 and must be corrected
@@ -668,7 +674,7 @@ func pduToSamples(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, o
668674
t, value, labelvalues...)
669675
if err != nil {
670676
sample = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric", nil, nil),
671-
fmt.Errorf("error for metric %s with labels %v from indexOids %v: %v", metric.Name, labelvalues, indexOids, err))
677+
fmt.Errorf("error for metric %s with labels %v from indexOids %v: %w", metric.Name, labelvalues, indexOids, err))
672678
}
673679

674680
return []prometheus.Metric{sample}
@@ -693,7 +699,7 @@ func applyRegexExtracts(metric *config.Metric, pduValue string, labelnames, labe
693699
prometheus.GaugeValue, v, labelvalues...)
694700
if err != nil {
695701
newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for regex_extract", nil, nil),
696-
fmt.Errorf("error for metric %s with labels %v: %v", metric.Name+name, labelvalues, err))
702+
fmt.Errorf("error for metric %s with labels %v: %w", metric.Name+name, labelvalues, err))
697703
}
698704
results = append(results, newMetric)
699705
break
@@ -715,7 +721,7 @@ func enumAsInfo(metric *config.Metric, value int, labelnames, labelvalues []stri
715721
prometheus.GaugeValue, 1.0, labelvalues...)
716722
if err != nil {
717723
newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for EnumAsInfo", nil, nil),
718-
fmt.Errorf("error for metric %s with labels %v: %v", metric.Name, labelvalues, err))
724+
fmt.Errorf("error for metric %s with labels %v: %w", metric.Name, labelvalues, err))
719725
}
720726
return []prometheus.Metric{newMetric}
721727
}
@@ -733,7 +739,7 @@ func enumAsStateSet(metric *config.Metric, value int, labelnames, labelvalues []
733739
prometheus.GaugeValue, 1.0, append(labelvalues, state)...)
734740
if err != nil {
735741
newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for EnumAsStateSet", nil, nil),
736-
fmt.Errorf("error for metric %s with labels %v: %v", metric.Name, labelvalues, err))
742+
fmt.Errorf("error for metric %s with labels %v: %w", metric.Name, labelvalues, err))
737743
}
738744
results = append(results, newMetric)
739745

@@ -745,14 +751,14 @@ func enumAsStateSet(metric *config.Metric, value int, labelnames, labelvalues []
745751
prometheus.GaugeValue, 0.0, append(labelvalues, v)...)
746752
if err != nil {
747753
newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for EnumAsStateSet", nil, nil),
748-
fmt.Errorf("error for metric %s with labels %v: %v", metric.Name, labelvalues, err))
754+
fmt.Errorf("error for metric %s with labels %v: %w", metric.Name, labelvalues, err))
749755
}
750756
results = append(results, newMetric)
751757
}
752758
return results
753759
}
754760

755-
func bits(metric *config.Metric, value interface{}, labelnames, labelvalues []string) []prometheus.Metric {
761+
func bits(metric *config.Metric, value any, labelnames, labelvalues []string) []prometheus.Metric {
756762
bytes, ok := value.([]byte)
757763
if !ok {
758764
return []prometheus.Metric{prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "BITS type was not a BISTRING on the wire.", nil, nil),
@@ -773,7 +779,7 @@ func bits(metric *config.Metric, value interface{}, labelnames, labelvalues []st
773779
prometheus.GaugeValue, bit, append(labelvalues, v)...)
774780
if err != nil {
775781
newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for Bits", nil, nil),
776-
fmt.Errorf("error for metric %s with labels %v: %v", metric.Name, labelvalues, err))
782+
fmt.Errorf("error for metric %s with labels %v: %w", metric.Name, labelvalues, err))
777783
}
778784
results = append(results, newMetric)
779785
}
@@ -797,36 +803,36 @@ func splitOid(oid []int, count int) ([]int, []int) {
797803

798804
// This mirrors decodeValue in gosnmp's helper.go.
799805
func pduValueAsString(pdu *gosnmp.SnmpPDU, typ string, metrics Metrics) string {
800-
switch pdu.Value.(type) {
806+
switch v := pdu.Value.(type) {
801807
case int:
802-
return strconv.Itoa(pdu.Value.(int))
808+
return strconv.Itoa(v)
803809
case uint:
804-
return strconv.FormatUint(uint64(pdu.Value.(uint)), 10)
810+
return strconv.FormatUint(uint64(v), 10)
805811
case uint64:
806-
return strconv.FormatUint(pdu.Value.(uint64), 10)
812+
return strconv.FormatUint(v, 10)
807813
case float32:
808-
return strconv.FormatFloat(float64(pdu.Value.(float32)), 'f', -1, 32)
814+
return strconv.FormatFloat(float64(v), 'f', -1, 32)
809815
case float64:
810-
return strconv.FormatFloat(pdu.Value.(float64), 'f', -1, 64)
816+
return strconv.FormatFloat(v, 'f', -1, 64)
811817
case string:
812818
if pdu.Type == gosnmp.ObjectIdentifier {
813819
// Trim leading period.
814-
return pdu.Value.(string)[1:]
820+
return v[1:]
815821
}
816822
// DisplayString.
817-
return strings.ToValidUTF8(pdu.Value.(string), "�")
823+
return strings.ToValidUTF8(v, "�")
818824
case []byte:
819825
if typ == "" || typ == "Bits" {
820826
typ = "OctetString"
821827
}
822828
// Reuse the OID index parsing code.
823-
parts := make([]int, len(pdu.Value.([]byte)))
824-
for i, o := range pdu.Value.([]byte) {
829+
parts := make([]int, len(v))
830+
for i, o := range v {
825831
parts[i] = int(o)
826832
}
827833
if typ == "OctetString" || typ == "DisplayString" {
828834
// Prepend the length, as it is explicit in an index.
829-
parts = append([]int{len(pdu.Value.([]byte))}, parts...)
835+
parts = append([]int{len(v)}, parts...)
830836
}
831837
str, _, _ := indexOidsAsString(parts, typ, 0, false, nil)
832838
return strings.ToValidUTF8(str, "�")
@@ -924,7 +930,7 @@ func indexOidsAsString(indexOids []int, typ string, fixedSize int, implied bool,
924930
return strings.Join(parts, "."), subOid, indexOids
925931
case "InetAddressIPv6":
926932
subOid, indexOids := splitOid(indexOids, 16)
927-
parts := make([]interface{}, 16)
933+
parts := make([]any, 16)
928934
for i, o := range subOid {
929935
parts[i] = o
930936
}

0 commit comments

Comments
 (0)