chore(go.d/ddsnmp): fix table collection with caching (#20509)
Ilya Mashchenko committed
Jun 18, 2025 at 00:31 UTC
c1fd278ba64316aaa287eec2111d96493c70d222
2 files changed
+340
-2587
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collect_table.go
+55
-33
@@ -22,18 +22,16 @@ type tableWalkResult struct {
22
}
23
24
func (c *Collector) collectTableMetrics(prof *ddsnmp.Profile) ([]Metric, error) {
25
- // Phase 1: Walk all tables and collect raw data
26
- walkResults, err := c.walkAllTables(prof)
25
+ walkResults, err := c.walkTablesAsNeeded(prof)
26
if err != nil {
27
return nil, err
28
}
29
31
- // Phase 2: Process walked data into metrics
30
return c.processTableWalkResults(walkResults)
31
}
32
35
-// Phase 1: Walk all tables
36
-func (c *Collector) walkAllTables(prof *ddsnmp.Profile) ([]tableWalkResult, error) {
33
+// Phase 1: Walk only tables that aren't fully cached
34
+func (c *Collector) walkTablesAsNeeded(prof *ddsnmp.Profile) ([]tableWalkResult, error) {
35
var results []tableWalkResult
36
var errs []error
37
var missingOIDs []string
@@ -44,7 +42,10 @@ func (c *Collector) walkAllTables(prof *ddsnmp.Profile) ([]tableWalkResult, erro
42
// Map to track which tables need to be walked
43
tablesToWalk := make(map[string]bool)
44
47
- // First pass: identify unique tables to walk
45
+ // Map configs by table OID
46
+ tableConfigs := make(map[string][]ddprofiledefinition.MetricsConfig)
47
+
48
+ // First pass: identify tables and check cache status
49
for _, cfg := range prof.Definition.Metrics {
50
if cfg.IsScalar() || cfg.Table.OID == "" {
51
continue
@@ -56,10 +57,16 @@ func (c *Collector) walkAllTables(prof *ddsnmp.Profile) ([]tableWalkResult, erro
57
continue
58
}
59
59
- tablesToWalk[tableOID] = true
60
+ tableConfigs[tableOID] = append(tableConfigs[tableOID], cfg)
61
+
62
+ if !c.tableCache.isConfigCached(cfg) {
63
+ tablesToWalk[tableOID] = true
64
+ }
65
}
66
62
- // Walk each unique table once
67
+ // Walk each table that needs it (only once per table)
68
+ c.log.Debugf("Tables walking %d cached %d (%d total)",
69
+ len(tablesToWalk), len(tableConfigs)-len(tablesToWalk), len(tableConfigs))
70
for tableOID := range tablesToWalk {
71
pdus, err := c.snmpWalk(tableOID)
72
if err != nil {
@@ -72,22 +79,31 @@ func (c *Collector) walkAllTables(prof *ddsnmp.Profile) ([]tableWalkResult, erro
79
}
80
}
81
75
- // Second pass: create results for ALL metric configs
82
+ // Second pass: create results for ALL configs
83
for _, cfg := range prof.Definition.Metrics {
84
if cfg.IsScalar() || cfg.Table.OID == "" {
85
continue
86
}
87
81
- pdus, ok := walkedTables[cfg.Table.OID]
82
- if !ok {
88
+ if c.missingOIDs[trimOID(cfg.Table.OID)] {
89
continue
90
}
91
86
- results = append(results, tableWalkResult{
87
- tableOID: cfg.Table.OID,
88
- pdus: pdus,
89
- config: cfg,
90
- })
92
+ // Add to results if we have walked data OR if it's cached
93
+ if pdus, ok := walkedTables[cfg.Table.OID]; ok {
94
+ results = append(results, tableWalkResult{
95
+ tableOID: cfg.Table.OID,
96
+ pdus: pdus,
97
+ config: cfg,
98
+ })
99
+ } else if c.tableCache.isConfigCached(cfg) {
100
+ // Add config without PDUs - will use cache in processing
101
+ results = append(results, tableWalkResult{
102
+ tableOID: cfg.Table.OID,
103
+ pdus: nil,
104
+ config: cfg,
105
+ })
106
+ }
107
}
108
109
if len(missingOIDs) > 0 {
@@ -109,7 +125,9 @@ func (c *Collector) processTableWalkResults(walkResults []tableWalkResult) ([]Me
125
// Build a map for quick lookup of walked data by table OID
126
walkedData := make(map[string]map[string]gosnmp.SnmpPDU)
127
for _, result := range walkResults {
112
- walkedData[result.tableOID] = result.pdus
128
+ if result.pdus != nil {
129
+ walkedData[result.tableOID] = result.pdus
130
+ }
131
}
132
133
// Build a map of table name to OID for cross-table lookups
@@ -122,12 +140,27 @@ func (c *Collector) processTableWalkResults(walkResults []tableWalkResult) ([]Me
140
141
// Process each table's walked data
142
for _, result := range walkResults {
125
- tableMetrics, err := c.processTableData(result.config, result.pdus, walkedData, tableNameToOID)
126
- if err != nil {
127
- errs = append(errs, fmt.Errorf("table '%s': %w", result.config.Table.Name, err))
128
- continue
143
+ // Try cache first
144
+ if cachedOIDs, cachedTags, ok := c.tableCache.getCachedData(result.config); ok {
145
+ columnOIDs := buildColumnOIDs(result.config)
146
+ cacheMetrics, err := c.collectTableWithCache(result.config, cachedOIDs, cachedTags, columnOIDs)
147
+ if err == nil {
148
+ c.log.Debugf("Successfully collected table %s using cache", result.config.Table.Name)
149
+ metrics = append(metrics, cacheMetrics...)
150
+ continue
151
+ }
152
+ c.log.Debugf("Cached collection failed for table %s, falling back to process walked data: %v", result.config.Table.Name, err)
153
+ }
154
+
155
+ // Process walked data if available
156
+ if result.pdus != nil {
157
+ tableMetrics, err := c.processTableData(result.config, result.pdus, walkedData, tableNameToOID)
158
+ if err != nil {
159
+ errs = append(errs, fmt.Errorf("table '%s': %w", result.config.Table.Name, err))
160
+ continue
161
+ }
162
+ metrics = append(metrics, tableMetrics...)
163
}
130
- metrics = append(metrics, tableMetrics...)
164
}
165
166
if len(metrics) == 0 && len(errs) > 0 {
@@ -139,17 +172,6 @@ func (c *Collector) processTableWalkResults(walkResults []tableWalkResult) ([]Me
172
173
// Process a single table's data
174
func (c *Collector) processTableData(cfg ddprofiledefinition.MetricsConfig, pdus map[string]gosnmp.SnmpPDU, allWalkedData map[string]map[string]gosnmp.SnmpPDU, tableNameToOID map[string]string) ([]Metric, error) {
142
- // Try to use cache if available
143
- if cachedOIDs, cachedTags, ok := c.tableCache.getCachedData(cfg); ok {
144
- metrics, err := c.collectTableWithCache(cfg, cachedOIDs, cachedTags, buildColumnOIDs(cfg))
145
- if err == nil {
146
- c.log.Debugf("Successfully collected table %s using cache", cfg.Table.Name)
147
- return metrics, nil
148
- }
149
- c.log.Debugf("Cached collection failed for table %s, falling back to process walked data: %v", cfg.Table.Name, err)
150
- }
151
-
152
- // Process without cache
175
columnOIDs := buildColumnOIDs(cfg)
176
tagColumnOIDs := buildTagColumnOIDs(cfg)
177
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_test.go
+285
-2554
@@ -6,6 +6,7 @@ import (
6
"errors"
7
"regexp"
8
"testing"
9
+ "time"
10
11
"github.com/golang/mock/gomock"
12
"github.com/gosnmp/gosnmp"
@@ -19,14 +20,14 @@ import (
20
"github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
21
)
22
22
-func TestCollector_Collect(t *testing.T) {
23
+func TestCollector_Collect_Group1_ScalarMetrics(t *testing.T) {
24
tests := map[string]struct {
24
- name string
25
profiles []*ddsnmp.Profile
26
setupMock func(m *snmpmock.MockHandler)
27
expectedResult []*ProfileMetrics
28
expectedError bool
29
errorContains string
30
+ enableCache bool // Whether to enable cache for this test
31
}{
32
"successful collection with scalar metrics only": {
33
profiles: []*ddsnmp.Profile{
@@ -83,7 +84,7 @@ func TestCollector_Collect(t *testing.T) {
84
},
85
},
86
},
86
- expectedError: false, // Changed to false - partial success is not an error
87
+ expectedError: false,
88
},
89
"successful collection with global tags": {
90
profiles: []*ddsnmp.Profile{
@@ -275,6 +276,124 @@ func TestCollector_Collect(t *testing.T) {
276
},
277
expectedError: false,
278
},
279
+ "metric with extract_value": {
280
+ profiles: []*ddsnmp.Profile{
281
+ {
282
+ SourceFile: "test-profile.yaml",
283
+ Definition: &ddprofiledefinition.ProfileDefinition{
284
+ Metrics: []ddprofiledefinition.MetricsConfig{
285
+ {
286
+ Symbol: ddprofiledefinition.SymbolConfig{
287
+ OID: "1.3.6.1.4.1.12124.1.1.8",
288
+ Name: "temperature",
289
+ ExtractValueCompiled: mustCompileRegex(`(\d+)C`),
290
+ },
291
+ },
292
+ },
293
+ },
294
+ },
295
+ },
296
+ setupMock: func(m *snmpmock.MockHandler) {
297
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
298
+ m.EXPECT().Get([]string{"1.3.6.1.4.1.12124.1.1.8"}).Return(
299
+ &gosnmp.SnmpPacket{
300
+ Variables: []gosnmp.SnmpPDU{
301
+ {
302
+ Name: "1.3.6.1.4.1.12124.1.1.8",
303
+ Type: gosnmp.OctetString,
304
+ Value: []byte("25C"),
305
+ },
306
+ },
307
+ }, nil,
308
+ )
309
+ },
310
+ expectedResult: []*ProfileMetrics{
311
+ {
312
+ Source: "test-profile.yaml",
313
+ DeviceMetadata: nil,
314
+ Metrics: []Metric{
315
+ {
316
+ Name: "temperature",
317
+ Value: 25,
318
+ MetricType: "gauge",
319
+ },
320
+ },
321
+ },
322
+ },
323
+ expectedError: false,
324
+ },
325
+ "global tags with mapping": {
326
+ profiles: []*ddsnmp.Profile{
327
+ {
328
+ SourceFile: "test-profile.yaml",
329
+ Definition: &ddprofiledefinition.ProfileDefinition{
330
+ MetricTags: []ddprofiledefinition.MetricTagConfig{
331
+ {
332
+ Tag: "device_type",
333
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
334
+ OID: "1.3.6.1.2.1.1.2.0",
335
+ Name: "sysObjectID",
336
+ },
337
+ Mapping: map[string]string{
338
+ "1.3.6.1.4.1.9.1.1": "router",
339
+ "1.3.6.1.4.1.9.1.2": "switch",
340
+ },
341
+ },
342
+ },
343
+ Metrics: []ddprofiledefinition.MetricsConfig{
344
+ {
345
+ Symbol: ddprofiledefinition.SymbolConfig{
346
+ OID: "1.3.6.1.2.1.1.3.0",
347
+ Name: "sysUpTime",
348
+ },
349
+ },
350
+ },
351
+ },
352
+ },
353
+ },
354
+ setupMock: func(m *snmpmock.MockHandler) {
355
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
356
+ // First call for global tags
357
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.1.2.0"}).Return(
358
+ &gosnmp.SnmpPacket{
359
+ Variables: []gosnmp.SnmpPDU{
360
+ {
361
+ Name: "1.3.6.1.2.1.1.2.0",
362
+ Type: gosnmp.ObjectIdentifier,
363
+ Value: "1.3.6.1.4.1.9.1.1",
364
+ },
365
+ },
366
+ }, nil,
367
+ )
368
+ // Second call for metrics
369
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.1.3.0"}).Return(
370
+ &gosnmp.SnmpPacket{
371
+ Variables: []gosnmp.SnmpPDU{
372
+ {
373
+ Name: "1.3.6.1.2.1.1.3.0",
374
+ Type: gosnmp.TimeTicks,
375
+ Value: uint32(123456),
376
+ },
377
+ },
378
+ }, nil,
379
+ )
380
+ },
381
+ expectedResult: []*ProfileMetrics{
382
+ {
383
+ Source: "test-profile.yaml",
384
+ DeviceMetadata: nil,
385
+ Tags: map[string]string{"device_type": "router"},
386
+ Metrics: []Metric{
387
+ {
388
+ Name: "sysUpTime",
389
+ Value: 123456,
390
+ MetricType: "gauge",
391
+ },
392
+ },
393
+ },
394
+ },
395
+ expectedError: false,
396
+ },
397
"OID not found - returns empty metrics": {
398
profiles: []*ddsnmp.Profile{
399
{
@@ -340,6 +459,26 @@ func TestCollector_Collect(t *testing.T) {
459
expectedError: true,
460
errorContains: "SNMP timeout",
461
},
462
+ "empty profile - no metrics defined": {
463
+ profiles: []*ddsnmp.Profile{
464
+ {
465
+ SourceFile: "empty-profile.yaml",
466
+ Definition: &ddprofiledefinition.ProfileDefinition{
467
+ Metrics: []ddprofiledefinition.MetricsConfig{},
468
+ },
469
+ },
470
+ },
471
+ setupMock: func(m *snmpmock.MockHandler) {
472
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
473
+ },
474
+ expectedResult: []*ProfileMetrics{
475
+ {
476
+ DeviceMetadata: nil,
477
+ Metrics: []Metric{},
478
+ },
479
+ },
480
+ expectedError: false,
481
+ },
482
"multiple profiles - one fails": {
483
profiles: []*ddsnmp.Profile{
484
{
@@ -404,7 +543,67 @@ func TestCollector_Collect(t *testing.T) {
543
},
544
expectedError: false, // Should return partial results
545
},
407
- "metric with extract_value": {
546
+ }
547
+
548
+ for name, tc := range tests {
549
+ t.Run(name, func(t *testing.T) {
550
+ ctrl := gomock.NewController(t)
551
+ defer ctrl.Finish()
552
+
553
+ mockHandler := snmpmock.NewMockHandler(ctrl)
554
+ tc.setupMock(mockHandler)
555
+
556
+ collector := New(mockHandler, tc.profiles, logger.New())
557
+ collector.DoTableMetrics = true
558
+
559
+ // Configure cache based on test requirements
560
+ if tc.enableCache {
561
+ collector.tableCache.setTTL(30*time.Second, 0)
562
+ } else {
563
+ collector.tableCache.setTTL(0, 0) // Disable cache
564
+ }
565
+
566
+ result, err := collector.Collect()
567
+
568
+ // Clear circular references
569
+ for _, profile := range result {
570
+ for i := range profile.Metrics {
571
+ profile.Metrics[i].Profile = nil
572
+ }
573
+ }
574
+
575
+ if tc.expectedError {
576
+ assert.Error(t, err)
577
+ if tc.errorContains != "" {
578
+ assert.Contains(t, err.Error(), tc.errorContains)
579
+ }
580
+ } else {
581
+ assert.NoError(t, err)
582
+ }
583
+
584
+ if tc.expectedResult != nil {
585
+ require.Equal(t, len(tc.expectedResult), len(result))
586
+ for i := range tc.expectedResult {
587
+ assert.Equal(t, tc.expectedResult[i].DeviceMetadata, result[i].DeviceMetadata)
588
+ assert.Equal(t, tc.expectedResult[i].Tags, result[i].Tags)
589
+ assert.ElementsMatch(t, tc.expectedResult[i].Metrics, result[i].Metrics)
590
+ }
591
+ } else {
592
+ assert.Nil(t, result)
593
+ }
594
+ })
595
+ }
596
+}
597
+
598
+func TestCollector_Collect_Group2_ValueMappings(t *testing.T) {
599
+ tests := map[string]struct {
600
+ profiles []*ddsnmp.Profile
601
+ setupMock func(m *snmpmock.MockHandler)
602
+ expectedResult []*ProfileMetrics
603
+ expectedError bool
604
+ errorContains string
605
+ }{
606
+ "metric with string to int mapping": {
607
profiles: []*ddsnmp.Profile{
608
{
609
SourceFile: "test-profile.yaml",
@@ -412,9 +611,13 @@ func TestCollector_Collect(t *testing.T) {
611
Metrics: []ddprofiledefinition.MetricsConfig{
612
{
613
Symbol: ddprofiledefinition.SymbolConfig{
415
- OID: "1.3.6.1.4.1.12124.1.1.8",
416
- Name: "temperature",
417
- ExtractValueCompiled: mustCompileRegex(`(\d+)C`),
614
+ OID: "1.3.6.1.4.1.12124.1.1.2",
615
+ Name: "clusterHealth",
616
+ Mapping: map[string]string{
617
+ "OK": "0",
618
+ "WARNING": "1",
619
+ "CRITICAL": "2",
620
+ },
621
},
622
},
623
},
@@ -423,13 +626,13 @@ func TestCollector_Collect(t *testing.T) {
626
},
627
setupMock: func(m *snmpmock.MockHandler) {
628
m.EXPECT().MaxOids().Return(10).AnyTimes()
426
- m.EXPECT().Get([]string{"1.3.6.1.4.1.12124.1.1.8"}).Return(
629
+ m.EXPECT().Get([]string{"1.3.6.1.4.1.12124.1.1.2"}).Return(
630
&gosnmp.SnmpPacket{
631
Variables: []gosnmp.SnmpPDU{
632
{
430
- Name: "1.3.6.1.4.1.12124.1.1.8",
633
+ Name: "1.3.6.1.4.1.12124.1.1.2",
634
Type: gosnmp.OctetString,
432
- Value: []byte("25C"),
635
+ Value: []byte("WARNING"),
636
},
637
},
638
}, nil,
@@ -441,38 +644,37 @@ func TestCollector_Collect(t *testing.T) {
644
DeviceMetadata: nil,
645
Metrics: []Metric{
646
{
444
- Name: "temperature",
445
- Value: 25,
647
+ Name: "clusterHealth",
648
+ Value: 1,
649
MetricType: "gauge",
650
+ Mappings: map[int64]string{
651
+ 0: "OK",
652
+ 1: "WARNING",
653
+ 2: "CRITICAL",
654
+ },
655
},
656
},
657
},
658
},
659
expectedError: false,
660
},
453
- "global tags with mapping": {
661
+ "metric with int to string mapping": {
662
profiles: []*ddsnmp.Profile{
663
{
664
SourceFile: "test-profile.yaml",
665
Definition: &ddprofiledefinition.ProfileDefinition{
458
- MetricTags: []ddprofiledefinition.MetricTagConfig{
459
- {
460
- Tag: "device_type",
461
- Symbol: ddprofiledefinition.SymbolConfigCompat{
462
- OID: "1.3.6.1.2.1.1.2.0",
463
- Name: "sysObjectID",
464
- },
465
- Mapping: map[string]string{
466
- "1.3.6.1.4.1.9.1.1": "router",
467
- "1.3.6.1.4.1.9.1.2": "switch",
468
- },
469
- },
470
- },
666
Metrics: []ddprofiledefinition.MetricsConfig{
667
{
668
Symbol: ddprofiledefinition.SymbolConfig{
474
- OID: "1.3.6.1.2.1.1.3.0",
475
- Name: "sysUpTime",
669
+ OID: "1.3.6.1.2.1.2.2.1.8",
670
+ Name: "ifOperStatus",
671
+ Mapping: map[string]string{
672
+ "1": "up",
673
+ "2": "down",
674
+ "3": "testing",
675
+ "4": "unknown",
676
+ "5": "dormant",
677
+ },
678
},
679
},
680
},
@@ -481,154 +683,13 @@ func TestCollector_Collect(t *testing.T) {
683
},
684
setupMock: func(m *snmpmock.MockHandler) {
685
m.EXPECT().MaxOids().Return(10).AnyTimes()
484
- // First call for global tags
485
- m.EXPECT().Get([]string{"1.3.6.1.2.1.1.2.0"}).Return(
686
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.2.2.1.8"}).Return(
687
&gosnmp.SnmpPacket{
688
Variables: []gosnmp.SnmpPDU{
689
{
489
- Name: "1.3.6.1.2.1.1.2.0",
490
- Type: gosnmp.ObjectIdentifier,
491
- Value: "1.3.6.1.4.1.9.1.1",
492
- },
493
- },
494
- }, nil,
495
- )
496
- // Second call for metrics
497
- m.EXPECT().Get([]string{"1.3.6.1.2.1.1.3.0"}).Return(
498
- &gosnmp.SnmpPacket{
499
- Variables: []gosnmp.SnmpPDU{
500
- {
501
- Name: "1.3.6.1.2.1.1.3.0",
502
- Type: gosnmp.TimeTicks,
503
- Value: uint32(123456),
504
- },
505
- },
506
- }, nil,
507
- )
508
- },
509
- expectedResult: []*ProfileMetrics{
510
- {
511
- Source: "test-profile.yaml",
512
- DeviceMetadata: nil,
513
- Tags: map[string]string{"device_type": "router"},
514
- Metrics: []Metric{
515
- {
516
- Name: "sysUpTime",
517
- Value: 123456,
518
- MetricType: "gauge",
519
- },
520
- },
521
- },
522
- },
523
- expectedError: false,
524
- },
525
- "empty profile - no metrics defined": {
526
- profiles: []*ddsnmp.Profile{
527
- {
528
- SourceFile: "empty-profile.yaml",
529
- Definition: &ddprofiledefinition.ProfileDefinition{
530
- Metrics: []ddprofiledefinition.MetricsConfig{},
531
- },
532
- },
533
- },
534
- setupMock: func(m *snmpmock.MockHandler) {
535
- m.EXPECT().MaxOids().Return(10).AnyTimes()
536
- },
537
- expectedResult: []*ProfileMetrics{
538
- {
539
- DeviceMetadata: nil,
540
- Metrics: []Metric{},
541
- },
542
- },
543
- expectedError: false,
544
- },
545
- "metric with string to int mapping": {
546
- profiles: []*ddsnmp.Profile{
547
- {
548
- SourceFile: "test-profile.yaml",
549
- Definition: &ddprofiledefinition.ProfileDefinition{
550
- Metrics: []ddprofiledefinition.MetricsConfig{
551
- {
552
- Symbol: ddprofiledefinition.SymbolConfig{
553
- OID: "1.3.6.1.4.1.12124.1.1.2",
554
- Name: "clusterHealth",
555
- Mapping: map[string]string{
556
- "OK": "0",
557
- "WARNING": "1",
558
- "CRITICAL": "2",
559
- },
560
- },
561
- },
562
- },
563
- },
564
- },
565
- },
566
- setupMock: func(m *snmpmock.MockHandler) {
567
- m.EXPECT().MaxOids().Return(10).AnyTimes()
568
- m.EXPECT().Get([]string{"1.3.6.1.4.1.12124.1.1.2"}).Return(
569
- &gosnmp.SnmpPacket{
570
- Variables: []gosnmp.SnmpPDU{
571
- {
572
- Name: "1.3.6.1.4.1.12124.1.1.2",
573
- Type: gosnmp.OctetString,
574
- Value: []byte("WARNING"),
575
- },
576
- },
577
- }, nil,
578
- )
579
- },
580
- expectedResult: []*ProfileMetrics{
581
- {
582
- Source: "test-profile.yaml",
583
- DeviceMetadata: nil,
584
- Metrics: []Metric{
585
- {
586
- Name: "clusterHealth",
587
- Value: 1,
588
- MetricType: "gauge",
589
- Mappings: map[int64]string{
590
- 0: "OK",
591
- 1: "WARNING",
592
- 2: "CRITICAL",
593
- },
594
- },
595
- },
596
- },
597
- },
598
- expectedError: false,
599
- },
600
- "metric with int to string mapping": {
601
- profiles: []*ddsnmp.Profile{
602
- {
603
- SourceFile: "test-profile.yaml",
604
- Definition: &ddprofiledefinition.ProfileDefinition{
605
- Metrics: []ddprofiledefinition.MetricsConfig{
606
- {
607
- Symbol: ddprofiledefinition.SymbolConfig{
608
- OID: "1.3.6.1.2.1.2.2.1.8",
609
- Name: "ifOperStatus",
610
- Mapping: map[string]string{
611
- "1": "up",
612
- "2": "down",
613
- "3": "testing",
614
- "4": "unknown",
615
- "5": "dormant",
616
- },
617
- },
618
- },
619
- },
620
- },
621
- },
622
- },
623
- setupMock: func(m *snmpmock.MockHandler) {
624
- m.EXPECT().MaxOids().Return(10).AnyTimes()
625
- m.EXPECT().Get([]string{"1.3.6.1.2.1.2.2.1.8"}).Return(
626
- &gosnmp.SnmpPacket{
627
- Variables: []gosnmp.SnmpPDU{
628
- {
629
- Name: "1.3.6.1.2.1.2.2.1.8",
630
- Type: gosnmp.Integer,
631
- Value: 2, // down
690
+ Name: "1.3.6.1.2.1.2.2.1.8",
691
+ Type: gosnmp.Integer,
692
+ Value: 2, // down
693
},
694
},
695
}, nil,
@@ -804,6 +865,7 @@ func TestCollector_Collect(t *testing.T) {
865
},
866
expectedResult: nil,
867
expectedError: true,
868
+ errorContains: "strconv.ParseInt",
869
},
870
"metric with mixed mapping values": {
871
profiles: []*ddsnmp.Profile{
@@ -842,6 +904,7 @@ func TestCollector_Collect(t *testing.T) {
904
},
905
expectedResult: nil,
906
expectedError: true,
907
+ errorContains: "strconv.ParseInt",
908
},
909
"metric with no mapping": {
910
profiles: []*ddsnmp.Profile{
@@ -889,35 +952,20 @@ func TestCollector_Collect(t *testing.T) {
952
},
953
expectedError: false,
954
},
892
- "table metrics with same-table tags": {
955
+ "metric with numeric value and int to string mapping": {
956
profiles: []*ddsnmp.Profile{
957
{
958
SourceFile: "test-profile.yaml",
959
Definition: &ddprofiledefinition.ProfileDefinition{
960
Metrics: []ddprofiledefinition.MetricsConfig{
961
{
899
- MIB: "IF-MIB",
900
- Table: ddprofiledefinition.SymbolConfig{
901
- OID: "1.3.6.1.2.1.2.2",
902
- Name: "ifTable",
903
- },
904
- Symbols: []ddprofiledefinition.SymbolConfig{
905
- {
906
- OID: "1.3.6.1.2.1.2.2.1.10",
907
- Name: "ifInOctets",
908
- },
909
- {
910
- OID: "1.3.6.1.2.1.2.2.1.16",
911
- Name: "ifOutOctets",
912
- },
913
- },
914
- MetricTags: []ddprofiledefinition.MetricTagConfig{
915
- {
916
- Tag: "interface",
917
- Symbol: ddprofiledefinition.SymbolConfigCompat{
918
- OID: "1.3.6.1.2.1.2.2.1.2",
919
- Name: "ifDescr",
920
- },
962
+ Symbol: ddprofiledefinition.SymbolConfig{
963
+ OID: "1.3.6.1.2.1.2.2.1.7",
964
+ Name: "ifAdminStatus",
965
+ Mapping: map[string]string{
966
+ "1": "up",
967
+ "2": "down",
968
+ "3": "testing",
969
},
970
},
971
},
@@ -927,146 +975,15 @@ func TestCollector_Collect(t *testing.T) {
975
},
976
setupMock: func(m *snmpmock.MockHandler) {
977
m.EXPECT().MaxOids().Return(10).AnyTimes()
930
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
931
- m.EXPECT().BulkWalkAll("1.3.6.1.2.1.2.2").Return(
932
- []gosnmp.SnmpPDU{
933
- // Row 1 - index 1
934
- {
935
- Name: "1.3.6.1.2.1.2.2.1.2.1",
936
- Type: gosnmp.OctetString,
937
- Value: []byte("eth0"),
938
- },
939
- {
940
- Name: "1.3.6.1.2.1.2.2.1.10.1",
941
- Type: gosnmp.Counter32,
942
- Value: uint(1000),
943
- },
944
- {
945
- Name: "1.3.6.1.2.1.2.2.1.16.1",
946
- Type: gosnmp.Counter32,
947
- Value: uint(2000),
948
- },
949
- // Row 2 - index 2
950
- {
951
- Name: "1.3.6.1.2.1.2.2.1.2.2",
952
- Type: gosnmp.OctetString,
953
- Value: []byte("eth1"),
954
- },
955
- {
956
- Name: "1.3.6.1.2.1.2.2.1.10.2",
957
- Type: gosnmp.Counter32,
958
- Value: uint(3000),
959
- },
960
- {
961
- Name: "1.3.6.1.2.1.2.2.1.16.2",
962
- Type: gosnmp.Counter32,
963
- Value: uint(4000),
964
- },
965
- }, nil,
966
- )
967
- },
968
- expectedResult: []*ProfileMetrics{
969
- {
970
- Source: "test-profile.yaml",
971
- DeviceMetadata: nil,
972
- Metrics: []Metric{
973
- {
974
- Name: "ifInOctets",
975
- Value: 1000,
976
- Tags: map[string]string{"interface": "eth0"},
977
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
978
- IsTable: true,
979
- },
980
- {
981
- Name: "ifOutOctets",
982
- Value: 2000,
983
- Tags: map[string]string{"interface": "eth0"},
984
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
985
- IsTable: true,
986
- },
987
- {
988
- Name: "ifInOctets",
989
- Value: 3000,
990
- Tags: map[string]string{"interface": "eth1"},
991
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
992
- IsTable: true,
993
- },
994
- {
995
- Name: "ifOutOctets",
996
- Value: 4000,
997
- Tags: map[string]string{"interface": "eth1"},
998
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
999
- IsTable: true,
1000
- },
1001
- },
1002
- },
1003
- },
1004
- expectedError: false,
1005
- },
1006
- "table metrics with tag mapping": {
1007
- profiles: []*ddsnmp.Profile{
1008
- {
1009
- SourceFile: "test-profile.yaml",
1010
- Definition: &ddprofiledefinition.ProfileDefinition{
1011
- Metrics: []ddprofiledefinition.MetricsConfig{
978
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.2.2.1.7"}).Return(
979
+ &gosnmp.SnmpPacket{
980
+ Variables: []gosnmp.SnmpPDU{
981
{
1013
- MIB: "IF-MIB",
1014
- Table: ddprofiledefinition.SymbolConfig{
1015
- OID: "1.3.6.1.2.1.2.2",
1016
- Name: "ifTable",
1017
- },
1018
- Symbols: []ddprofiledefinition.SymbolConfig{
1019
- {
1020
- OID: "1.3.6.1.2.1.2.2.1.10",
1021
- Name: "ifInOctets",
1022
- },
1023
- },
1024
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1025
- {
1026
- Tag: "if_type",
1027
- Symbol: ddprofiledefinition.SymbolConfigCompat{
1028
- OID: "1.3.6.1.2.1.2.2.1.3",
1029
- Name: "ifType",
1030
- },
1031
- Mapping: map[string]string{
1032
- "1": "other",
1033
- "2": "regular1822",
1034
- "6": "ethernetCsmacd",
1035
- },
1036
- },
1037
- },
982
+ Name: "1.3.6.1.2.1.2.2.1.7",
983
+ Type: gosnmp.Integer,
984
+ Value: 1, // up
985
},
986
},
1040
- },
1041
- },
1042
- },
1043
- setupMock: func(m *snmpmock.MockHandler) {
1044
- m.EXPECT().MaxOids().Return(10).AnyTimes()
1045
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1046
- m.EXPECT().BulkWalkAll("1.3.6.1.2.1.2.2").Return(
1047
- []gosnmp.SnmpPDU{
1048
- // Row 1
1049
- {
1050
- Name: "1.3.6.1.2.1.2.2.1.3.1",
1051
- Type: gosnmp.Integer,
1052
- Value: 6, // ethernetCsmacd
1053
- },
1054
- {
1055
- Name: "1.3.6.1.2.1.2.2.1.10.1",
1056
- Type: gosnmp.Counter32,
1057
- Value: uint(1000),
1058
- },
1059
- // Row 2
1060
- {
1061
- Name: "1.3.6.1.2.1.2.2.1.3.2",
1062
- Type: gosnmp.Integer,
1063
- Value: 1, // other
1064
- },
1065
- {
1066
- Name: "1.3.6.1.2.1.2.2.1.10.2",
1067
- Type: gosnmp.Counter32,
1068
- Value: uint(2000),
1069
- },
987
}, nil,
988
)
989
},
@@ -1076,50 +993,35 @@ func TestCollector_Collect(t *testing.T) {
993
DeviceMetadata: nil,
994
Metrics: []Metric{
995
{
1079
- Name: "ifInOctets",
1080
- Value: 1000,
1081
- Tags: map[string]string{"if_type": "ethernetCsmacd"},
1082
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1083
- IsTable: true,
1084
- },
1085
- {
1086
- Name: "ifInOctets",
1087
- Value: 2000,
1088
- Tags: map[string]string{"if_type": "other"},
1089
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1090
- IsTable: true,
996
+ Name: "ifAdminStatus",
997
+ Value: 1,
998
+ MetricType: "gauge",
999
+ Mappings: map[int64]string{
1000
+ 1: "up",
1001
+ 2: "down",
1002
+ 3: "testing",
1003
+ },
1004
},
1005
},
1006
},
1007
},
1008
expectedError: false,
1009
},
1097
- "table metrics with pattern matching tags": {
1010
+ "metric with string value and string to int mapping": {
1011
profiles: []*ddsnmp.Profile{
1012
{
1013
SourceFile: "test-profile.yaml",
1014
Definition: &ddprofiledefinition.ProfileDefinition{
1015
Metrics: []ddprofiledefinition.MetricsConfig{
1016
{
1104
- MIB: "MY-MIB",
1105
- Table: ddprofiledefinition.SymbolConfig{
1106
- OID: "1.3.6.1.4.1.1000.1",
1107
- Name: "myTable",
1108
- },
1109
- Symbols: []ddprofiledefinition.SymbolConfig{
1110
- {
1111
- OID: "1.3.6.1.4.1.1000.1.1.1",
1112
- Name: "myMetric",
1113
- },
1114
- },
1115
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1116
- {
1117
- Symbol: ddprofiledefinition.SymbolConfigCompat{
1118
- OID: "1.3.6.1.4.1.1000.1.1.2",
1119
- Name: "myDescription",
1120
- ExtractValueCompiled: mustCompileRegex(`Interface (\w+)`),
1121
- },
1122
- Tag: "port",
1017
+ Symbol: ddprofiledefinition.SymbolConfig{
1018
+ OID: "1.3.6.1.4.1.318.1.1.1.2.2.1.0",
1019
+ Name: "upsBasicBatteryStatus",
1020
+ Mapping: map[string]string{
1021
+ "batteryNormal": "0",
1022
+ "batteryLow": "1",
1023
+ "batteryDepleted": "2",
1024
+ "batteryCharging": "3",
1025
},
1026
},
1027
},
@@ -1129,18 +1031,14 @@ func TestCollector_Collect(t *testing.T) {
1031
},
1032
setupMock: func(m *snmpmock.MockHandler) {
1033
m.EXPECT().MaxOids().Return(10).AnyTimes()
1132
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1133
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
1134
- []gosnmp.SnmpPDU{
1135
- {
1136
- Name: "1.3.6.1.4.1.1000.1.1.1.1",
1137
- Type: gosnmp.Gauge32,
1138
- Value: uint(100),
1139
- },
1140
- {
1141
- Name: "1.3.6.1.4.1.1000.1.1.2.1",
1142
- Type: gosnmp.OctetString,
1143
- Value: []byte("Interface Gi0/1"),
1034
+ m.EXPECT().Get([]string{"1.3.6.1.4.1.318.1.1.1.2.2.1.0"}).Return(
1035
+ &gosnmp.SnmpPacket{
1036
+ Variables: []gosnmp.SnmpPDU{
1037
+ {
1038
+ Name: "1.3.6.1.4.1.318.1.1.1.2.2.1.0",
1039
+ Type: gosnmp.OctetString,
1040
+ Value: []byte("batteryLow"),
1041
+ },
1042
},
1043
}, nil,
1044
)
@@ -1151,2182 +1049,16 @@ func TestCollector_Collect(t *testing.T) {
1049
DeviceMetadata: nil,
1050
Metrics: []Metric{
1051
{
1154
- Name: "myMetric",
1155
- Value: 100,
1156
- Tags: map[string]string{"port": "Gi0"},
1157
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
1158
- IsTable: true,
1159
- },
1160
- },
1161
- },
1162
- },
1163
- expectedError: false,
1164
- },
1165
- "table metrics with static tags": {
1166
- profiles: []*ddsnmp.Profile{
1167
- {
1168
- SourceFile: "test-profile.yaml",
1169
- Definition: &ddprofiledefinition.ProfileDefinition{
1170
- Metrics: []ddprofiledefinition.MetricsConfig{
1171
- {
1172
- MIB: "MY-MIB",
1173
- Table: ddprofiledefinition.SymbolConfig{
1174
- OID: "1.3.6.1.4.1.1000.1",
1175
- Name: "myTable",
1176
- },
1177
- Symbols: []ddprofiledefinition.SymbolConfig{
1178
- {
1179
- OID: "1.3.6.1.4.1.1000.1.1.1",
1180
- Name: "myMetric",
1181
- },
1182
- },
1183
- StaticTags: []string{"table_type:performance", "source:snmp"},
1184
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1185
- {
1186
- Tag: "interface",
1187
- Symbol: ddprofiledefinition.SymbolConfigCompat{
1188
- OID: "1.3.6.1.4.1.1000.1.1.2",
1189
- Name: "ifName",
1190
- },
1191
- },
1192
- },
1193
- },
1194
- },
1195
- },
1196
- },
1197
- },
1198
- setupMock: func(m *snmpmock.MockHandler) {
1199
- m.EXPECT().MaxOids().Return(10).AnyTimes()
1200
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1201
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
1202
- []gosnmp.SnmpPDU{
1203
- {
1204
- Name: "1.3.6.1.4.1.1000.1.1.1.1",
1205
- Type: gosnmp.Gauge32,
1206
- Value: uint(100),
1207
- },
1208
- {
1209
- Name: "1.3.6.1.4.1.1000.1.1.2.1",
1210
- Type: gosnmp.OctetString,
1211
- Value: []byte("eth0"),
1212
- },
1213
- }, nil,
1214
- )
1215
- },
1216
- expectedResult: []*ProfileMetrics{
1217
- {
1218
- Source: "test-profile.yaml",
1219
- DeviceMetadata: nil,
1220
- Metrics: []Metric{
1221
- {
1222
- Name: "myMetric",
1223
- Value: 100,
1224
- StaticTags: map[string]string{
1225
- "table_type": "performance",
1226
- "source": "snmp",
1227
- },
1228
- Tags: map[string]string{
1229
- "interface": "eth0",
1230
- },
1231
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
1232
- IsTable: true,
1233
- },
1234
- },
1235
- },
1236
- },
1237
- expectedError: false,
1238
- },
1239
- "table metrics with missing tag values": {
1240
- profiles: []*ddsnmp.Profile{
1241
- {
1242
- SourceFile: "test-profile.yaml",
1243
- Definition: &ddprofiledefinition.ProfileDefinition{
1244
- Metrics: []ddprofiledefinition.MetricsConfig{
1245
- {
1246
- MIB: "IF-MIB",
1247
- Table: ddprofiledefinition.SymbolConfig{
1248
- OID: "1.3.6.1.2.1.2.2",
1249
- Name: "ifTable",
1250
- },
1251
- Symbols: []ddprofiledefinition.SymbolConfig{
1252
- {
1253
- OID: "1.3.6.1.2.1.2.2.1.10",
1254
- Name: "ifInOctets",
1255
- },
1256
- },
1257
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1258
- {
1259
- Tag: "interface",
1260
- Symbol: ddprofiledefinition.SymbolConfigCompat{
1261
- OID: "1.3.6.1.2.1.2.2.1.2",
1262
- Name: "ifDescr",
1263
- },
1264
- },
1265
- },
1266
- },
1267
- },
1268
- },
1269
- },
1270
- },
1271
- setupMock: func(m *snmpmock.MockHandler) {
1272
- m.EXPECT().MaxOids().Return(10).AnyTimes()
1273
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1274
- m.EXPECT().BulkWalkAll("1.3.6.1.2.1.2.2").Return(
1275
- []gosnmp.SnmpPDU{
1276
- // Row 1 - has both metric and tag
1277
- {
1278
- Name: "1.3.6.1.2.1.2.2.1.2.1",
1279
- Type: gosnmp.OctetString,
1280
- Value: []byte("eth0"),
1281
- },
1282
- {
1283
- Name: "1.3.6.1.2.1.2.2.1.10.1",
1284
- Type: gosnmp.Counter32,
1285
- Value: uint(1000),
1286
- },
1287
- // Row 2 - missing tag value
1288
- {
1289
- Name: "1.3.6.1.2.1.2.2.1.10.2",
1290
- Type: gosnmp.Counter32,
1291
- Value: uint(2000),
1292
- },
1293
- }, nil,
1294
- )
1295
- },
1296
- expectedResult: []*ProfileMetrics{
1297
- {
1298
- Source: "test-profile.yaml",
1299
- DeviceMetadata: nil,
1300
- Metrics: []Metric{
1301
- {
1302
- Name: "ifInOctets",
1303
- Value: 1000,
1304
- Tags: map[string]string{"interface": "eth0"},
1305
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1306
- IsTable: true,
1307
- },
1308
- {
1309
- Name: "ifInOctets",
1310
- Value: 2000,
1311
- Tags: nil, // No interface tag because it's missing
1312
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1313
- IsTable: true,
1314
- },
1315
- },
1316
- },
1317
- },
1318
- expectedError: false,
1319
- },
1320
-
1321
- "cross-table tags with same index": {
1322
- profiles: []*ddsnmp.Profile{
1323
- {
1324
- SourceFile: "test-profile.yaml",
1325
- Definition: &ddprofiledefinition.ProfileDefinition{
1326
- Metrics: []ddprofiledefinition.MetricsConfig{
1327
- {
1328
- MIB: "CISCO-IF-EXTENSION-MIB",
1329
- Table: ddprofiledefinition.SymbolConfig{
1330
- OID: "1.3.6.1.4.1.9.9.276.1.1.2",
1331
- Name: "cieIfInterfaceTable",
1332
- },
1333
- Symbols: []ddprofiledefinition.SymbolConfig{
1334
- {
1335
- OID: "1.3.6.1.4.1.9.9.276.1.1.2.1.1",
1336
- Name: "cieIfResetCount",
1337
- },
1338
- },
1339
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1340
- {
1341
- Symbol: ddprofiledefinition.SymbolConfigCompat{
1342
- OID: "1.3.6.1.2.1.31.1.1.1.1",
1343
- Name: "ifName",
1344
- },
1345
- Table: "ifXTable",
1346
- Tag: "interface",
1347
- },
1348
- },
1349
- },
1350
- {
1351
- MIB: "IF-MIB",
1352
- Table: ddprofiledefinition.SymbolConfig{
1353
- OID: "1.3.6.1.2.1.31.1.1",
1354
- Name: "ifXTable",
1355
- },
1356
- Symbols: []ddprofiledefinition.SymbolConfig{
1357
- // No symbols needed - this table is only used for cross-table tags
1358
- },
1359
- },
1360
- },
1361
- },
1362
- },
1363
- },
1364
- setupMock: func(m *snmpmock.MockHandler) {
1365
- m.EXPECT().MaxOids().Return(10).AnyTimes()
1366
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1367
-
1368
- // Walk cieIfInterfaceTable
1369
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.9.9.276.1.1.2").Return(
1370
- []gosnmp.SnmpPDU{
1371
- {
1372
- Name: "1.3.6.1.4.1.9.9.276.1.1.2.1.1.1",
1373
- Type: gosnmp.Counter32,
1374
- Value: uint(10),
1375
- },
1376
- {
1377
- Name: "1.3.6.1.4.1.9.9.276.1.1.2.1.1.2",
1378
- Type: gosnmp.Counter32,
1379
- Value: uint(20),
1380
- },
1381
- }, nil,
1382
- )
1383
-
1384
- // Walk ifXTable
1385
- m.EXPECT().BulkWalkAll("1.3.6.1.2.1.31.1.1").Return(
1386
- []gosnmp.SnmpPDU{
1387
- // ifName values that will be used as tags
1388
- {
1389
- Name: "1.3.6.1.2.1.31.1.1.1.1.1",
1390
- Type: gosnmp.OctetString,
1391
- Value: []byte("GigabitEthernet0/1"),
1392
- },
1393
- {
1394
- Name: "1.3.6.1.2.1.31.1.1.1.1.2",
1395
- Type: gosnmp.OctetString,
1396
- Value: []byte("GigabitEthernet0/2"),
1397
- },
1398
- }, nil,
1399
- )
1400
- },
1401
- expectedResult: []*ProfileMetrics{
1402
- {
1403
- Source: "test-profile.yaml",
1404
- DeviceMetadata: nil,
1405
- Metrics: []Metric{
1406
- {
1407
- Name: "cieIfResetCount",
1408
- Value: 10,
1409
- Tags: map[string]string{"interface": "GigabitEthernet0/1"},
1410
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1411
- IsTable: true,
1412
- },
1413
- {
1414
- Name: "cieIfResetCount",
1415
- Value: 20,
1416
- Tags: map[string]string{"interface": "GigabitEthernet0/2"},
1417
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1418
- IsTable: true,
1419
- },
1420
- },
1421
- },
1422
- },
1423
- expectedError: false,
1424
- },
1425
- "cross-table tags with missing referenced table": {
1426
- profiles: []*ddsnmp.Profile{
1427
- {
1428
- SourceFile: "test-profile.yaml",
1429
- Definition: &ddprofiledefinition.ProfileDefinition{
1430
- Metrics: []ddprofiledefinition.MetricsConfig{
1431
- {
1432
- MIB: "MY-MIB",
1433
- Table: ddprofiledefinition.SymbolConfig{
1434
- OID: "1.3.6.1.4.1.1000.1",
1435
- Name: "myTable",
1436
- },
1437
- Symbols: []ddprofiledefinition.SymbolConfig{
1438
- {
1439
- OID: "1.3.6.1.4.1.1000.1.1.1",
1440
- Name: "myMetric",
1441
- },
1442
- },
1443
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1444
- {
1445
- Symbol: ddprofiledefinition.SymbolConfigCompat{
1446
- OID: "1.3.6.1.2.1.31.1.1.1.1",
1447
- Name: "ifName",
1448
- },
1449
- Table: "ifXTable", // This table is not defined
1450
- Tag: "interface",
1451
- },
1452
- },
1453
- },
1454
- },
1455
- },
1456
- },
1457
- },
1458
- setupMock: func(m *snmpmock.MockHandler) {
1459
- m.EXPECT().MaxOids().Return(10).AnyTimes()
1460
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1461
-
1462
- // Walk myTable
1463
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
1464
- []gosnmp.SnmpPDU{
1465
- {
1466
- Name: "1.3.6.1.4.1.1000.1.1.1.1",
1467
- Type: gosnmp.Gauge32,
1468
- Value: uint(100),
1469
- },
1470
- }, nil,
1471
- )
1472
- },
1473
- expectedResult: []*ProfileMetrics{
1474
- {
1475
- Source: "test-profile.yaml",
1476
- DeviceMetadata: nil,
1477
- Metrics: []Metric{
1478
- {
1479
- Name: "myMetric",
1480
- Value: 100,
1481
- Tags: nil, // No cross-table tag because referenced table is missing
1482
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
1483
- IsTable: true,
1484
- },
1485
- },
1486
- },
1487
- },
1488
- expectedError: false,
1489
- },
1490
- "cross-table tags with missing value in referenced table": {
1491
- profiles: []*ddsnmp.Profile{
1492
- {
1493
- SourceFile: "test-profile.yaml",
1494
- Definition: &ddprofiledefinition.ProfileDefinition{
1495
- Metrics: []ddprofiledefinition.MetricsConfig{
1496
- {
1497
- MIB: "MY-MIB",
1498
- Table: ddprofiledefinition.SymbolConfig{
1499
- OID: "1.3.6.1.4.1.1000.1",
1500
- Name: "myTable",
1501
- },
1502
- Symbols: []ddprofiledefinition.SymbolConfig{
1503
- {
1504
- OID: "1.3.6.1.4.1.1000.1.1.1",
1505
- Name: "myMetric",
1506
- },
1507
- },
1508
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1509
- {
1510
- Symbol: ddprofiledefinition.SymbolConfigCompat{
1511
- OID: "1.3.6.1.2.1.31.1.1.1.1",
1512
- Name: "ifName",
1513
- },
1514
- Table: "ifXTable",
1515
- Tag: "interface",
1516
- },
1517
- },
1518
- },
1519
- {
1520
- MIB: "IF-MIB",
1521
- Table: ddprofiledefinition.SymbolConfig{
1522
- OID: "1.3.6.1.2.1.31.1.1",
1523
- Name: "ifXTable",
1524
- },
1525
- Symbols: []ddprofiledefinition.SymbolConfig{
1526
- // No symbols needed - this table is only used for cross-table tags
1527
- },
1528
- },
1529
- },
1530
- },
1531
- },
1532
- },
1533
- setupMock: func(m *snmpmock.MockHandler) {
1534
- m.EXPECT().MaxOids().Return(10).AnyTimes()
1535
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1536
-
1537
- // Walk myTable - has rows 1 and 2
1538
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
1539
- []gosnmp.SnmpPDU{
1540
- {
1541
- Name: "1.3.6.1.4.1.1000.1.1.1.1",
1542
- Type: gosnmp.Gauge32,
1543
- Value: uint(100),
1544
- },
1545
- {
1546
- Name: "1.3.6.1.4.1.1000.1.1.1.2",
1547
- Type: gosnmp.Gauge32,
1548
- Value: uint(200),
1549
- },
1550
- }, nil,
1551
- )
1552
-
1553
- // Walk ifXTable - only has ifName for row 1, missing row 2
1554
- m.EXPECT().BulkWalkAll("1.3.6.1.2.1.31.1.1").Return(
1555
- []gosnmp.SnmpPDU{
1556
- {
1557
- Name: "1.3.6.1.2.1.31.1.1.1.1.1",
1558
- Type: gosnmp.OctetString,
1559
- Value: []byte("eth0"),
1560
- },
1561
- // Missing ifName for index 2
1562
- }, nil,
1563
- )
1564
- },
1565
- expectedResult: []*ProfileMetrics{
1566
- {
1567
- Source: "test-profile.yaml",
1568
- DeviceMetadata: nil,
1569
- Metrics: []Metric{
1570
- {
1571
- Name: "myMetric",
1572
- Value: 100,
1573
- Tags: map[string]string{"interface": "eth0"},
1574
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
1575
- IsTable: true,
1576
- },
1577
- {
1578
- Name: "myMetric",
1579
- Value: 200,
1580
- Tags: nil, // No tag because ifName is missing for this index
1581
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
1582
- IsTable: true,
1583
- },
1584
- },
1585
- },
1586
- },
1587
- expectedError: false,
1588
- },
1589
- "cross-table tags with mapping": {
1590
- profiles: []*ddsnmp.Profile{
1591
- {
1592
- SourceFile: "test-profile.yaml",
1593
- Definition: &ddprofiledefinition.ProfileDefinition{
1594
- Metrics: []ddprofiledefinition.MetricsConfig{
1595
- {
1596
- MIB: "MY-MIB",
1597
- Table: ddprofiledefinition.SymbolConfig{
1598
- OID: "1.3.6.1.4.1.1000.1",
1599
- Name: "myTable",
1600
- },
1601
- Symbols: []ddprofiledefinition.SymbolConfig{
1602
- {
1603
- OID: "1.3.6.1.4.1.1000.1.1.1",
1604
- Name: "myMetric",
1605
- },
1606
- },
1607
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1608
- {
1609
- Symbol: ddprofiledefinition.SymbolConfigCompat{
1610
- OID: "1.3.6.1.2.1.2.2.1.3",
1611
- Name: "ifType",
1612
- },
1613
- Table: "ifTable",
1614
- Tag: "if_type",
1615
- Mapping: map[string]string{
1616
- "6": "ethernet",
1617
- "71": "wifi",
1618
- },
1619
- },
1620
- },
1621
- },
1622
- {
1623
- MIB: "IF-MIB",
1624
- Table: ddprofiledefinition.SymbolConfig{
1625
- OID: "1.3.6.1.2.1.2.2",
1626
- Name: "ifTable",
1627
- },
1628
- Symbols: []ddprofiledefinition.SymbolConfig{
1629
- {
1630
- OID: "1.3.6.1.2.1.2.2.1.10",
1631
- Name: "ifInOctets",
1632
- },
1633
- },
1634
- },
1635
- },
1636
- },
1637
- },
1638
- },
1639
- setupMock: func(m *snmpmock.MockHandler) {
1640
- m.EXPECT().MaxOids().Return(10).AnyTimes()
1641
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1642
-
1643
- // Walk myTable
1644
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
1645
- []gosnmp.SnmpPDU{
1646
- {
1647
- Name: "1.3.6.1.4.1.1000.1.1.1.1",
1648
- Type: gosnmp.Gauge32,
1649
- Value: uint(100),
1650
- },
1651
- {
1652
- Name: "1.3.6.1.4.1.1000.1.1.1.2",
1653
- Type: gosnmp.Gauge32,
1654
- Value: uint(200),
1655
- },
1656
- }, nil,
1657
- )
1658
-
1659
- // Walk ifTable
1660
- m.EXPECT().BulkWalkAll("1.3.6.1.2.1.2.2").Return(
1661
- []gosnmp.SnmpPDU{
1662
- {
1663
- Name: "1.3.6.1.2.1.2.2.1.3.1",
1664
- Type: gosnmp.Integer,
1665
- Value: 6, // ethernet
1666
- },
1667
- {
1668
- Name: "1.3.6.1.2.1.2.2.1.3.2",
1669
- Type: gosnmp.Integer,
1670
- Value: 71, // wifi
1671
- },
1672
- {
1673
- Name: "1.3.6.1.2.1.2.2.1.10.1",
1674
- Type: gosnmp.Counter32,
1675
- Value: uint(1000),
1676
- },
1677
- }, nil,
1678
- )
1679
- },
1680
- expectedResult: []*ProfileMetrics{
1681
- {
1682
- Source: "test-profile.yaml",
1683
- DeviceMetadata: nil,
1684
- Metrics: []Metric{
1685
- {
1686
- Name: "myMetric",
1687
- Value: 100,
1688
- Tags: map[string]string{"if_type": "ethernet"},
1689
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
1690
- IsTable: true,
1691
- },
1692
- {
1693
- Name: "myMetric",
1694
- Value: 200,
1695
- Tags: map[string]string{"if_type": "wifi"},
1696
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
1697
- IsTable: true,
1698
- },
1699
- {
1700
- Name: "ifInOctets",
1701
- Value: 1000,
1702
- Tags: nil,
1703
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1704
- IsTable: true,
1705
- },
1706
- },
1707
- },
1708
- },
1709
- expectedError: false,
1710
- },
1711
- "multiple cross-table tags from different tables": {
1712
- profiles: []*ddsnmp.Profile{
1713
- {
1714
- SourceFile: "test-profile.yaml",
1715
- Definition: &ddprofiledefinition.ProfileDefinition{
1716
- Metrics: []ddprofiledefinition.MetricsConfig{
1717
- {
1718
- MIB: "MY-MIB",
1719
- Table: ddprofiledefinition.SymbolConfig{
1720
- OID: "1.3.6.1.4.1.1000.1",
1721
- Name: "myTable",
1722
- },
1723
- Symbols: []ddprofiledefinition.SymbolConfig{
1724
- {
1725
- OID: "1.3.6.1.4.1.1000.1.1.1",
1726
- Name: "myMetric",
1727
- },
1728
- },
1729
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1730
- {
1731
- Symbol: ddprofiledefinition.SymbolConfigCompat{
1732
- OID: "1.3.6.1.2.1.31.1.1.1.1",
1733
- Name: "ifName",
1734
- },
1735
- Table: "ifXTable",
1736
- Tag: "interface",
1737
- },
1738
- {
1739
- Symbol: ddprofiledefinition.SymbolConfigCompat{
1740
- OID: "1.3.6.1.2.1.2.2.1.2",
1741
- Name: "ifDescr",
1742
- },
1743
- Table: "ifTable",
1744
- Tag: "description",
1745
- },
1746
- },
1747
- },
1748
- {
1749
- MIB: "IF-MIB",
1750
- Table: ddprofiledefinition.SymbolConfig{
1751
- OID: "1.3.6.1.2.1.31.1.1",
1752
- Name: "ifXTable",
1753
- },
1754
- Symbols: []ddprofiledefinition.SymbolConfig{
1755
- // No symbols needed - this table is only used for cross-table tags
1756
- },
1757
- },
1758
- {
1759
- MIB: "IF-MIB",
1760
- Table: ddprofiledefinition.SymbolConfig{
1761
- OID: "1.3.6.1.2.1.2.2",
1762
- Name: "ifTable",
1763
- },
1764
- Symbols: []ddprofiledefinition.SymbolConfig{
1765
- // No symbols needed - this table is only used for cross-table tags
1766
- },
1767
- },
1768
- },
1769
- },
1770
- },
1771
- },
1772
- setupMock: func(m *snmpmock.MockHandler) {
1773
- m.EXPECT().MaxOids().Return(10).AnyTimes()
1774
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1775
-
1776
- // Walk myTable
1777
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
1778
- []gosnmp.SnmpPDU{
1779
- {
1780
- Name: "1.3.6.1.4.1.1000.1.1.1.1",
1781
- Type: gosnmp.Gauge32,
1782
- Value: uint(100),
1783
- },
1784
- }, nil,
1785
- )
1786
-
1787
- // Walk ifXTable
1788
- m.EXPECT().BulkWalkAll("1.3.6.1.2.1.31.1.1").Return(
1789
- []gosnmp.SnmpPDU{
1790
- {
1791
- Name: "1.3.6.1.2.1.31.1.1.1.1.1",
1792
- Type: gosnmp.OctetString,
1793
- Value: []byte("GigabitEthernet0/1"),
1794
- },
1795
- }, nil,
1796
- )
1797
-
1798
- // Walk ifTable
1799
- m.EXPECT().BulkWalkAll("1.3.6.1.2.1.2.2").Return(
1800
- []gosnmp.SnmpPDU{
1801
- {
1802
- Name: "1.3.6.1.2.1.2.2.1.2.1",
1803
- Type: gosnmp.OctetString,
1804
- Value: []byte("GigE0/1"),
1805
- },
1806
- }, nil,
1807
- )
1808
- },
1809
- expectedResult: []*ProfileMetrics{
1810
- {
1811
- Source: "test-profile.yaml",
1812
- DeviceMetadata: nil,
1813
- Metrics: []Metric{
1814
- {
1815
- Name: "myMetric",
1816
- Value: 100,
1817
- Tags: map[string]string{
1818
- "interface": "GigabitEthernet0/1",
1819
- "description": "GigE0/1",
1820
- },
1821
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
1822
- IsTable: true,
1823
- },
1824
- },
1825
- },
1826
- },
1827
- expectedError: false,
1828
- },
1829
-
1830
- "index-based tags with single position": {
1831
- profiles: []*ddsnmp.Profile{
1832
- {
1833
- SourceFile: "test-profile.yaml",
1834
- Definition: &ddprofiledefinition.ProfileDefinition{
1835
- Metrics: []ddprofiledefinition.MetricsConfig{
1836
- {
1837
- MIB: "IP-MIB",
1838
- Table: ddprofiledefinition.SymbolConfig{
1839
- OID: "1.3.6.1.2.1.4.31.1",
1840
- Name: "ipSystemStatsTable",
1841
- },
1842
- Symbols: []ddprofiledefinition.SymbolConfig{
1843
- {
1844
- OID: "1.3.6.1.2.1.4.31.1.1.4",
1845
- Name: "ipSystemStatsHCInReceives",
1846
- },
1847
- },
1848
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1849
- {
1850
- Index: 1,
1851
- Tag: "ipversion",
1852
- Mapping: map[string]string{
1853
- "0": "unknown",
1854
- "1": "ipv4",
1855
- "2": "ipv6",
1856
- "3": "ipv4z",
1857
- "4": "ipv6z",
1858
- "16": "dns",
1859
- },
1860
- },
1861
- },
1862
- },
1863
- },
1864
- },
1865
- },
1866
- },
1867
- setupMock: func(m *snmpmock.MockHandler) {
1868
- m.EXPECT().MaxOids().Return(10).AnyTimes()
1869
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1870
-
1871
- m.EXPECT().BulkWalkAll("1.3.6.1.2.1.4.31.1").Return(
1872
- []gosnmp.SnmpPDU{
1873
- // IPv4 row
1874
- {
1875
- Name: "1.3.6.1.2.1.4.31.1.1.4.1",
1876
- Type: gosnmp.Counter64,
1877
- Value: uint64(1000),
1878
- },
1879
- // IPv6 row
1880
- {
1881
- Name: "1.3.6.1.2.1.4.31.1.1.4.2",
1882
- Type: gosnmp.Counter64,
1883
- Value: uint64(2000),
1884
- },
1885
- }, nil,
1886
- )
1887
- },
1888
- expectedResult: []*ProfileMetrics{
1889
- {
1890
- Source: "test-profile.yaml",
1891
- DeviceMetadata: nil,
1892
- Metrics: []Metric{
1893
- {
1894
- Name: "ipSystemStatsHCInReceives",
1895
- Value: 1000,
1896
- Tags: map[string]string{"ipversion": "ipv4"},
1897
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1898
- IsTable: true,
1899
- },
1900
- {
1901
- Name: "ipSystemStatsHCInReceives",
1902
- Value: 2000,
1903
- Tags: map[string]string{"ipversion": "ipv6"},
1904
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1905
- IsTable: true,
1906
- },
1907
- },
1908
- },
1909
- },
1910
- expectedError: false,
1911
- },
1912
- "index-based tags with multiple positions": {
1913
- profiles: []*ddsnmp.Profile{
1914
- {
1915
- SourceFile: "test-profile.yaml",
1916
- Definition: &ddprofiledefinition.ProfileDefinition{
1917
- Metrics: []ddprofiledefinition.MetricsConfig{
1918
- {
1919
- MIB: "CISCO-FIREWALL-MIB",
1920
- Table: ddprofiledefinition.SymbolConfig{
1921
- OID: "1.3.6.1.4.1.9.9.147.1.2.2.2",
1922
- Name: "cfwConnectionStatTable",
1923
- },
1924
- Symbols: []ddprofiledefinition.SymbolConfig{
1925
- {
1926
- OID: "1.3.6.1.4.1.9.9.147.1.2.2.2.1.5",
1927
- Name: "cfwConnectionStatValue",
1928
- },
1929
- },
1930
- MetricTags: []ddprofiledefinition.MetricTagConfig{
1931
- {
1932
- Index: 1,
1933
- Tag: "service_type",
1934
- },
1935
- {
1936
- Index: 2,
1937
- Tag: "stat_type",
1938
- },
1939
- },
1940
- },
1941
- },
1942
- },
1943
- },
1944
- },
1945
- setupMock: func(m *snmpmock.MockHandler) {
1946
- m.EXPECT().MaxOids().Return(10).AnyTimes()
1947
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
1948
-
1949
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.9.9.147.1.2.2.2").Return(
1950
- []gosnmp.SnmpPDU{
1951
- // Index: 20.2 (service_type=20, stat_type=2)
1952
- {
1953
- Name: "1.3.6.1.4.1.9.9.147.1.2.2.2.1.5.20.2",
1954
- Type: gosnmp.Counter64,
1955
- Value: uint64(4087850099),
1956
- },
1957
- // Index: 21.3 (service_type=21, stat_type=3)
1958
- {
1959
- Name: "1.3.6.1.4.1.9.9.147.1.2.2.2.1.5.21.3",
1960
- Type: gosnmp.Counter64,
1961
- Value: uint64(5000000),
1962
- },
1963
- }, nil,
1964
- )
1965
- },
1966
- expectedResult: []*ProfileMetrics{
1967
- {
1968
- Source: "test-profile.yaml",
1969
- DeviceMetadata: nil,
1970
- Metrics: []Metric{
1971
- {
1972
- Name: "cfwConnectionStatValue",
1973
- Value: 4087850099,
1974
- Tags: map[string]string{
1975
- "service_type": "20",
1976
- "stat_type": "2",
1977
- },
1978
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1979
- IsTable: true,
1980
- },
1981
- {
1982
- Name: "cfwConnectionStatValue",
1983
- Value: 5000000,
1984
- Tags: map[string]string{
1985
- "service_type": "21",
1986
- "stat_type": "3",
1987
- },
1988
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
1989
- IsTable: true,
1990
- },
1991
- },
1992
- },
1993
- },
1994
- expectedError: false,
1995
- },
1996
- "index-based tags with missing mapping value": {
1997
- profiles: []*ddsnmp.Profile{
1998
- {
1999
- SourceFile: "test-profile.yaml",
2000
- Definition: &ddprofiledefinition.ProfileDefinition{
2001
- Metrics: []ddprofiledefinition.MetricsConfig{
2002
- {
2003
- MIB: "IP-MIB",
2004
- Table: ddprofiledefinition.SymbolConfig{
2005
- OID: "1.3.6.1.2.1.4.31.1",
2006
- Name: "ipSystemStatsTable",
2007
- },
2008
- Symbols: []ddprofiledefinition.SymbolConfig{
2009
- {
2010
- OID: "1.3.6.1.2.1.4.31.1.1.4",
2011
- Name: "ipSystemStatsHCInReceives",
2012
- },
2013
- },
2014
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2015
- {
2016
- Index: 1,
2017
- Tag: "ipversion",
2018
- Mapping: map[string]string{
2019
- "1": "ipv4",
2020
- "2": "ipv6",
2021
- // Missing mapping for "99"
2022
- },
2023
- },
2024
- },
2025
- },
2026
- },
2027
- },
2028
- },
2029
- },
2030
- setupMock: func(m *snmpmock.MockHandler) {
2031
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2032
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2033
-
2034
- m.EXPECT().BulkWalkAll("1.3.6.1.2.1.4.31.1").Return(
2035
- []gosnmp.SnmpPDU{
2036
- // Index 99 - no mapping defined
2037
- {
2038
- Name: "1.3.6.1.2.1.4.31.1.1.4.99",
2039
- Type: gosnmp.Counter64,
2040
- Value: uint64(3000),
2041
- },
2042
- }, nil,
2043
- )
2044
- },
2045
- expectedResult: []*ProfileMetrics{
2046
- {
2047
- Source: "test-profile.yaml",
2048
- DeviceMetadata: nil,
2049
- Metrics: []Metric{
2050
- {
2051
- Name: "ipSystemStatsHCInReceives",
2052
- Value: 3000,
2053
- Tags: map[string]string{"ipversion": "99"}, // Raw value when no mapping exists
2054
- MetricType: ddprofiledefinition.ProfileMetricTypeRate,
2055
- IsTable: true,
2056
- },
2057
- },
2058
- },
2059
- },
2060
- expectedError: false,
2061
- },
2062
- "index-based tags with complex multi-part index": {
2063
- profiles: []*ddsnmp.Profile{
2064
- {
2065
- SourceFile: "test-profile.yaml",
2066
- Definition: &ddprofiledefinition.ProfileDefinition{
2067
- Metrics: []ddprofiledefinition.MetricsConfig{
2068
- {
2069
- MIB: "MY-MIB",
2070
- Table: ddprofiledefinition.SymbolConfig{
2071
- OID: "1.3.6.1.4.1.1000.1",
2072
- Name: "myComplexTable",
2073
- },
2074
- Symbols: []ddprofiledefinition.SymbolConfig{
2075
- {
2076
- OID: "1.3.6.1.4.1.1000.1.1.1",
2077
- Name: "myMetric",
2078
- },
2079
- },
2080
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2081
- {
2082
- Index: 1,
2083
- Tag: "first",
2084
- },
2085
- {
2086
- Index: 3,
2087
- Tag: "third",
2088
- },
2089
- {
2090
- Index: 5,
2091
- Tag: "fifth",
2092
- },
2093
- },
2094
- },
2095
- },
2096
- },
2097
- },
2098
- },
2099
- setupMock: func(m *snmpmock.MockHandler) {
2100
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2101
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2102
-
2103
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
2104
- []gosnmp.SnmpPDU{
2105
- // Complex index: 10.20.30.40.50
2106
- {
2107
- Name: "1.3.6.1.4.1.1000.1.1.1.10.20.30.40.50",
2108
- Type: gosnmp.Gauge32,
2109
- Value: uint(100),
2110
- },
2111
- }, nil,
2112
- )
2113
- },
2114
- expectedResult: []*ProfileMetrics{
2115
- {
2116
- Source: "test-profile.yaml",
2117
- DeviceMetadata: nil,
2118
- Metrics: []Metric{
2119
- {
2120
- Name: "myMetric",
2121
- Value: 100,
2122
- Tags: map[string]string{
2123
- "first": "10",
2124
- "third": "30",
2125
- "fifth": "50",
2126
- },
2127
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2128
- IsTable: true,
2129
- },
2130
- },
2131
- },
2132
- },
2133
- expectedError: false,
2134
- },
2135
- "index-based tags with out-of-range position": {
2136
- profiles: []*ddsnmp.Profile{
2137
- {
2138
- SourceFile: "test-profile.yaml",
2139
- Definition: &ddprofiledefinition.ProfileDefinition{
2140
- Metrics: []ddprofiledefinition.MetricsConfig{
2141
- {
2142
- MIB: "MY-MIB",
2143
- Table: ddprofiledefinition.SymbolConfig{
2144
- OID: "1.3.6.1.4.1.1000.1",
2145
- Name: "myTable",
2146
- },
2147
- Symbols: []ddprofiledefinition.SymbolConfig{
2148
- {
2149
- OID: "1.3.6.1.4.1.1000.1.1.1",
2150
- Name: "myMetric",
2151
- },
2152
- },
2153
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2154
- {
2155
- Index: 1,
2156
- Tag: "first",
2157
- },
2158
- {
2159
- Index: 5, // This position doesn't exist in index "1.2"
2160
- Tag: "fifth",
2161
- },
2162
- },
2163
- },
2164
- },
2165
- },
2166
- },
2167
- },
2168
- setupMock: func(m *snmpmock.MockHandler) {
2169
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2170
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2171
-
2172
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
2173
- []gosnmp.SnmpPDU{
2174
- // Simple index: 1.2
2175
- {
2176
- Name: "1.3.6.1.4.1.1000.1.1.1.1.2",
2177
- Type: gosnmp.Gauge32,
2178
- Value: uint(100),
2179
- },
2180
- }, nil,
2181
- )
2182
- },
2183
- expectedResult: []*ProfileMetrics{
2184
- {
2185
- Source: "test-profile.yaml",
2186
- DeviceMetadata: nil,
2187
- Metrics: []Metric{
2188
- {
2189
- Name: "myMetric",
2190
- Value: 100,
2191
- Tags: map[string]string{
2192
- "first": "1",
2193
- // "fifth" tag is not present because position 5 doesn't exist
2194
- },
2195
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2196
- IsTable: true,
2197
- },
2198
- },
2199
- },
2200
- },
2201
- expectedError: false,
2202
- },
2203
- "index-based tags combined with symbol tags": {
2204
- profiles: []*ddsnmp.Profile{
2205
- {
2206
- SourceFile: "test-profile.yaml",
2207
- Definition: &ddprofiledefinition.ProfileDefinition{
2208
- Metrics: []ddprofiledefinition.MetricsConfig{
2209
- {
2210
- MIB: "MY-MIB",
2211
- Table: ddprofiledefinition.SymbolConfig{
2212
- OID: "1.3.6.1.4.1.1000.1",
2213
- Name: "myTable",
2214
- },
2215
- Symbols: []ddprofiledefinition.SymbolConfig{
2216
- {
2217
- OID: "1.3.6.1.4.1.1000.1.1.1",
2218
- Name: "myMetric",
2219
- },
2220
- },
2221
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2222
- {
2223
- Index: 1,
2224
- Tag: "index_tag",
2225
- },
2226
- {
2227
- Tag: "name_tag",
2228
- Symbol: ddprofiledefinition.SymbolConfigCompat{
2229
- OID: "1.3.6.1.4.1.1000.1.1.2",
2230
- Name: "myName",
2231
- },
2232
- },
2233
- },
2234
- },
2235
- },
2236
- },
2237
- },
2238
- },
2239
- setupMock: func(m *snmpmock.MockHandler) {
2240
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2241
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2242
-
2243
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
2244
- []gosnmp.SnmpPDU{
2245
- {
2246
- Name: "1.3.6.1.4.1.1000.1.1.1.5",
2247
- Type: gosnmp.Gauge32,
2248
- Value: uint(100),
2249
- },
2250
- {
2251
- Name: "1.3.6.1.4.1.1000.1.1.2.5",
2252
- Type: gosnmp.OctetString,
2253
- Value: []byte("device-5"),
2254
- },
2255
- }, nil,
2256
- )
2257
- },
2258
- expectedResult: []*ProfileMetrics{
2259
- {
2260
- Source: "test-profile.yaml",
2261
- DeviceMetadata: nil,
2262
- Metrics: []Metric{
2263
- {
2264
- Name: "myMetric",
2265
- Value: 100,
2266
- Tags: map[string]string{
2267
- "index_tag": "5",
2268
- "name_tag": "device-5",
2269
- },
2270
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2271
- IsTable: true,
2272
- },
2273
- },
2274
- },
2275
- },
2276
- expectedError: false,
2277
- },
2278
- "index-based tags with default tag name": {
2279
- profiles: []*ddsnmp.Profile{
2280
- {
2281
- SourceFile: "test-profile.yaml",
2282
- Definition: &ddprofiledefinition.ProfileDefinition{
2283
- Metrics: []ddprofiledefinition.MetricsConfig{
2284
- {
2285
- MIB: "MY-MIB",
2286
- Table: ddprofiledefinition.SymbolConfig{
2287
- OID: "1.3.6.1.4.1.1000.1",
2288
- Name: "myTable",
2289
- },
2290
- Symbols: []ddprofiledefinition.SymbolConfig{
2291
- {
2292
- OID: "1.3.6.1.4.1.1000.1.1.1",
2293
- Name: "myMetric",
2294
- },
2295
- },
2296
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2297
- {
2298
- Index: 2,
2299
- // No tag name specified, should default to "index2"
2300
- },
2301
- },
2302
- },
2303
- },
2304
- },
2305
- },
2306
- },
2307
- setupMock: func(m *snmpmock.MockHandler) {
2308
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2309
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2310
-
2311
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
2312
- []gosnmp.SnmpPDU{
2313
- {
2314
- Name: "1.3.6.1.4.1.1000.1.1.1.10.20",
2315
- Type: gosnmp.Gauge32,
2316
- Value: uint(100),
2317
- },
2318
- }, nil,
2319
- )
2320
- },
2321
- expectedResult: []*ProfileMetrics{
2322
- {
2323
- Source: "test-profile.yaml",
2324
- DeviceMetadata: nil,
2325
- Metrics: []Metric{
2326
- {
2327
- Name: "myMetric",
2328
- Value: 100,
2329
- Tags: map[string]string{"index2": "20"},
2330
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2331
- IsTable: true,
2332
- },
2333
- },
2334
- },
2335
- },
2336
- expectedError: false,
2337
- },
2338
- "index-based tags with single component index": {
2339
- profiles: []*ddsnmp.Profile{
2340
- {
2341
- SourceFile: "test-profile.yaml",
2342
- Definition: &ddprofiledefinition.ProfileDefinition{
2343
- Metrics: []ddprofiledefinition.MetricsConfig{
2344
- {
2345
- MIB: "MY-MIB",
2346
- Table: ddprofiledefinition.SymbolConfig{
2347
- OID: "1.3.6.1.4.1.1000.1",
2348
- Name: "myTable",
2349
- },
2350
- Symbols: []ddprofiledefinition.SymbolConfig{
2351
- {
2352
- OID: "1.3.6.1.4.1.1000.1.1.1",
2353
- Name: "myMetric",
2354
- },
2355
- },
2356
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2357
- {
2358
- Index: 1,
2359
- Tag: "id",
2360
- },
2361
- },
2362
- },
2363
- },
2364
- },
2365
- },
2366
- },
2367
- setupMock: func(m *snmpmock.MockHandler) {
2368
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2369
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2370
-
2371
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
2372
- []gosnmp.SnmpPDU{
2373
- // Single component index: just "42"
2374
- {
2375
- Name: "1.3.6.1.4.1.1000.1.1.1.42",
2376
- Type: gosnmp.Gauge32,
2377
- Value: uint(100),
2378
- },
2379
- }, nil,
2380
- )
2381
- },
2382
- expectedResult: []*ProfileMetrics{
2383
- {
2384
- Source: "test-profile.yaml",
2385
- DeviceMetadata: nil,
2386
- Metrics: []Metric{
2387
- {
2388
- Name: "myMetric",
2389
- Value: 100,
2390
- Tags: map[string]string{"id": "42"},
2391
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2392
- IsTable: true,
2393
- },
2394
- },
2395
- },
2396
- },
2397
- expectedError: false,
2398
- },
2399
-
2400
- "cross-table tags with index transformation": {
2401
- profiles: []*ddsnmp.Profile{
2402
- {
2403
- SourceFile: "test-profile.yaml",
2404
- Definition: &ddprofiledefinition.ProfileDefinition{
2405
- Metrics: []ddprofiledefinition.MetricsConfig{
2406
- {
2407
- MIB: "CPI-UNITY-MIB",
2408
- Table: ddprofiledefinition.SymbolConfig{
2409
- OID: "1.3.6.1.4.1.30932.1.10.1.3.110",
2410
- Name: "cpiPduBranchTable",
2411
- },
2412
- Symbols: []ddprofiledefinition.SymbolConfig{
2413
- {
2414
- OID: "1.3.6.1.4.1.30932.1.10.1.3.110.1.3",
2415
- Name: "cpiPduBranchCurrent",
2416
- },
2417
- },
2418
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2419
- {
2420
- Symbol: ddprofiledefinition.SymbolConfigCompat{
2421
- OID: "1.3.6.1.4.1.30932.1.10.1.2.10.1.3",
2422
- Name: "cpiPduName",
2423
- },
2424
- Table: "cpiPduTable",
2425
- IndexTransform: []ddprofiledefinition.MetricIndexTransform{
2426
- {Start: 1, End: 7},
2427
- },
2428
- Tag: "pdu_name",
2429
- },
2430
- },
2431
- },
2432
- {
2433
- MIB: "CPI-UNITY-MIB",
2434
- Table: ddprofiledefinition.SymbolConfig{
2435
- OID: "1.3.6.1.4.1.30932.1.10.1.2.10",
2436
- Name: "cpiPduTable",
2437
- },
2438
- Symbols: []ddprofiledefinition.SymbolConfig{
2439
- // No symbols needed - only used for cross-table reference
2440
- },
2441
- },
2442
- },
2443
- },
2444
- },
2445
- },
2446
- setupMock: func(m *snmpmock.MockHandler) {
2447
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2448
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2449
-
2450
- // Walk cpiPduBranchTable
2451
- // Index structure: <branch_id>.<mac_address>
2452
- // Example: 1.6.0.36.155.53.3.246
2453
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.30932.1.10.1.3.110").Return(
2454
- []gosnmp.SnmpPDU{
2455
- {
2456
- Name: "1.3.6.1.4.1.30932.1.10.1.3.110.1.3.1.6.0.36.155.53.3.246",
2457
- Type: gosnmp.Gauge32,
2458
- Value: uint(150), // 1.5 Amps
2459
- },
2460
- {
2461
- Name: "1.3.6.1.4.1.30932.1.10.1.3.110.1.3.2.6.0.36.155.53.3.247",
2462
- Type: gosnmp.Gauge32,
2463
- Value: uint(200), // 2.0 Amps
2464
- },
2465
- }, nil,
2466
- )
2467
-
2468
- // Walk cpiPduTable
2469
- // Index structure: <mac_address> only
2470
- // Example: 6.0.36.155.53.3.246
2471
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.30932.1.10.1.2.10").Return(
2472
- []gosnmp.SnmpPDU{
2473
- {
2474
- Name: "1.3.6.1.4.1.30932.1.10.1.2.10.1.3.6.0.36.155.53.3.246",
2475
- Type: gosnmp.OctetString,
2476
- Value: []byte("PDU-A"),
2477
- },
2478
- {
2479
- Name: "1.3.6.1.4.1.30932.1.10.1.2.10.1.3.6.0.36.155.53.3.247",
2480
- Type: gosnmp.OctetString,
2481
- Value: []byte("PDU-B"),
2482
- },
2483
- }, nil,
2484
- )
2485
- },
2486
- expectedResult: []*ProfileMetrics{
2487
- {
2488
- Source: "test-profile.yaml",
2489
- DeviceMetadata: nil,
2490
- Metrics: []Metric{
2491
- {
2492
- Name: "cpiPduBranchCurrent",
2493
- Value: 150,
2494
- Tags: map[string]string{"pdu_name": "PDU-A"},
2495
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2496
- IsTable: true,
2497
- },
2498
- {
2499
- Name: "cpiPduBranchCurrent",
2500
- Value: 200,
2501
- Tags: map[string]string{"pdu_name": "PDU-B"},
2502
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2503
- IsTable: true,
2504
- },
2505
- },
2506
- },
2507
- },
2508
- expectedError: false,
2509
- },
2510
- "cross-table tags with multiple index transformations": {
2511
- profiles: []*ddsnmp.Profile{
2512
- {
2513
- SourceFile: "test-profile.yaml",
2514
- Definition: &ddprofiledefinition.ProfileDefinition{
2515
- Metrics: []ddprofiledefinition.MetricsConfig{
2516
- {
2517
- MIB: "MY-MIB",
2518
- Table: ddprofiledefinition.SymbolConfig{
2519
- OID: "1.3.6.1.4.1.1000.1",
2520
- Name: "myComplexTable",
2521
- },
2522
- Symbols: []ddprofiledefinition.SymbolConfig{
2523
- {
2524
- OID: "1.3.6.1.4.1.1000.1.1.1",
2525
- Name: "myMetric",
2526
- },
2527
- },
2528
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2529
- {
2530
- Symbol: ddprofiledefinition.SymbolConfigCompat{
2531
- OID: "1.3.6.1.4.1.1000.2.1.1",
2532
- Name: "refName",
2533
- },
2534
- Table: "refTable",
2535
- IndexTransform: []ddprofiledefinition.MetricIndexTransform{
2536
- {Start: 0, End: 1},
2537
- {Start: 3, End: 5},
2538
- },
2539
- Tag: "ref_name",
2540
- },
2541
- },
2542
- },
2543
- {
2544
- MIB: "MY-MIB",
2545
- Table: ddprofiledefinition.SymbolConfig{
2546
- OID: "1.3.6.1.4.1.1000.2",
2547
- Name: "refTable",
2548
- },
2549
- Symbols: []ddprofiledefinition.SymbolConfig{
2550
- // No symbols needed
2551
- },
2552
- },
2553
- },
2554
- },
2555
- },
2556
- },
2557
- setupMock: func(m *snmpmock.MockHandler) {
2558
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2559
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2560
-
2561
- // Walk myComplexTable
2562
- // Index: 1.2.3.4.5.6.7
2563
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
2564
- []gosnmp.SnmpPDU{
2565
- {
2566
- Name: "1.3.6.1.4.1.1000.1.1.1.1.2.3.4.5.6.7",
2567
- Type: gosnmp.Gauge32,
2568
- Value: uint(100),
2569
- },
2570
- }, nil,
2571
- )
2572
-
2573
- // Walk refTable
2574
- // Expected transformed index: 1.2.4.5.6 (positions 1-2 and 4-6)
2575
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.2").Return(
2576
- []gosnmp.SnmpPDU{
2577
- {
2578
- Name: "1.3.6.1.4.1.1000.2.1.1.1.2.4.5.6",
2579
- Type: gosnmp.OctetString,
2580
- Value: []byte("Complex-Ref"),
2581
- },
2582
- }, nil,
2583
- )
2584
- },
2585
- expectedResult: []*ProfileMetrics{
2586
- {
2587
- Source: "test-profile.yaml",
2588
- DeviceMetadata: nil,
2589
- Metrics: []Metric{
2590
- {
2591
- Name: "myMetric",
2592
- Value: 100,
2593
- Tags: map[string]string{"ref_name": "Complex-Ref"},
2594
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2595
- IsTable: true,
2596
- },
2597
- },
2598
- },
2599
- },
2600
- expectedError: false,
2601
- },
2602
- "cross-table tags with index transformation no match": {
2603
- profiles: []*ddsnmp.Profile{
2604
- {
2605
- SourceFile: "test-profile.yaml",
2606
- Definition: &ddprofiledefinition.ProfileDefinition{
2607
- Metrics: []ddprofiledefinition.MetricsConfig{
2608
- {
2609
- MIB: "MY-MIB",
2610
- Table: ddprofiledefinition.SymbolConfig{
2611
- OID: "1.3.6.1.4.1.1000.1",
2612
- Name: "myTable",
2613
- },
2614
- Symbols: []ddprofiledefinition.SymbolConfig{
2615
- {
2616
- OID: "1.3.6.1.4.1.1000.1.1.1",
2617
- Name: "myMetric",
2618
- },
2619
- },
2620
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2621
- {
2622
- Symbol: ddprofiledefinition.SymbolConfigCompat{
2623
- OID: "1.3.6.1.4.1.1000.2.1.1",
2624
- Name: "refName",
2625
- },
2626
- Table: "refTable",
2627
- IndexTransform: []ddprofiledefinition.MetricIndexTransform{
2628
- {Start: 2, End: 4},
2629
- },
2630
- Tag: "ref_name",
2631
- },
2632
- },
2633
- },
2634
- {
2635
- MIB: "MY-MIB",
2636
- Table: ddprofiledefinition.SymbolConfig{
2637
- OID: "1.3.6.1.4.1.1000.2",
2638
- Name: "refTable",
2639
- },
2640
- Symbols: []ddprofiledefinition.SymbolConfig{
2641
- // No symbols needed
2642
- },
2643
- },
2644
- },
2645
- },
2646
- },
2647
- },
2648
- setupMock: func(m *snmpmock.MockHandler) {
2649
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2650
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2651
-
2652
- // Walk myTable
2653
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
2654
- []gosnmp.SnmpPDU{
2655
- {
2656
- Name: "1.3.6.1.4.1.1000.1.1.1.1.2.3",
2657
- Type: gosnmp.Gauge32,
2658
- Value: uint(100),
2659
- },
2660
- }, nil,
2661
- )
2662
-
2663
- // Walk refTable - but it doesn't have the transformed index
2664
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.2").Return(
2665
- []gosnmp.SnmpPDU{
2666
- {
2667
- Name: "1.3.6.1.4.1.1000.2.1.1.9.9.9", // Different index
2668
- Type: gosnmp.OctetString,
2669
- Value: []byte("Other-Ref"),
2670
- },
2671
- }, nil,
2672
- )
2673
- },
2674
- expectedResult: []*ProfileMetrics{
2675
- {
2676
- Source: "test-profile.yaml",
2677
- DeviceMetadata: nil,
2678
- Metrics: []Metric{
2679
- {
2680
- Name: "myMetric",
2681
- Value: 100,
2682
- Tags: nil, // No tag because transformed index not found
2683
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2684
- IsTable: true,
2685
- },
2686
- },
2687
- },
2688
- },
2689
- expectedError: false,
2690
- },
2691
- "cross-table tags with invalid index transformation": {
2692
- profiles: []*ddsnmp.Profile{
2693
- {
2694
- SourceFile: "test-profile.yaml",
2695
- Definition: &ddprofiledefinition.ProfileDefinition{
2696
- Metrics: []ddprofiledefinition.MetricsConfig{
2697
- {
2698
- MIB: "MY-MIB",
2699
- Table: ddprofiledefinition.SymbolConfig{
2700
- OID: "1.3.6.1.4.1.1000.1",
2701
- Name: "myTable",
2702
- },
2703
- Symbols: []ddprofiledefinition.SymbolConfig{
2704
- {
2705
- OID: "1.3.6.1.4.1.1000.1.1.1",
2706
- Name: "myMetric",
2707
- },
2708
- },
2709
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2710
- {
2711
- Symbol: ddprofiledefinition.SymbolConfigCompat{
2712
- OID: "1.3.6.1.4.1.1000.2.1.1",
2713
- Name: "refName",
2714
- },
2715
- Table: "refTable",
2716
- IndexTransform: []ddprofiledefinition.MetricIndexTransform{
2717
- {Start: 5, End: 10}, // Out of bounds
2718
- },
2719
- Tag: "ref_name",
2720
- },
2721
- },
2722
- },
2723
- {
2724
- MIB: "MY-MIB",
2725
- Table: ddprofiledefinition.SymbolConfig{
2726
- OID: "1.3.6.1.4.1.1000.2",
2727
- Name: "refTable",
2728
- },
2729
- Symbols: []ddprofiledefinition.SymbolConfig{
2730
- // No symbols needed
2731
- },
2732
- },
2733
- },
2734
- },
2735
- },
2736
- },
2737
- setupMock: func(m *snmpmock.MockHandler) {
2738
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2739
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2740
-
2741
- // Walk myTable with short index
2742
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
2743
- []gosnmp.SnmpPDU{
2744
- {
2745
- Name: "1.3.6.1.4.1.1000.1.1.1.1.2",
2746
- Type: gosnmp.Gauge32,
2747
- Value: uint(100),
2748
- },
2749
- }, nil,
2750
- )
2751
-
2752
- // Walk refTable
2753
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.2").Return(
2754
- []gosnmp.SnmpPDU{
2755
- {
2756
- Name: "1.3.6.1.4.1.1000.2.1.1.1.2",
2757
- Type: gosnmp.OctetString,
2758
- Value: []byte("Ref"),
2759
- },
2760
- }, nil,
2761
- )
2762
- },
2763
- expectedResult: []*ProfileMetrics{
2764
- {
2765
- Source: "test-profile.yaml",
2766
- DeviceMetadata: nil,
2767
- Metrics: []Metric{
2768
- {
2769
- Name: "myMetric",
2770
- Value: 100,
2771
- Tags: nil, // No tag because transformation failed
2772
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2773
- IsTable: true,
2774
- },
2775
- },
2776
- },
2777
- },
2778
- expectedError: false,
2779
- },
2780
- "cross-table tags with transformation and mapping": {
2781
- profiles: []*ddsnmp.Profile{
2782
- {
2783
- SourceFile: "test-profile.yaml",
2784
- Definition: &ddprofiledefinition.ProfileDefinition{
2785
- Metrics: []ddprofiledefinition.MetricsConfig{
2786
- {
2787
- MIB: "MY-MIB",
2788
- Table: ddprofiledefinition.SymbolConfig{
2789
- OID: "1.3.6.1.4.1.1000.1",
2790
- Name: "myTable",
2791
- },
2792
- Symbols: []ddprofiledefinition.SymbolConfig{
2793
- {
2794
- OID: "1.3.6.1.4.1.1000.1.1.1",
2795
- Name: "myMetric",
2796
- },
2797
- },
2798
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2799
- {
2800
- Symbol: ddprofiledefinition.SymbolConfigCompat{
2801
- OID: "1.3.6.1.4.1.1000.2.1.1",
2802
- Name: "refType",
2803
- },
2804
- Table: "refTable",
2805
- IndexTransform: []ddprofiledefinition.MetricIndexTransform{
2806
- {Start: 1, End: 2},
2807
- },
2808
- Tag: "ref_type",
2809
- Mapping: map[string]string{
2810
- "1": "primary",
2811
- "2": "secondary",
2812
- "3": "backup",
2813
- },
2814
- },
2815
- },
2816
- },
2817
- {
2818
- MIB: "MY-MIB",
2819
- Table: ddprofiledefinition.SymbolConfig{
2820
- OID: "1.3.6.1.4.1.1000.2",
2821
- Name: "refTable",
2822
- },
2823
- Symbols: []ddprofiledefinition.SymbolConfig{
2824
- // No symbols needed
2825
- },
2826
- },
2827
- },
2828
- },
2829
- },
2830
- },
2831
- setupMock: func(m *snmpmock.MockHandler) {
2832
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2833
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2834
-
2835
- // Walk myTable
2836
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
2837
- []gosnmp.SnmpPDU{
2838
- {
2839
- Name: "1.3.6.1.4.1.1000.1.1.1.5.10.20",
2840
- Type: gosnmp.Gauge32,
2841
- Value: uint(100),
2842
- },
2843
- {
2844
- Name: "1.3.6.1.4.1.1000.1.1.1.6.20.30",
2845
- Type: gosnmp.Gauge32,
2846
- Value: uint(200),
2847
- },
2848
- }, nil,
2849
- )
2850
-
2851
- // Walk refTable with transformed indexes
2852
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.2").Return(
2853
- []gosnmp.SnmpPDU{
2854
- {
2855
- Name: "1.3.6.1.4.1.1000.2.1.1.10.20", // Matches first row (positions 2-3)
2856
- Type: gosnmp.Integer,
2857
- Value: 1, // Will map to "primary"
2858
- },
2859
- {
2860
- Name: "1.3.6.1.4.1.1000.2.1.1.20.30", // Matches second row (positions 2-3)
2861
- Type: gosnmp.Integer,
2862
- Value: 3, // Will map to "backup"
2863
- },
2864
- }, nil,
2865
- )
2866
- },
2867
- expectedResult: []*ProfileMetrics{
2868
- {
2869
- Source: "test-profile.yaml",
2870
- DeviceMetadata: nil,
2871
- Metrics: []Metric{
2872
- {
2873
- Name: "myMetric",
2874
- Value: 100,
2875
- Tags: map[string]string{"ref_type": "primary"},
2876
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2877
- IsTable: true,
2878
- },
2879
- {
2880
- Name: "myMetric",
2881
- Value: 200,
2882
- Tags: map[string]string{"ref_type": "backup"},
2883
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2884
- IsTable: true,
2885
- },
2886
- },
2887
- },
2888
- },
2889
- expectedError: false,
2890
- },
2891
- "cross-table tags mixed with index-based tags": {
2892
- profiles: []*ddsnmp.Profile{
2893
- {
2894
- SourceFile: "test-profile.yaml",
2895
- Definition: &ddprofiledefinition.ProfileDefinition{
2896
- Metrics: []ddprofiledefinition.MetricsConfig{
2897
- {
2898
- MIB: "MY-MIB",
2899
- Table: ddprofiledefinition.SymbolConfig{
2900
- OID: "1.3.6.1.4.1.1000.1",
2901
- Name: "myTable",
2902
- },
2903
- Symbols: []ddprofiledefinition.SymbolConfig{
2904
- {
2905
- OID: "1.3.6.1.4.1.1000.1.1.1",
2906
- Name: "myMetric",
2907
- },
2908
- },
2909
- MetricTags: []ddprofiledefinition.MetricTagConfig{
2910
- {
2911
- Index: 1,
2912
- Tag: "branch_id",
2913
- },
2914
- {
2915
- Symbol: ddprofiledefinition.SymbolConfigCompat{
2916
- OID: "1.3.6.1.4.1.1000.2.1.1",
2917
- Name: "pduName",
2918
- },
2919
- Table: "pduTable",
2920
- IndexTransform: []ddprofiledefinition.MetricIndexTransform{
2921
- {Start: 1, End: 7},
2922
- },
2923
- Tag: "pdu_name",
2924
- },
2925
- },
2926
- },
2927
- {
2928
- MIB: "MY-MIB",
2929
- Table: ddprofiledefinition.SymbolConfig{
2930
- OID: "1.3.6.1.4.1.1000.2",
2931
- Name: "pduTable",
2932
- },
2933
- Symbols: []ddprofiledefinition.SymbolConfig{
2934
- // No symbols needed
2935
- },
2936
- },
2937
- },
2938
- },
2939
- },
2940
- },
2941
- setupMock: func(m *snmpmock.MockHandler) {
2942
- m.EXPECT().MaxOids().Return(10).AnyTimes()
2943
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
2944
-
2945
- // Walk myTable
2946
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.1").Return(
2947
- []gosnmp.SnmpPDU{
2948
- {
2949
- Name: "1.3.6.1.4.1.1000.1.1.1.1.6.0.36.155.53.3.246",
2950
- Type: gosnmp.Gauge32,
2951
- Value: uint(100),
2952
- },
2953
- }, nil,
2954
- )
2955
-
2956
- // Walk pduTable
2957
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.1000.2").Return(
2958
- []gosnmp.SnmpPDU{
2959
- {
2960
- Name: "1.3.6.1.4.1.1000.2.1.1.6.0.36.155.53.3.246",
2961
- Type: gosnmp.OctetString,
2962
- Value: []byte("Main-PDU"),
2963
- },
2964
- }, nil,
2965
- )
2966
- },
2967
- expectedResult: []*ProfileMetrics{
2968
- {
2969
- Source: "test-profile.yaml",
2970
- DeviceMetadata: nil,
2971
- Metrics: []Metric{
2972
- {
2973
- Name: "myMetric",
2974
- Value: 100,
2975
- Tags: map[string]string{
2976
- "branch_id": "1",
2977
- "pdu_name": "Main-PDU",
2978
- },
2979
- MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
2980
- IsTable: true,
2981
- },
2982
- },
2983
- },
2984
- },
2985
- expectedError: false,
2986
- },
2987
-
2988
- "scalar metric with OpaqueFloat": {
2989
- profiles: []*ddsnmp.Profile{
2990
- {
2991
- SourceFile: "test-profile.yaml",
2992
- Definition: &ddprofiledefinition.ProfileDefinition{
2993
- Metrics: []ddprofiledefinition.MetricsConfig{
2994
- {
2995
- Symbol: ddprofiledefinition.SymbolConfig{
2996
- OID: "1.3.6.1.4.1.6574.4.2.12.1.0",
2997
- Name: "temperature",
2998
- },
2999
- },
3000
- },
3001
- },
3002
- },
3003
- },
3004
- setupMock: func(m *snmpmock.MockHandler) {
3005
- m.EXPECT().MaxOids().Return(10).AnyTimes()
3006
- m.EXPECT().Get([]string{"1.3.6.1.4.1.6574.4.2.12.1.0"}).Return(
3007
- &gosnmp.SnmpPacket{
3008
- Variables: []gosnmp.SnmpPDU{
3009
- {
3010
- Name: "1.3.6.1.4.1.6574.4.2.12.1.0",
3011
- Type: gosnmp.OpaqueFloat,
3012
- Value: float32(29.5),
3013
- },
3014
- },
3015
- }, nil,
3016
- )
3017
- },
3018
- expectedResult: []*ProfileMetrics{
3019
- {
3020
- Source: "test-profile.yaml",
3021
- DeviceMetadata: nil,
3022
- Metrics: []Metric{
3023
- {
3024
- Name: "temperature",
3025
- Value: 29, // Truncated from 29.5
3026
- MetricType: "gauge",
3027
- },
3028
- },
3029
- },
3030
- },
3031
- expectedError: false,
3032
- },
3033
- "scalar metric with OpaqueDouble": {
3034
- profiles: []*ddsnmp.Profile{
3035
- {
3036
- SourceFile: "test-profile.yaml",
3037
- Definition: &ddprofiledefinition.ProfileDefinition{
3038
- Metrics: []ddprofiledefinition.MetricsConfig{
3039
- {
3040
- Symbol: ddprofiledefinition.SymbolConfig{
3041
- OID: "1.3.6.1.4.1.6574.4.4.1.1.0",
3042
- Name: "voltage",
3043
- },
3044
- },
3045
- },
3046
- },
3047
- },
3048
- },
3049
- setupMock: func(m *snmpmock.MockHandler) {
3050
- m.EXPECT().MaxOids().Return(10).AnyTimes()
3051
- m.EXPECT().Get([]string{"1.3.6.1.4.1.6574.4.4.1.1.0"}).Return(
3052
- &gosnmp.SnmpPacket{
3053
- Variables: []gosnmp.SnmpPDU{
3054
- {
3055
- Name: "1.3.6.1.4.1.6574.4.4.1.1.0",
3056
- Type: gosnmp.OpaqueDouble,
3057
- Value: float64(232.75),
3058
- },
3059
- },
3060
- }, nil,
3061
- )
3062
- },
3063
- expectedResult: []*ProfileMetrics{
3064
- {
3065
- Source: "test-profile.yaml",
3066
- DeviceMetadata: nil,
3067
- Metrics: []Metric{
3068
- {
3069
- Name: "voltage",
3070
- Value: 232, // Truncated from 232.75
3071
- MetricType: "gauge",
3072
- },
3073
- },
3074
- },
3075
- },
3076
- expectedError: false,
3077
- },
3078
- "scalar metric with OpaqueFloat and scale factor": {
3079
- profiles: []*ddsnmp.Profile{
3080
- {
3081
- SourceFile: "test-profile.yaml",
3082
- Definition: &ddprofiledefinition.ProfileDefinition{
3083
- Metrics: []ddprofiledefinition.MetricsConfig{
3084
- {
3085
- Symbol: ddprofiledefinition.SymbolConfig{
3086
- OID: "1.3.6.1.4.1.6574.4.2.12.1.0",
3087
- Name: "temperatureMilliDegrees",
3088
- ScaleFactor: 1000, // Convert to milli-degrees to preserve precision
3089
- },
3090
- },
3091
- },
3092
- },
3093
- },
3094
- },
3095
- setupMock: func(m *snmpmock.MockHandler) {
3096
- m.EXPECT().MaxOids().Return(10).AnyTimes()
3097
- m.EXPECT().Get([]string{"1.3.6.1.4.1.6574.4.2.12.1.0"}).Return(
3098
- &gosnmp.SnmpPacket{
3099
- Variables: []gosnmp.SnmpPDU{
3100
- {
3101
- Name: "1.3.6.1.4.1.6574.4.2.12.1.0",
3102
- Type: gosnmp.OpaqueFloat,
3103
- Value: float32(29.567),
3104
- },
3105
- },
3106
- }, nil,
3107
- )
3108
- },
3109
- expectedResult: []*ProfileMetrics{
3110
- {
3111
- Source: "test-profile.yaml",
3112
- DeviceMetadata: nil,
3113
- Metrics: []Metric{
3114
- {
3115
- Name: "temperatureMilliDegrees",
3116
- Value: 29566, // 29.567 * 1000
3117
- MetricType: "gauge",
3118
- },
3119
- },
3120
- },
3121
- },
3122
- expectedError: false,
3123
- },
3124
- "table metric with OpaqueFloat": {
3125
- profiles: []*ddsnmp.Profile{
3126
- {
3127
- SourceFile: "test-profile.yaml",
3128
- Definition: &ddprofiledefinition.ProfileDefinition{
3129
- Metrics: []ddprofiledefinition.MetricsConfig{
3130
- {
3131
- MIB: "SYNOLOGY-SYSTEM-MIB",
3132
- Table: ddprofiledefinition.SymbolConfig{
3133
- OID: "1.3.6.1.4.1.6574.1",
3134
- Name: "temperatureTable",
3135
- },
3136
- Symbols: []ddprofiledefinition.SymbolConfig{
3137
- {
3138
- OID: "1.3.6.1.4.1.6574.1.2",
3139
- Name: "temperature",
3140
- },
3141
- },
3142
- MetricTags: []ddprofiledefinition.MetricTagConfig{
3143
- {
3144
- Tag: "sensor",
3145
- Symbol: ddprofiledefinition.SymbolConfigCompat{
3146
- OID: "1.3.6.1.4.1.6574.1.1",
3147
- Name: "temperatureIndex",
3148
- },
3149
- },
3150
- },
3151
- },
3152
- },
3153
- },
3154
- },
3155
- },
3156
- setupMock: func(m *snmpmock.MockHandler) {
3157
- m.EXPECT().MaxOids().Return(10).AnyTimes()
3158
- m.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
3159
-
3160
- m.EXPECT().BulkWalkAll("1.3.6.1.4.1.6574.1").Return(
3161
- []gosnmp.SnmpPDU{
3162
- // Row 1
3163
- {
3164
- Name: "1.3.6.1.4.1.6574.1.1.1",
3165
- Type: gosnmp.Integer,
3166
- Value: 1,
3167
- },
3168
- {
3169
- Name: "1.3.6.1.4.1.6574.1.2.1",
3170
- Type: gosnmp.OpaqueFloat,
3171
- Value: float32(65.5),
3172
- },
3173
- // Row 2
3174
- {
3175
- Name: "1.3.6.1.4.1.6574.1.1.2",
3176
- Type: gosnmp.Integer,
3177
- Value: 2,
3178
- },
3179
- {
3180
- Name: "1.3.6.1.4.1.6574.1.2.2",
3181
- Type: gosnmp.OpaqueFloat,
3182
- Value: float32(71.25),
3183
- },
3184
- }, nil,
3185
- )
3186
- },
3187
- expectedResult: []*ProfileMetrics{
3188
- {
3189
- Source: "test-profile.yaml",
3190
- DeviceMetadata: nil,
3191
- Metrics: []Metric{
3192
- {
3193
- Name: "temperature",
3194
- Value: 65, // Truncated from 65.5
3195
- Tags: map[string]string{"sensor": "1"},
3196
- MetricType: "gauge",
3197
- IsTable: true,
3198
- },
3199
- {
3200
- Name: "temperature",
3201
- Value: 71, // Truncated from 71.25
3202
- Tags: map[string]string{"sensor": "2"},
3203
- MetricType: "gauge",
3204
- IsTable: true,
3205
- },
3206
- },
3207
- },
3208
- },
3209
- expectedError: false,
3210
- },
3211
- "OpaqueFloat with unexpected type": {
3212
- profiles: []*ddsnmp.Profile{
3213
- {
3214
- SourceFile: "test-profile.yaml",
3215
- Definition: &ddprofiledefinition.ProfileDefinition{
3216
- Metrics: []ddprofiledefinition.MetricsConfig{
3217
- {
3218
- Symbol: ddprofiledefinition.SymbolConfig{
3219
- OID: "1.3.6.1.4.1.6574.4.2.12.1.0",
3220
- Name: "temperature",
3221
- },
3222
- },
3223
- },
3224
- },
3225
- },
3226
- },
3227
- setupMock: func(m *snmpmock.MockHandler) {
3228
- m.EXPECT().MaxOids().Return(10).AnyTimes()
3229
- m.EXPECT().Get([]string{"1.3.6.1.4.1.6574.4.2.12.1.0"}).Return(
3230
- &gosnmp.SnmpPacket{
3231
- Variables: []gosnmp.SnmpPDU{
3232
- {
3233
- Name: "1.3.6.1.4.1.6574.4.2.12.1.0",
3234
- Type: gosnmp.OpaqueFloat,
3235
- Value: "29.5", // Wrong type - should be float32
3236
- },
3237
- },
3238
- }, nil,
3239
- )
3240
- },
3241
- expectedResult: nil,
3242
- expectedError: true,
3243
- errorContains: "OpaqueFloat has unexpected type",
3244
- },
3245
- "OpaqueFloat with negative value": {
3246
- profiles: []*ddsnmp.Profile{
3247
- {
3248
- SourceFile: "test-profile.yaml",
3249
- Definition: &ddprofiledefinition.ProfileDefinition{
3250
- Metrics: []ddprofiledefinition.MetricsConfig{
3251
- {
3252
- Symbol: ddprofiledefinition.SymbolConfig{
3253
- OID: "1.3.6.1.4.1.6574.4.2.12.1.0",
3254
- Name: "temperature",
3255
- },
3256
- },
3257
- },
3258
- },
3259
- },
3260
- },
3261
- setupMock: func(m *snmpmock.MockHandler) {
3262
- m.EXPECT().MaxOids().Return(10).AnyTimes()
3263
- m.EXPECT().Get([]string{"1.3.6.1.4.1.6574.4.2.12.1.0"}).Return(
3264
- &gosnmp.SnmpPacket{
3265
- Variables: []gosnmp.SnmpPDU{
3266
- {
3267
- Name: "1.3.6.1.4.1.6574.4.2.12.1.0",
3268
- Type: gosnmp.OpaqueFloat,
3269
- Value: float32(-15.5),
3270
- },
3271
- },
3272
- }, nil,
3273
- )
3274
- },
3275
- expectedResult: []*ProfileMetrics{
3276
- {
3277
- Source: "test-profile.yaml",
3278
- DeviceMetadata: nil,
3279
- Metrics: []Metric{
3280
- {
3281
- Name: "temperature",
3282
- Value: -15, // Truncated from -15.5
1052
+ Name: "upsBasicBatteryStatus",
1053
+ Value: 1,
1054
MetricType: "gauge",
3284
- },
3285
- },
3286
- },
3287
- },
3288
- expectedError: false,
3289
- },
3290
- "OpaqueFloat with very large value": {
3291
- profiles: []*ddsnmp.Profile{
3292
- {
3293
- SourceFile: "test-profile.yaml",
3294
- Definition: &ddprofiledefinition.ProfileDefinition{
3295
- Metrics: []ddprofiledefinition.MetricsConfig{
3296
- {
3297
- Symbol: ddprofiledefinition.SymbolConfig{
3298
- OID: "1.3.6.1.4.1.6574.4.4.2.2.0",
3299
- Name: "power",
3300
- },
3301
- },
3302
- },
3303
- },
3304
- },
3305
- },
3306
- setupMock: func(m *snmpmock.MockHandler) {
3307
- m.EXPECT().MaxOids().Return(10).AnyTimes()
3308
- m.EXPECT().Get([]string{"1.3.6.1.4.1.6574.4.4.2.2.0"}).Return(
3309
- &gosnmp.SnmpPacket{
3310
- Variables: []gosnmp.SnmpPDU{
3311
- {
3312
- Name: "1.3.6.1.4.1.6574.4.4.2.2.0",
3313
- Type: gosnmp.OpaqueFloat,
3314
- Value: float32(1234567.89),
1055
+ Mappings: map[int64]string{
1056
+ 0: "batteryNormal",
1057
+ 1: "batteryLow",
1058
+ 2: "batteryDepleted",
1059
+ 3: "batteryCharging",
1060
},
1061
},
3317
- }, nil,
3318
- )
3319
- },
3320
- expectedResult: []*ProfileMetrics{
3321
- {
3322
- Source: "test-profile.yaml",
3323
- DeviceMetadata: nil,
3324
- Metrics: []Metric{
3325
- {
3326
- Name: "power",
3327
- Value: 1234567, // Truncated from 1234567.89
3328
- MetricType: "gauge",
3329
- },
1062
},
1063
},
1064
},
@@ -3344,13 +1076,11 @@ func TestCollector_Collect(t *testing.T) {
1076
1077
collector := New(mockHandler, tc.profiles, logger.New())
1078
collector.DoTableMetrics = true
3347
- collector.tableCache.setTTL(0, 0)
1079
+ collector.tableCache.setTTL(0, 0) // Disable cache
1080
1081
result, err := collector.Collect()
1082
3351
- // The Metric struct has a Profile field that contains a pointer to ProfileMetrics,
3352
- // which itself contains the Metrics slice.
3353
- // This creates a circular reference that makes ElementsMatch fail.
1083
+ // Clear circular references
1084
for _, profile := range result {
1085
for i := range profile.Metrics {
1086
profile.Metrics[i].Profile = nil
@@ -3370,6 +1100,7 @@ func TestCollector_Collect(t *testing.T) {
1100
require.Equal(t, len(tc.expectedResult), len(result))
1101
for i := range tc.expectedResult {
1102
assert.Equal(t, tc.expectedResult[i].DeviceMetadata, result[i].DeviceMetadata)
1103
+ assert.Equal(t, tc.expectedResult[i].Tags, result[i].Tags)
1104
assert.ElementsMatch(t, tc.expectedResult[i].Metrics, result[i].Metrics)
1105
}
1106
} else {