chore(go.d/ddsnmp): correctly handle all mapping types (#20420)
Ilya Mashchenko committed
Jun 5, 2025 at 13:19 UTC
e47687ece3669058691105ec1b5b44967eb7b718
5 files changed
+400
-58
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collect_device_meta.go
+18
-13
@@ -21,11 +21,11 @@ func (c *Collector) collectDeviceMetadata(prof *ddsnmp.Profile) (map[string]stri
21
var tagOIDs []string
22
tags := make(map[string]string)
23
24
- for resName, metaCfg := range prof.Definition.Metadata {
24
+ for resName, cfg := range prof.Definition.Metadata {
25
if !ddprofiledefinition.IsMetadataResourceWithScalarOids(resName) {
26
continue
27
}
28
- for name, field := range metaCfg.Fields {
28
+ for name, field := range cfg.Fields {
29
switch {
30
case field.Value != "":
31
tags[name] = field.Value
@@ -55,11 +55,11 @@ func (c *Collector) collectDeviceMetadata(prof *ddsnmp.Profile) (map[string]stri
55
56
var errs []error
57
58
- for resName, metaCfg := range prof.Definition.Metadata {
58
+ for resName, cfg := range prof.Definition.Metadata {
59
if !ddprofiledefinition.IsMetadataResourceWithScalarOids(resName) {
60
continue
61
}
62
- for name, field := range metaCfg.Fields {
62
+ for name, field := range cfg.Fields {
63
switch {
64
case field.Symbol.OID != "":
65
v, err := processSymbolTagValue(field.Symbol, pdus)
@@ -88,26 +88,31 @@ func (c *Collector) collectDeviceMetadata(prof *ddsnmp.Profile) (map[string]stri
88
return tags, nil
89
}
90
91
-func processSymbolTagValue(symCfg ddprofiledefinition.SymbolConfig, result map[string]gosnmp.SnmpPDU) (string, error) {
92
- pdu, ok := result[trimOID(symCfg.OID)]
91
+func processSymbolTagValue(cfg ddprofiledefinition.SymbolConfig, result map[string]gosnmp.SnmpPDU) (string, error) {
92
+ pdu, ok := result[trimOID(cfg.OID)]
93
if !ok {
94
return "", nil
95
}
96
97
- val, err := convPduToStringf(pdu, symCfg.Format)
97
+ val, err := convPduToStringf(pdu, cfg.Format)
98
if err != nil {
99
return "", err
100
}
101
102
switch {
103
- case symCfg.ExtractValueCompiled != nil:
104
- if sm := symCfg.ExtractValueCompiled.FindStringSubmatch(val); len(sm) > 1 {
105
- return sm[1], nil
103
+ case cfg.ExtractValueCompiled != nil:
104
+ if sm := cfg.ExtractValueCompiled.FindStringSubmatch(val); len(sm) > 1 {
105
+ val = sm[1]
106
}
107
- case symCfg.MatchPatternCompiled != nil:
108
- if sm := symCfg.MatchPatternCompiled.FindStringSubmatch(val); len(sm) > 0 {
109
- return replaceSubmatches(symCfg.MatchValue, sm), nil
107
+ case cfg.MatchPatternCompiled != nil:
108
+ if sm := cfg.MatchPatternCompiled.FindStringSubmatch(val); len(sm) > 0 {
109
+ val = replaceSubmatches(cfg.MatchValue, sm)
110
}
111
}
112
+
113
+ if v, ok := cfg.Mapping[val]; ok {
114
+ val = v
115
+ }
116
+
117
return val, nil
118
}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collect_scalar.go
+36
-2
@@ -67,7 +67,7 @@ func (c *Collector) collectScalarMetric(cfg ddprofiledefinition.MetricsConfig, p
67
68
value, err := processSymbolValue(cfg.Symbol, pdu)
69
if err != nil {
70
- return nil, fmt.Errorf("error processing value: %w", err)
70
+ return nil, fmt.Errorf("error processing value for OID %s (%s): %w", cfg.Symbol.Name, cfg.Symbol.OID, err)
71
}
72
73
tags := make(map[string]string)
@@ -78,12 +78,31 @@ func (c *Collector) collectScalarMetric(cfg ddprofiledefinition.MetricsConfig, p
78
}
79
}
80
81
+ var mappings map[int64]string
82
+
83
+ if len(cfg.Symbol.Mapping) > 0 {
84
+ mappings = make(map[int64]string)
85
+ if isMappingKeysNumeric(cfg.Symbol.Mapping) {
86
+ for k, v := range cfg.Symbol.Mapping {
87
+ intKey, _ := strconv.ParseInt(k, 10, 64)
88
+ mappings[intKey] = v
89
+ }
90
+ } else {
91
+ for k, v := range cfg.Symbol.Mapping {
92
+ if intVal, err := strconv.ParseInt(v, 10, 64); err == nil {
93
+ mappings[intVal] = k
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
return &Metric{
100
Name: cfg.Symbol.Name,
101
Value: value,
102
Tags: tags,
103
Unit: cfg.Symbol.Unit,
104
Description: cfg.Symbol.Description,
105
+ Mappings: mappings,
106
MetricType: string(getMetricType(cfg.Symbol, pdu)),
107
}, nil
108
}
@@ -93,11 +112,18 @@ func processSymbolValue(sym ddprofiledefinition.SymbolConfig, pdu gosnmp.SnmpPDU
112
113
if isPduNumericType(pdu) {
114
value = gosnmp.ToBigInt(pdu.Value).Int64()
115
+ if len(sym.Mapping) > 0 {
116
+ s := strconv.FormatInt(value, 10)
117
+ if v, ok := sym.Mapping[s]; ok && isInt(v) {
118
+ value, _ = strconv.ParseInt(v, 10, 64)
119
+ }
120
+ }
121
} else {
122
s, err := convPduToStringf(pdu, sym.Format)
123
if err != nil {
124
return 0, err
125
}
126
+
127
switch {
128
case sym.ExtractValueCompiled != nil:
129
if sm := sym.ExtractValueCompiled.FindStringSubmatch(s); len(sm) > 1 {
@@ -108,7 +134,15 @@ func processSymbolValue(sym ddprofiledefinition.SymbolConfig, pdu gosnmp.SnmpPDU
134
s = replaceSubmatches(sym.MatchValue, sm)
135
}
136
}
111
- if v, ok := sym.Mapping[s]; ok {
137
+
138
+ // Handle mapping based on the mapping type:
139
+ // 1. Int -> String mapping (e.g., {"1": "up", "2": "down"}):
140
+ // - Used for creating dimensions later
141
+ // - Value remains numeric, no conversion needed here
142
+ // 2. String -> Int mapping (e.g., {"OK": "0", "WARNING": "1"}):
143
+ // - Used to convert string values to numeric values
144
+ // - Apply the mapping to get the numeric representation
145
+ if v, ok := sym.Mapping[s]; ok && isInt(v) {
146
s = v
147
}
148
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector.go
+2
-2
@@ -27,7 +27,7 @@ type Metric struct {
27
Unit string
28
MetricType string
29
Tags map[string]string
30
- Mappings map[string]string
30
+ Mappings map[int64]string
31
Value int64
32
}
33
@@ -77,7 +77,7 @@ func (c *Collector) Collect() ([]*ProfileMetrics, error) {
77
return nil, errors.Join(errs...)
78
}
79
if len(errs) > 0 {
80
- c.log.Debugf("collecting metrics: %v", errs)
80
+ c.log.Debugf("collecting metrics: %v", errors.Join(errs...))
81
}
82
83
return metrics, nil
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_test.go
+330
-41
@@ -10,6 +10,7 @@ import (
10
"github.com/golang/mock/gomock"
11
"github.com/gosnmp/gosnmp"
12
"github.com/stretchr/testify/assert"
13
+ "github.com/stretchr/testify/require"
14
15
snmpmock "github.com/gosnmp/gosnmp/mocks"
16
@@ -448,7 +449,98 @@ func TestCollector_Collect(t *testing.T) {
449
},
450
expectedError: false,
451
},
451
- "metric with mapping": {
452
+ "global tags with mapping": {
453
+ profiles: []*ddsnmp.Profile{
454
+ {
455
+ SourceFile: "test-profile.yaml",
456
+ Definition: &ddprofiledefinition.ProfileDefinition{
457
+ MetricTags: []ddprofiledefinition.MetricTagConfig{
458
+ {
459
+ Tag: "device_type",
460
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
461
+ OID: "1.3.6.1.2.1.1.2.0",
462
+ Name: "sysObjectID",
463
+ },
464
+ Mapping: map[string]string{
465
+ "1.3.6.1.4.1.9.1.1": "router",
466
+ "1.3.6.1.4.1.9.1.2": "switch",
467
+ },
468
+ },
469
+ },
470
+ Metrics: []ddprofiledefinition.MetricsConfig{
471
+ {
472
+ Symbol: ddprofiledefinition.SymbolConfig{
473
+ OID: "1.3.6.1.2.1.1.3.0",
474
+ Name: "sysUpTime",
475
+ },
476
+ },
477
+ },
478
+ },
479
+ },
480
+ },
481
+ setupMock: func(m *snmpmock.MockHandler) {
482
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
483
+ // First call for global tags
484
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.1.2.0"}).Return(
485
+ &gosnmp.SnmpPacket{
486
+ Variables: []gosnmp.SnmpPDU{
487
+ {
488
+ Name: "1.3.6.1.2.1.1.2.0",
489
+ Type: gosnmp.ObjectIdentifier,
490
+ Value: "1.3.6.1.4.1.9.1.1",
491
+ },
492
+ },
493
+ }, nil,
494
+ )
495
+ // Second call for metrics
496
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.1.3.0"}).Return(
497
+ &gosnmp.SnmpPacket{
498
+ Variables: []gosnmp.SnmpPDU{
499
+ {
500
+ Name: "1.3.6.1.2.1.1.3.0",
501
+ Type: gosnmp.TimeTicks,
502
+ Value: uint32(123456),
503
+ },
504
+ },
505
+ }, nil,
506
+ )
507
+ },
508
+ expectedResult: []*ProfileMetrics{
509
+ {
510
+ DeviceMetadata: nil,
511
+ Metrics: []Metric{
512
+ {
513
+ Name: "sysUpTime",
514
+ Value: 123456,
515
+ Tags: map[string]string{"device_type": "router"},
516
+ MetricType: "gauge",
517
+ },
518
+ },
519
+ },
520
+ },
521
+ expectedError: false,
522
+ },
523
+ "empty profile - no metrics defined": {
524
+ profiles: []*ddsnmp.Profile{
525
+ {
526
+ SourceFile: "empty-profile.yaml",
527
+ Definition: &ddprofiledefinition.ProfileDefinition{
528
+ Metrics: []ddprofiledefinition.MetricsConfig{},
529
+ },
530
+ },
531
+ },
532
+ setupMock: func(m *snmpmock.MockHandler) {
533
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
534
+ },
535
+ expectedResult: []*ProfileMetrics{
536
+ {
537
+ DeviceMetadata: nil,
538
+ Metrics: []Metric{},
539
+ },
540
+ },
541
+ expectedError: false,
542
+ },
543
+ "metric with string to int mapping": {
544
profiles: []*ddsnmp.Profile{
545
{
546
SourceFile: "test-profile.yaml",
@@ -492,35 +584,92 @@ func TestCollector_Collect(t *testing.T) {
584
Value: 1,
585
Tags: map[string]string{},
586
MetricType: "gauge",
587
+ Mappings: map[int64]string{
588
+ 0: "OK",
589
+ 1: "WARNING",
590
+ 2: "CRITICAL",
591
+ },
592
},
593
},
594
},
595
},
596
expectedError: false,
597
},
501
- "global tags with mapping": {
598
+ "metric with int to string mapping": {
599
profiles: []*ddsnmp.Profile{
600
{
601
SourceFile: "test-profile.yaml",
602
Definition: &ddprofiledefinition.ProfileDefinition{
506
- MetricTags: []ddprofiledefinition.MetricTagConfig{
603
+ Metrics: []ddprofiledefinition.MetricsConfig{
604
{
508
- Tag: "device_type",
509
- Symbol: ddprofiledefinition.SymbolConfigCompat{
510
- OID: "1.3.6.1.2.1.1.2.0",
511
- Name: "sysObjectID",
512
- },
513
- Mapping: map[string]string{
514
- "1.3.6.1.4.1.9.1.1": "router",
515
- "1.3.6.1.4.1.9.1.2": "switch",
605
+ Symbol: ddprofiledefinition.SymbolConfig{
606
+ OID: "1.3.6.1.2.1.2.2.1.8",
607
+ Name: "ifOperStatus",
608
+ Mapping: map[string]string{
609
+ "1": "up",
610
+ "2": "down",
611
+ "3": "testing",
612
+ "4": "unknown",
613
+ "5": "dormant",
614
+ },
615
},
616
},
617
},
618
+ },
619
+ },
620
+ },
621
+ setupMock: func(m *snmpmock.MockHandler) {
622
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
623
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.2.2.1.8"}).Return(
624
+ &gosnmp.SnmpPacket{
625
+ Variables: []gosnmp.SnmpPDU{
626
+ {
627
+ Name: "1.3.6.1.2.1.2.2.1.8",
628
+ Type: gosnmp.Integer,
629
+ Value: 2, // down
630
+ },
631
+ },
632
+ }, nil,
633
+ )
634
+ },
635
+ expectedResult: []*ProfileMetrics{
636
+ {
637
+ DeviceMetadata: nil,
638
+ Metrics: []Metric{
639
+ {
640
+ Name: "ifOperStatus",
641
+ Value: 2,
642
+ Tags: map[string]string{},
643
+ MetricType: "gauge",
644
+ Mappings: map[int64]string{
645
+ 1: "up",
646
+ 2: "down",
647
+ 3: "testing",
648
+ 4: "unknown",
649
+ 5: "dormant",
650
+ },
651
+ },
652
+ },
653
+ },
654
+ },
655
+ expectedError: false,
656
+ },
657
+ "metric with extract_value and int to string mapping": {
658
+ profiles: []*ddsnmp.Profile{
659
+ {
660
+ SourceFile: "test-profile.yaml",
661
+ Definition: &ddprofiledefinition.ProfileDefinition{
662
Metrics: []ddprofiledefinition.MetricsConfig{
663
{
664
Symbol: ddprofiledefinition.SymbolConfig{
522
- OID: "1.3.6.1.2.1.1.3.0",
523
- Name: "sysUpTime",
665
+ OID: "1.3.6.1.4.1.12124.1.1.8",
666
+ Name: "fanStatus",
667
+ ExtractValueCompiled: mustCompileRegex(`Fan(\d+)`),
668
+ Mapping: map[string]string{
669
+ "1": "normal",
670
+ "2": "warning",
671
+ "3": "critical",
672
+ },
673
},
674
},
675
},
@@ -529,26 +678,68 @@ func TestCollector_Collect(t *testing.T) {
678
},
679
setupMock: func(m *snmpmock.MockHandler) {
680
m.EXPECT().MaxOids().Return(10).AnyTimes()
532
- // First call for global tags
533
- m.EXPECT().Get([]string{"1.3.6.1.2.1.1.2.0"}).Return(
681
+ m.EXPECT().Get([]string{"1.3.6.1.4.1.12124.1.1.8"}).Return(
682
&gosnmp.SnmpPacket{
683
Variables: []gosnmp.SnmpPDU{
684
{
537
- Name: "1.3.6.1.2.1.1.2.0",
538
- Type: gosnmp.ObjectIdentifier,
539
- Value: "1.3.6.1.4.1.9.1.1",
685
+ Name: "1.3.6.1.4.1.12124.1.1.8",
686
+ Type: gosnmp.OctetString,
687
+ Value: []byte("Fan2"),
688
},
689
},
690
}, nil,
691
)
544
- // Second call for metrics
545
- m.EXPECT().Get([]string{"1.3.6.1.2.1.1.3.0"}).Return(
692
+ },
693
+ expectedResult: []*ProfileMetrics{
694
+ {
695
+ DeviceMetadata: nil,
696
+ Metrics: []Metric{
697
+ {
698
+ Name: "fanStatus",
699
+ Value: 2,
700
+ Tags: map[string]string{},
701
+ MetricType: "gauge",
702
+ Mappings: map[int64]string{
703
+ 1: "normal",
704
+ 2: "warning",
705
+ 3: "critical",
706
+ },
707
+ },
708
+ },
709
+ },
710
+ },
711
+ expectedError: false,
712
+ },
713
+ "metric with int to int mapping": {
714
+ profiles: []*ddsnmp.Profile{
715
+ {
716
+ SourceFile: "test-profile.yaml",
717
+ Definition: &ddprofiledefinition.ProfileDefinition{
718
+ Metrics: []ddprofiledefinition.MetricsConfig{
719
+ {
720
+ Symbol: ddprofiledefinition.SymbolConfig{
721
+ OID: "1.3.6.1.2.1.2.2.1.7",
722
+ Name: "ifAdminStatus",
723
+ Mapping: map[string]string{
724
+ "1": "1", // up -> 1
725
+ "2": "0", // down -> 0
726
+ "3": "0", // testing -> 0
727
+ },
728
+ },
729
+ },
730
+ },
731
+ },
732
+ },
733
+ },
734
+ setupMock: func(m *snmpmock.MockHandler) {
735
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
736
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.2.2.1.7"}).Return(
737
&gosnmp.SnmpPacket{
738
Variables: []gosnmp.SnmpPDU{
739
{
549
- Name: "1.3.6.1.2.1.1.3.0",
550
- Type: gosnmp.TimeTicks,
551
- Value: uint32(123456),
740
+ Name: "1.3.6.1.2.1.2.2.1.7",
741
+ Type: gosnmp.Integer,
742
+ Value: 2, // down
743
},
744
},
745
}, nil,
@@ -559,32 +750,139 @@ func TestCollector_Collect(t *testing.T) {
750
DeviceMetadata: nil,
751
Metrics: []Metric{
752
{
562
- Name: "sysUpTime",
563
- Value: 123456,
564
- Tags: map[string]string{"device_type": "router"},
753
+ Name: "ifAdminStatus",
754
+ Value: 0, // mapped from 2 -> 0
755
+ Tags: map[string]string{},
756
MetricType: "gauge",
757
+ Mappings: map[int64]string{
758
+ 1: "1",
759
+ 2: "0",
760
+ 3: "0",
761
+ },
762
},
763
},
764
},
765
},
766
expectedError: false,
767
},
572
- "empty profile - no metrics defined": {
768
+ "metric with partial string to int mapping": {
769
profiles: []*ddsnmp.Profile{
770
{
575
- SourceFile: "empty-profile.yaml",
771
+ SourceFile: "test-profile.yaml",
772
Definition: &ddprofiledefinition.ProfileDefinition{
577
- Metrics: []ddprofiledefinition.MetricsConfig{},
773
+ Metrics: []ddprofiledefinition.MetricsConfig{
774
+ {
775
+ Symbol: ddprofiledefinition.SymbolConfig{
776
+ OID: "1.3.6.1.4.1.12124.1.1.2",
777
+ Name: "clusterHealth",
778
+ Mapping: map[string]string{
779
+ "OK": "0",
780
+ "WARNING": "1",
781
+ // CRITICAL is not mapped
782
+ },
783
+ },
784
+ },
785
+ },
786
},
787
},
788
},
789
setupMock: func(m *snmpmock.MockHandler) {
790
m.EXPECT().MaxOids().Return(10).AnyTimes()
791
+ m.EXPECT().Get([]string{"1.3.6.1.4.1.12124.1.1.2"}).Return(
792
+ &gosnmp.SnmpPacket{
793
+ Variables: []gosnmp.SnmpPDU{
794
+ {
795
+ Name: "1.3.6.1.4.1.12124.1.1.2",
796
+ Type: gosnmp.OctetString,
797
+ Value: []byte("CRITICAL"),
798
+ },
799
+ },
800
+ }, nil,
801
+ )
802
+ },
803
+ expectedResult: nil,
804
+ expectedError: true,
805
+ },
806
+ "metric with mixed mapping values": {
807
+ profiles: []*ddsnmp.Profile{
808
+ {
809
+ SourceFile: "test-profile.yaml",
810
+ Definition: &ddprofiledefinition.ProfileDefinition{
811
+ Metrics: []ddprofiledefinition.MetricsConfig{
812
+ {
813
+ Symbol: ddprofiledefinition.SymbolConfig{
814
+ OID: "1.3.6.1.4.1.12124.1.1.2",
815
+ Name: "deviceStatus",
816
+ Mapping: map[string]string{
817
+ "OK": "0",
818
+ "WARNING": "1",
819
+ "ERROR": "invalid", // This will cause metric to be skipped
820
+ },
821
+ },
822
+ },
823
+ },
824
+ },
825
+ },
826
+ },
827
+ setupMock: func(m *snmpmock.MockHandler) {
828
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
829
+ m.EXPECT().Get([]string{"1.3.6.1.4.1.12124.1.1.2"}).Return(
830
+ &gosnmp.SnmpPacket{
831
+ Variables: []gosnmp.SnmpPDU{
832
+ {
833
+ Name: "1.3.6.1.4.1.12124.1.1.2",
834
+ Type: gosnmp.OctetString,
835
+ Value: []byte("ERROR"),
836
+ },
837
+ },
838
+ }, nil,
839
+ )
840
+ },
841
+ expectedResult: nil,
842
+ expectedError: true,
843
+ },
844
+ "metric with no mapping": {
845
+ profiles: []*ddsnmp.Profile{
846
+ {
847
+ SourceFile: "test-profile.yaml",
848
+ Definition: &ddprofiledefinition.ProfileDefinition{
849
+ Metrics: []ddprofiledefinition.MetricsConfig{
850
+ {
851
+ Symbol: ddprofiledefinition.SymbolConfig{
852
+ OID: "1.3.6.1.2.1.1.3.0",
853
+ Name: "sysUpTime",
854
+ },
855
+ },
856
+ },
857
+ },
858
+ },
859
+ },
860
+ setupMock: func(m *snmpmock.MockHandler) {
861
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
862
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.1.3.0"}).Return(
863
+ &gosnmp.SnmpPacket{
864
+ Variables: []gosnmp.SnmpPDU{
865
+ {
866
+ Name: "1.3.6.1.2.1.1.3.0",
867
+ Type: gosnmp.TimeTicks,
868
+ Value: uint32(123456),
869
+ },
870
+ },
871
+ }, nil,
872
+ )
873
},
874
expectedResult: []*ProfileMetrics{
875
{
876
DeviceMetadata: nil,
587
- Metrics: []Metric{},
877
+ Metrics: []Metric{
878
+ {
879
+ Name: "sysUpTime",
880
+ Value: 123456,
881
+ Tags: map[string]string{},
882
+ MetricType: "gauge",
883
+ Mappings: nil, // No mappings
884
+ },
885
+ },
886
},
887
},
888
expectedError: false,
@@ -593,24 +891,16 @@ func TestCollector_Collect(t *testing.T) {
891
892
for name, tc := range tests {
893
t.Run(name, func(t *testing.T) {
596
- // Create gomock controller
894
ctrl := gomock.NewController(t)
895
defer ctrl.Finish()
896
600
- // Create mock SNMP client
897
mockHandler := snmpmock.NewMockHandler(ctrl)
898
tc.setupMock(mockHandler)
899
604
- // Create logger
605
- log := logger.New()
606
-
607
- // Create collector
608
- collector := New(mockHandler, tc.profiles, log)
900
+ collector := New(mockHandler, tc.profiles, logger.New())
901
610
- // Execute
902
result, err := collector.Collect()
903
613
- // Verify error
904
if tc.expectedError {
905
assert.Error(t, err)
906
if tc.errorContains != "" {
@@ -620,9 +910,8 @@ func TestCollector_Collect(t *testing.T) {
910
assert.NoError(t, err)
911
}
912
623
- // Verify result
913
if tc.expectedResult != nil {
625
- assert.Equal(t, len(tc.expectedResult), len(result))
914
+ require.Equal(t, len(tc.expectedResult), len(result))
915
for i := range tc.expectedResult {
916
assert.Equal(t, tc.expectedResult[i].DeviceMetadata, result[i].DeviceMetadata)
917
assert.ElementsMatch(t, tc.expectedResult[i].Metrics, result[i].Metrics)
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/utils.go
+14
@@ -214,3 +214,17 @@ func ternary[T any](cond bool, a, b T) T {
214
}
215
return b
216
}
217
+
218
+func isInt(s string) bool {
219
+ _, err := strconv.ParseInt(s, 10, 64)
220
+ return err == nil
221
+}
222
+
223
+func isMappingKeysNumeric(mapping map[string]string) bool {
224
+ for k := range mapping {
225
+ if !isInt(k) {
226
+ return false
227
+ }
228
+ }
229
+ return true
230
+}