chore(go.d/ddsnmp): replace Mappings with MultiValue for state-based metrics (#20787)
Ilya Mashchenko committed
Aug 11, 2025 at 00:54 UTC
c42f98869b80e3ef0f1473a4cc5068c559d4ad05
12 files changed
+384
-133
src/go/plugin/go.d/collector/snmp/charts.go
+12
-12
@@ -347,13 +347,13 @@ func (c *Collector) addProfileScalarMetricChart(m ddsnmp.Metric) {
347
chart.Labels = append(chart.Labels, module.Label{Key: k, Value: v})
348
}
349
350
- if len(m.Mappings) > 0 {
350
+ if len(m.MultiValue) > 0 {
351
seen := make(map[string]bool)
352
- for _, v := range m.Mappings {
353
- if !seen[v] {
354
- seen[v] = true
355
- id := fmt.Sprintf("snmp_device_prof_%s_%s", m.Name, v)
356
- chart.Dims = append(chart.Dims, &module.Dim{ID: id, Name: v, Algo: module.Absolute})
352
+ for k := range m.MultiValue {
353
+ if !seen[k] {
354
+ seen[k] = true
355
+ id := fmt.Sprintf("snmp_device_prof_%s_%s", m.Name, k)
356
+ chart.Dims = append(chart.Dims, &module.Dim{ID: id, Name: k, Algo: module.Absolute})
357
}
358
}
359
} else {
@@ -411,13 +411,13 @@ func (c *Collector) addProfileTableMetricChart(m ddsnmp.Metric) {
411
chart.Labels = append(chart.Labels, module.Label{Key: k, Value: v})
412
}
413
414
- if len(m.Mappings) > 0 {
414
+ if len(m.MultiValue) > 0 {
415
seen := make(map[string]bool)
416
- for _, v := range m.Mappings {
417
- if !seen[v] {
418
- seen[v] = true
419
- id := fmt.Sprintf("snmp_device_prof_%s_%s", key, v)
420
- chart.Dims = append(chart.Dims, &module.Dim{ID: id, Name: v, Algo: module.Absolute})
416
+ for k := range m.MultiValue {
417
+ if !seen[k] {
418
+ seen[k] = true
419
+ id := fmt.Sprintf("snmp_device_prof_%s_%s", key, k)
420
+ chart.Dims = append(chart.Dims, &module.Dim{ID: id, Name: k, Algo: module.Absolute})
421
}
422
}
423
} else {
src/go/plugin/go.d/collector/snmp/collect_profiles.go
+8
-9
@@ -8,7 +8,6 @@ import (
8
"strings"
9
10
"github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
11
- "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/metrix"
11
)
12
13
func (c *Collector) collectProfiles(mx map[string]int64) error {
@@ -39,13 +38,13 @@ func (c *Collector) collectProfileScalarMetrics(mx map[string]int64, pms []*ddsn
38
c.addProfileScalarMetricChart(m)
39
}
40
42
- if len(m.Mappings) == 0 {
41
+ if len(m.MultiValue) == 0 {
42
id := fmt.Sprintf("snmp_device_prof_%s", m.Name)
43
mx[id] = m.Value
44
} else {
46
- for k, v := range m.Mappings {
47
- id := fmt.Sprintf("snmp_device_prof_%s_%s", m.Name, v)
48
- mx[id] = metrix.Bool(m.Value == k)
45
+ for k, v := range m.MultiValue {
46
+ id := fmt.Sprintf("snmp_device_prof_%s_%s", m.Name, k)
47
+ mx[id] = v
48
}
49
}
50
}
@@ -70,13 +69,13 @@ func (c *Collector) collectProfileTableMetrics(mx map[string]int64, pms []*ddsnm
69
c.addProfileTableMetricChart(m)
70
}
71
73
- if len(m.Mappings) == 0 {
72
+ if len(m.MultiValue) == 0 {
73
id := fmt.Sprintf("snmp_device_prof_%s", key)
74
mx[id] += m.Value
75
} else {
77
- for k, v := range m.Mappings {
78
- id := fmt.Sprintf("snmp_device_prof_%s_%s", key, v)
79
- mx[id] = metrix.Bool(m.Value == k)
76
+ for k, v := range m.MultiValue {
77
+ id := fmt.Sprintf("snmp_device_prof_%s_%s", key, k)
78
+ mx[id] = v
79
}
80
}
81
}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_scalar_test.go
+24
-29
@@ -330,10 +330,10 @@ func TestScalarCollector_Collect(t *testing.T) {
330
Name: "clusterHealth",
331
Value: 1,
332
MetricType: "gauge",
333
- Mappings: map[int64]string{
334
- 0: "OK",
335
- 1: "WARNING",
336
- 2: "CRITICAL",
333
+ MultiValue: map[string]int64{
334
+ "OK": 0,
335
+ "WARNING": 1,
336
+ "CRITICAL": 0,
337
},
338
},
339
},
@@ -370,12 +370,12 @@ func TestScalarCollector_Collect(t *testing.T) {
370
Name: "ifOperStatus",
371
Value: 2,
372
MetricType: "gauge",
373
- Mappings: map[int64]string{
374
- 1: "up",
375
- 2: "down",
376
- 3: "testing",
377
- 4: "unknown",
378
- 5: "dormant",
373
+ MultiValue: map[string]int64{
374
+ "up": 0,
375
+ "down": 1,
376
+ "testing": 0,
377
+ "unknown": 0,
378
+ "dormant": 0,
379
},
380
},
381
},
@@ -411,10 +411,10 @@ func TestScalarCollector_Collect(t *testing.T) {
411
Name: "fanStatus",
412
Value: 2,
413
MetricType: "gauge",
414
- Mappings: map[int64]string{
415
- 1: "normal",
416
- 2: "warning",
417
- 3: "critical",
414
+ MultiValue: map[string]int64{
415
+ "normal": 0,
416
+ "warning": 1,
417
+ "critical": 0,
418
},
419
},
420
},
@@ -449,11 +449,6 @@ func TestScalarCollector_Collect(t *testing.T) {
449
Name: "ifAdminStatus",
450
Value: 0, // mapped from 2 -> 0
451
MetricType: "gauge",
452
- Mappings: map[int64]string{
453
- 1: "1",
454
- 2: "0",
455
- 3: "0",
456
- },
452
},
453
},
454
expectedError: false,
@@ -528,7 +523,7 @@ func TestScalarCollector_Collect(t *testing.T) {
523
Name: "sysUpTime",
524
Value: 123456,
525
MetricType: "gauge",
531
- Mappings: nil, // No mappings
526
+ MultiValue: nil, // No mappings
527
},
528
},
529
expectedError: false,
@@ -562,10 +557,10 @@ func TestScalarCollector_Collect(t *testing.T) {
557
Name: "ifAdminStatus",
558
Value: 1,
559
MetricType: "gauge",
565
- Mappings: map[int64]string{
566
- 1: "up",
567
- 2: "down",
568
- 3: "testing",
560
+ MultiValue: map[string]int64{
561
+ "up": 1,
562
+ "down": 0,
563
+ "testing": 0,
564
},
565
},
566
},
@@ -601,11 +596,11 @@ func TestScalarCollector_Collect(t *testing.T) {
596
Name: "upsBasicBatteryStatus",
597
Value: 1,
598
MetricType: "gauge",
604
- Mappings: map[int64]string{
605
- 0: "batteryNormal",
606
- 1: "batteryLow",
607
- 2: "batteryDepleted",
608
- 3: "batteryCharging",
599
+ MultiValue: map[string]int64{
600
+ "batteryNormal": 0,
601
+ "batteryLow": 1,
602
+ "batteryDepleted": 0,
603
+ "batteryCharging": 0,
604
},
605
},
606
},
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_table_test.go
+61
-61
@@ -1301,12 +1301,12 @@ func TestTableCollector_Collect(t *testing.T) {
1301
Tags: map[string]string{"interface": "eth0"},
1302
MetricType: "gauge",
1303
IsTable: true,
1304
- Mappings: map[int64]string{
1305
- 1: "up",
1306
- 2: "down",
1307
- 3: "testing",
1308
- 4: "unknown",
1309
- 5: "dormant",
1304
+ MultiValue: map[string]int64{
1305
+ "up": 1,
1306
+ "down": 0,
1307
+ "testing": 0,
1308
+ "unknown": 0,
1309
+ "dormant": 0,
1310
},
1311
},
1312
{
@@ -1315,12 +1315,12 @@ func TestTableCollector_Collect(t *testing.T) {
1315
Tags: map[string]string{"interface": "eth1"},
1316
MetricType: "gauge",
1317
IsTable: true,
1318
- Mappings: map[int64]string{
1319
- 1: "up",
1320
- 2: "down",
1321
- 3: "testing",
1322
- 4: "unknown",
1323
- 5: "dormant",
1318
+ MultiValue: map[string]int64{
1319
+ "up": 0,
1320
+ "down": 1,
1321
+ "testing": 0,
1322
+ "unknown": 0,
1323
+ "dormant": 0,
1324
},
1325
},
1326
},
@@ -1376,10 +1376,10 @@ func TestTableCollector_Collect(t *testing.T) {
1376
Tags: map[string]string{"fan_name": "Fan1"},
1377
MetricType: "gauge",
1378
IsTable: true,
1379
- Mappings: map[int64]string{
1380
- 1: "normal",
1381
- 2: "warning",
1382
- 3: "critical",
1379
+ MultiValue: map[string]int64{
1380
+ "normal": 1,
1381
+ "warning": 0,
1382
+ "critical": 0,
1383
},
1384
},
1385
{
@@ -1388,10 +1388,10 @@ func TestTableCollector_Collect(t *testing.T) {
1388
Tags: map[string]string{"fan_name": "Fan2"},
1389
MetricType: "gauge",
1390
IsTable: true,
1391
- Mappings: map[int64]string{
1392
- 1: "normal",
1393
- 2: "warning",
1394
- 3: "critical",
1391
+ MultiValue: map[string]int64{
1392
+ "normal": 0,
1393
+ "warning": 0,
1394
+ "critical": 1,
1395
},
1396
},
1397
},
@@ -1568,11 +1568,11 @@ func TestTableCollector_Collect(t *testing.T) {
1568
Tags: map[string]string{"node_name": "node1"},
1569
MetricType: "gauge",
1570
IsTable: true,
1571
- Mappings: map[int64]string{
1572
- 0: "OK",
1573
- 1: "ATTN",
1574
- 2: "DOWN",
1575
- 3: "INVALID",
1571
+ MultiValue: map[string]int64{
1572
+ "OK": 1,
1573
+ "ATTN": 0,
1574
+ "DOWN": 0,
1575
+ "INVALID": 0,
1576
},
1577
},
1578
{
@@ -1581,11 +1581,11 @@ func TestTableCollector_Collect(t *testing.T) {
1581
Tags: map[string]string{"node_name": "node2"},
1582
MetricType: "gauge",
1583
IsTable: true,
1584
- Mappings: map[int64]string{
1585
- 0: "OK",
1586
- 1: "ATTN",
1587
- 2: "DOWN",
1588
- 3: "INVALID",
1584
+ MultiValue: map[string]int64{
1585
+ "OK": 0,
1586
+ "ATTN": 0,
1587
+ "DOWN": 1,
1588
+ "INVALID": 0,
1589
},
1590
},
1591
},
@@ -3302,7 +3302,7 @@ func TestTableCollector_Collect(t *testing.T) {
3302
{{- setFamily .Metric (get $config "family") -}}
3303
{{- with get $config "unit" -}}{{- setUnit $.Metric . -}}{{- end -}}
3304
{{- with get $config "divisor" -}}{{- setValue $.Metric (int64 (div (float64 $.Metric.Value) .)) -}}{{- end -}}
3305
- {{- with get $config "mapping" -}}{{- setMappings $.Metric . -}}{{- end -}}
3305
+ {{- with get $config "mapping" -}}{{- setMultivalue $.Metric . -}}{{- end -}}
3306
{{- end -}}
3307
3308
{{- deleteTag .Metric "sensor_type" -}}`,
@@ -3355,16 +3355,16 @@ func TestTableCollector_Collect(t *testing.T) {
3355
IsTable: true,
3356
},
3357
{
3358
- Name: "mtxrHlSensorValue_sensor_status",
3359
- Value: 1,
3360
- Tags: map[string]string{"sensor_name": "psu1-state"},
3361
- Family: "Health/Status",
3362
- Mappings: map[int64]string{
3363
- 0: "not_ok",
3364
- 1: "ok",
3365
- },
3358
+ Name: "mtxrHlSensorValue_sensor_status",
3359
+ Value: 1,
3360
+ Tags: map[string]string{"sensor_name": "psu1-state"},
3361
+ Family: "Health/Status",
3362
MetricType: "gauge",
3363
IsTable: true,
3364
+ MultiValue: map[string]int64{
3365
+ "not_ok": 0,
3366
+ "ok": 1,
3367
+ },
3368
},
3369
{
3370
Name: "mtxrHlSensorValue_voltage",
@@ -3482,7 +3482,7 @@ func TestTableCollector_Collect(t *testing.T) {
3482
{{- else -}}
3483
{{- setValue .Metric 0 -}}
3484
{{- end -}}
3485
-{{- setMappings .Metric (i64map 0 "down" 1 "up") -}}
3485
+{{- setMultivalue .Metric (i64map 0 "down" 1 "up") -}}
3486
{{- setName .Metric "interface_status" -}}`,
3487
},
3488
},
@@ -3511,37 +3511,37 @@ func TestTableCollector_Collect(t *testing.T) {
3511
},
3512
expectedResult: []ddsnmp.Metric{
3513
{
3514
- Name: "interface_status",
3515
- Value: 1,
3516
- Tags: map[string]string{"interface": "eth0"},
3517
- Mappings: map[int64]string{
3518
- 0: "down",
3519
- 1: "up",
3520
- },
3514
+ Name: "interface_status",
3515
+ Value: 1,
3516
+ Tags: map[string]string{"interface": "eth0"},
3517
MetricType: "gauge",
3518
IsTable: true,
3519
+ MultiValue: map[string]int64{
3520
+ "down": 0,
3521
+ "up": 1,
3522
+ },
3523
},
3524
{
3525
- Name: "interface_status",
3526
- Value: 0,
3527
- Tags: map[string]string{"interface": "eth1"},
3528
- Mappings: map[int64]string{
3529
- 0: "down",
3530
- 1: "up",
3531
- },
3525
+ Name: "interface_status",
3526
+ Value: 0,
3527
+ Tags: map[string]string{"interface": "eth1"},
3528
MetricType: "gauge",
3529
IsTable: true,
3530
+ MultiValue: map[string]int64{
3531
+ "down": 1,
3532
+ "up": 0,
3533
+ },
3534
},
3535
{
3536
- Name: "interface_status",
3537
- Value: 0,
3538
- Tags: map[string]string{"interface": "eth2"},
3539
- Mappings: map[int64]string{
3540
- 0: "down",
3541
- 1: "up",
3542
- },
3536
+ Name: "interface_status",
3537
+ Value: 0,
3538
+ Tags: map[string]string{"interface": "eth2"},
3539
MetricType: "gauge",
3540
IsTable: true,
3541
+ MultiValue: map[string]int64{
3542
+ "down": 1,
3543
+ "up": 0,
3544
+ },
3545
},
3546
},
3547
expectedError: false,
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_vmetrics.go
+12
-1
@@ -28,6 +28,7 @@ type vmetricsSourceKey struct {
28
type vmetricsAggregator struct {
29
config ddprofiledefinition.VirtualMetricConfig
30
sum int64
31
+ multiSum map[string]int64 // For aggregating MultiValue metrics
32
sourceCount int
33
metricType ddprofiledefinition.ProfileMetricType
34
}
@@ -52,7 +53,16 @@ func (p *vmetricsCollector) Collect(profDef *ddprofiledefinition.ProfileDefiniti
53
// Find all aggregators that need this metric
54
if aggrs, found := sourceToAggregators[key]; found {
55
for _, agg := range aggrs {
55
- agg.sum += metric.Value
56
+ if len(metric.MultiValue) > 0 {
57
+ if agg.multiSum == nil {
58
+ agg.multiSum = make(map[string]int64)
59
+ }
60
+ for state, value := range metric.MultiValue {
61
+ agg.multiSum[state] += value
62
+ }
63
+ } else {
64
+ agg.sum += metric.Value
65
+ }
66
agg.sourceCount++
67
if agg.metricType == "" {
68
agg.metricType = metric.MetricType
@@ -72,6 +82,7 @@ func (p *vmetricsCollector) Collect(profDef *ddprofiledefinition.ProfileDefiniti
82
virtualMetrics = append(virtualMetrics, ddsnmp.Metric{
83
Name: agg.config.Name,
84
Value: agg.sum,
85
+ MultiValue: agg.multiSum,
86
Description: agg.config.ChartMeta.Description,
87
Family: agg.config.ChartMeta.Family,
88
Unit: agg.config.ChartMeta.Unit,
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_vmetrics_test.go
+203
@@ -429,6 +429,209 @@ func TestVirtualMetricsCollector_Collect(t *testing.T) {
429
},
430
},
431
},
432
+
433
+ "aggregate MultiValue metrics": {
434
+ profileDef: &ddprofiledefinition.ProfileDefinition{
435
+ Metrics: []ddprofiledefinition.MetricsConfig{
436
+ {
437
+ Table: ddprofiledefinition.SymbolConfig{
438
+ OID: "1.3.6.1.2.1.2.2",
439
+ Name: "ifTable",
440
+ },
441
+ Symbols: []ddprofiledefinition.SymbolConfig{
442
+ {
443
+ OID: "1.3.6.1.2.1.2.2.1.8",
444
+ Name: "ifOperStatus",
445
+ Mapping: map[string]string{
446
+ "1": "up",
447
+ "2": "down",
448
+ "3": "testing",
449
+ "4": "unknown",
450
+ "5": "dormant",
451
+ },
452
+ },
453
+ },
454
+ },
455
+ },
456
+ VirtualMetrics: []ddprofiledefinition.VirtualMetricConfig{
457
+ {
458
+ Name: "ifTotalOperStatus",
459
+ Sources: []ddprofiledefinition.VirtualMetricSourceConfig{
460
+ {Metric: "ifOperStatus", Table: "ifTable"},
461
+ },
462
+ ChartMeta: ddprofiledefinition.ChartMeta{
463
+ Description: "Total interface operational status",
464
+ Family: "Network/Total/Interface/Status",
465
+ Unit: "{status}",
466
+ },
467
+ },
468
+ },
469
+ },
470
+ collectedMetrics: []ddsnmp.Metric{
471
+ // Interface 1 - up
472
+ {
473
+ Name: "ifOperStatus",
474
+ Value: 1,
475
+ IsTable: true,
476
+ Table: "ifTable",
477
+ MultiValue: map[string]int64{
478
+ "up": 1,
479
+ "down": 0,
480
+ "testing": 0,
481
+ "unknown": 0,
482
+ "dormant": 0,
483
+ },
484
+ MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
485
+ },
486
+ // Interface 2 - down
487
+ {
488
+ Name: "ifOperStatus",
489
+ Value: 2,
490
+ IsTable: true,
491
+ Table: "ifTable",
492
+ MultiValue: map[string]int64{
493
+ "up": 0,
494
+ "down": 1,
495
+ "testing": 0,
496
+ "unknown": 0,
497
+ "dormant": 0,
498
+ },
499
+ },
500
+ // Interface 3 - up
501
+ {
502
+ Name: "ifOperStatus",
503
+ Value: 1,
504
+ IsTable: true,
505
+ Table: "ifTable",
506
+ MultiValue: map[string]int64{
507
+ "up": 1,
508
+ "down": 0,
509
+ "testing": 0,
510
+ "unknown": 0,
511
+ "dormant": 0,
512
+ },
513
+ },
514
+ // Interface 4 - testing
515
+ {
516
+ Name: "ifOperStatus",
517
+ Value: 3,
518
+ IsTable: true,
519
+ Table: "ifTable",
520
+ MultiValue: map[string]int64{
521
+ "up": 0,
522
+ "down": 0,
523
+ "testing": 1,
524
+ "unknown": 0,
525
+ "dormant": 0,
526
+ },
527
+ },
528
+ },
529
+ expected: []ddsnmp.Metric{
530
+ {
531
+ Name: "ifTotalOperStatus",
532
+ Value: 0, // Not used for MultiValue
533
+ MultiValue: map[string]int64{
534
+ "up": 2, // 2 interfaces up
535
+ "down": 1, // 1 interface down
536
+ "testing": 1, // 1 interface testing
537
+ "unknown": 0,
538
+ "dormant": 0,
539
+ },
540
+ Description: "Total interface operational status",
541
+ Family: "Network/Total/Interface/Status",
542
+ Unit: "{status}",
543
+ MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
544
+ },
545
+ },
546
+ },
547
+
548
+ "mixed MultiValue and regular metrics": {
549
+ profileDef: &ddprofiledefinition.ProfileDefinition{
550
+ Metrics: []ddprofiledefinition.MetricsConfig{
551
+ {
552
+ Table: ddprofiledefinition.SymbolConfig{
553
+ OID: "1.3.6.1.2.1.2.2",
554
+ Name: "ifTable",
555
+ },
556
+ Symbols: []ddprofiledefinition.SymbolConfig{
557
+ {
558
+ OID: "1.3.6.1.2.1.2.2.1.10",
559
+ Name: "ifInOctets",
560
+ },
561
+ {
562
+ OID: "1.3.6.1.2.1.2.2.1.8",
563
+ Name: "ifOperStatus",
564
+ Mapping: map[string]string{
565
+ "1": "up",
566
+ "2": "down",
567
+ },
568
+ },
569
+ },
570
+ },
571
+ },
572
+ VirtualMetrics: []ddprofiledefinition.VirtualMetricConfig{
573
+ {
574
+ Name: "ifTotalInOctets",
575
+ Sources: []ddprofiledefinition.VirtualMetricSourceConfig{
576
+ {Metric: "ifInOctets", Table: "ifTable"},
577
+ },
578
+ ChartMeta: ddprofiledefinition.ChartMeta{
579
+ Description: "Total inbound traffic",
580
+ Family: "Network/Total/Traffic",
581
+ Unit: "bit/s",
582
+ },
583
+ },
584
+ {
585
+ Name: "ifTotalOperStatus",
586
+ Sources: []ddprofiledefinition.VirtualMetricSourceConfig{
587
+ {Metric: "ifOperStatus", Table: "ifTable"},
588
+ },
589
+ ChartMeta: ddprofiledefinition.ChartMeta{
590
+ Description: "Total interface status",
591
+ Family: "Network/Total/Status",
592
+ Unit: "{status}",
593
+ },
594
+ },
595
+ },
596
+ },
597
+ collectedMetrics: []ddsnmp.Metric{
598
+ // Regular metric
599
+ {Name: "ifInOctets", Value: 1000, IsTable: true, Table: "ifTable"},
600
+ {Name: "ifInOctets", Value: 2000, IsTable: true, Table: "ifTable"},
601
+ // MultiValue metric
602
+ {
603
+ Name: "ifOperStatus",
604
+ Value: 1,
605
+ IsTable: true,
606
+ Table: "ifTable",
607
+ MultiValue: map[string]int64{"up": 1, "down": 0},
608
+ },
609
+ {
610
+ Name: "ifOperStatus",
611
+ Value: 2,
612
+ IsTable: true,
613
+ Table: "ifTable",
614
+ MultiValue: map[string]int64{"up": 0, "down": 1},
615
+ },
616
+ },
617
+ expected: []ddsnmp.Metric{
618
+ {
619
+ Name: "ifTotalInOctets",
620
+ Value: 3000,
621
+ Description: "Total inbound traffic",
622
+ Family: "Network/Total/Traffic",
623
+ Unit: "bit/s",
624
+ },
625
+ {
626
+ Name: "ifTotalOperStatus",
627
+ Value: 0,
628
+ MultiValue: map[string]int64{"up": 1, "down": 1},
629
+ Description: "Total interface status",
630
+ Family: "Network/Total/Status",
631
+ Unit: "{status}",
632
+ },
633
+ },
634
+ },
635
}
636
637
for name, tc := range tests {
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/common_test.go
+1
-1
@@ -131,7 +131,7 @@ func assertMetricsEqual(t *testing.T, expected, actual []ddsnmp.Metric) {
131
assert.Equal(t, expected[i].Unit, actual[i].Unit, "metric unit")
132
assert.Equal(t, expected[i].Family, actual[i].Family, "metric family")
133
assert.Equal(t, expected[i].Description, actual[i].Description, "metric description")
134
- assert.Equal(t, expected[i].Mappings, actual[i].Mappings, "metric mappings")
134
+ assert.Equal(t, expected[i].MultiValue, actual[i].MultiValue, "metric multi value")
135
}
136
}
137
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/metric_builder.go
+34
-10
@@ -12,6 +12,7 @@ import (
12
13
"github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
"github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/metrix"
16
)
17
18
type metricBuilder struct {
@@ -52,7 +53,7 @@ func (mb *metricBuilder) fromSymbol(sym ddprofiledefinition.SymbolConfig, pdu go
53
mb.metric.Description = sym.ChartMeta.Description
54
mb.metric.Family = sym.ChartMeta.Family
55
mb.metric.MetricType = ternary(sym.MetricType != "", sym.MetricType, getMetricTypeFromPDUType(pdu))
55
- mb.metric.Mappings = convMappingToNumeric(sym)
56
+ mb.metric.MultiValue = buildMultiValue(mb.metric.Value, sym.Mapping)
57
return mb
58
}
59
@@ -92,13 +93,25 @@ func buildTableMetric(cfg ddprofiledefinition.SymbolConfig, pdu gosnmp.SnmpPDU,
93
return &metric, nil
94
}
95
95
-func convMappingToNumeric(cfg ddprofiledefinition.SymbolConfig) map[int64]string {
96
- if len(cfg.Mapping) == 0 {
96
+func buildMultiValue(value int64, mappings map[string]string) map[string]int64 {
97
+ if len(mappings) == 0 {
98
+ return nil
99
+ }
100
+
101
+ // Check if this is an int→int mapping (value transformation)
102
+ if isIntToIntMapping := func() bool {
103
+ for k, v := range mappings {
104
+ if !isInt(k) || !isInt(v) {
105
+ return false
106
+ }
107
+ }
108
+ return true
109
+ }(); isIntToIntMapping {
110
return nil
111
}
112
113
isMappingKeysNumeric := func() bool {
101
- for k := range cfg.Mapping {
114
+ for k := range mappings {
115
if !isInt(k) {
116
return false
117
}
@@ -106,22 +119,33 @@ func convMappingToNumeric(cfg ddprofiledefinition.SymbolConfig) map[int64]string
119
return true
120
}()
121
109
- mappings := make(map[int64]string)
122
+ multiValue := make(map[string]int64)
123
124
if isMappingKeysNumeric {
112
- for k, v := range cfg.Mapping {
125
+ // int→string mapping (e.g., 1→"up", 2→"down")
126
+ for k, v := range mappings {
127
intKey, _ := strconv.ParseInt(k, 10, 64)
114
- mappings[intKey] = v
128
+ // Only set the value if:
129
+ // 1. We haven't seen this state name before (!ok), OR
130
+ // 2. The current value matches this key (value == intKey)
131
+ // This ensures that if multiple keys map to the same state name,
132
+ // we preserve the "1" (active) value if any key matches
133
+ if _, ok := multiValue[v]; !ok || value == intKey {
134
+ multiValue[v] = metrix.Bool(value == intKey)
135
+ }
136
}
137
} else {
117
- for k, v := range cfg.Mapping {
138
+ // string→int mapping (e.g., "OK"→"0", "WARNING"→"1", "CRITICAL"→"2")
139
+ // value has already been converted from string to int by the value processor
140
+ // We need to find which original string maps to our current value
141
+ for k, v := range mappings {
142
if intVal, err := strconv.ParseInt(v, 10, 64); err == nil {
119
- mappings[intVal] = k
143
+ multiValue[k] = metrix.Bool(value == intVal)
144
}
145
}
146
}
147
124
- return mappings
148
+ return multiValue
149
}
150
151
func applyTransform(metric *ddsnmp.Metric, sym ddprofiledefinition.SymbolConfig) error {
src/go/plugin/go.d/collector/snmp/ddsnmp/metric.go
+1
-2
@@ -24,6 +24,5 @@ type Metric struct {
24
Value int64
25
MultiValue map[string]int64
26
27
- Mappings map[int64]string
28
- IsTable bool
27
+ IsTable bool
28
}
src/go/plugin/go.d/collector/snmp/ddsnmp/transform.go
+19
-7
@@ -11,6 +11,8 @@ import (
11
"text/template"
12
13
"github.com/Masterminds/sprig/v3"
14
+
15
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/metrix"
16
)
17
18
// CompileTransforms exported for testing purposes only
@@ -99,8 +101,13 @@ func newMetricTransformFuncMap() template.FuncMap {
101
m.Tags[key] = value
102
return ""
103
},
102
- "setMappings": func(m *Metric, mappings map[int64]string) string {
103
- m.Mappings = mappings
104
+ "setMultivalue": func(m *Metric, mappings map[int64]string) string {
105
+ m.MultiValue = make(map[string]int64)
106
+ for k, v := range mappings {
107
+ if _, ok := m.MultiValue[v]; !ok || m.Value == k {
108
+ m.MultiValue[v] = metrix.Bool(m.Value == k)
109
+ }
110
+ }
111
return ""
112
},
113
"i64map": func(pairs ...any) map[int64]string {
@@ -201,10 +208,10 @@ func newMetricTransformFuncMap() template.FuncMap {
208
if family, ok := conf["family"].(string); ok {
209
m.Family = famPrefix + family + "/Status"
210
}
204
- m.Mappings = map[int64]string{
205
- 1: "ok",
206
- 2: "unavailable",
207
- 3: "nonoperational",
211
+ m.MultiValue = map[string]int64{
212
+ "ok": metrix.Bool(m.Value == 1),
213
+ "unavailable": metrix.Bool(m.Value == 2),
214
+ "nonoperational": metrix.Bool(m.Value == 3),
215
}
216
case "entPhySensorValue", "entSensorValue":
217
scaleMap := map[string]float64{
@@ -247,7 +254,12 @@ func newMetricTransformFuncMap() template.FuncMap {
254
m.Unit = unit
255
}
256
if mapping, ok := conf["mapping"].(map[int64]string); ok {
250
- m.Mappings = mapping
257
+ m.MultiValue = make(map[string]int64)
258
+ for k, v := range mapping {
259
+ if _, ok := m.MultiValue[v]; !ok || m.Value == k {
260
+ m.MultiValue[v] = metrix.Bool(m.Value == k)
261
+ }
262
+ }
263
} else {
264
val := float64(m.Value) * scale
265
if precision > 0 {
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_std-if-mib.yaml
+8
@@ -174,3 +174,11 @@ virtual_metrics:
174
description: Total outbound traffic across all interfaces
175
family: 'Network/Total/Traffic/Out'
176
unit: "bit/s"
177
+ - name: ifTotalOperStatus
178
+ sources:
179
+ - metric: ifOperStatus
180
+ table: ifTable
181
+ chart_meta:
182
+ description: Total count of interfaces by operational status
183
+ family: 'Network/Total/Interface/Status'
184
+ unit: "{status}"
src/go/plugin/go.d/config/go.d/snmp.profiles/default/mikrotik-router.yaml
+1
-1
@@ -228,7 +228,7 @@ metrics:
228
{{- with get $config "desc" -}}{{- setDesc $.Metric . -}}{{- end -}}
229
{{- with get $config "unit" -}}{{- setUnit $.Metric . -}}{{- end -}}
230
{{- with get $config "divisor" -}}{{- setValue $.Metric (int64 (div (float64 $.Value) .)) -}}{{- end -}}
231
- {{- with get $config "mapping" -}}{{- setMappings $.Metric . -}}{{- end -}}
231
+ {{- with get $config "mapping" -}}{{- setMultivalue $.Metric . -}}{{- end -}}
232
{{- end -}}
233
metric_tags:
234
- tag: sensor_name