@cryptotaxi247 / netdata-1 / commits / c42f98869

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