fix(go.d/ddsnmp): respect metric tag order from profile definition (#20784)
Ilya Mashchenko committed
Aug 9, 2025 at 13:05 UTC
28ec237e2ea3bd580d6828f199e91cf139526311
3 files changed
+457
-116
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_table.go
+98
-69
@@ -64,57 +64,69 @@ type tableWalkResult struct {
64
config ddprofiledefinition.MetricsConfig
65
}
66
67
-type tableProcessingContext struct {
68
- // === Input data (set when context is created) ===
69
-
70
- // config is the metric configuration for this table
71
- // Contains table OID, symbols to collect, and tag configurations
72
- config ddprofiledefinition.MetricsConfig
73
-
74
- // pdus contains all PDUs from walking this specific table
75
- // Key: full OID (e.g., "1.3.6.1.2.1.2.2.1.10.1"), Value: SNMP PDU
76
- pdus map[string]gosnmp.SnmpPDU
77
-
78
- // walkedData contains PDUs from ALL walked tables in this collection
79
- // Used for cross-table tag resolution
80
- // Key: table OID → map[full OID]PDU
81
- walkedData map[string]map[string]gosnmp.SnmpPDU
82
-
83
- // tableNameToOID maps table names to their OIDs
84
- // Used to resolve cross-table references (e.g., "ifXTable" → "1.3.6.1.2.1.31.1.1")
85
- tableNameToOID map[string]string
86
-
87
- // === Computed during processing (set by various methods) ===
88
-
89
- // columnOIDs maps column OIDs to their symbol configurations
90
- // Built from config.Symbols, used to identify which columns contain metrics
91
- // Key: column OID (e.g., "1.3.6.1.2.1.2.2.1.10"), Value: symbol config
92
- columnOIDs map[string]ddprofiledefinition.SymbolConfig
93
-
94
- // sameTableTagOIDs maps column OIDs to tag configurations for same-table tags only
95
- // Built from config.MetricTags, excludes cross-table tags
96
- // Key: column OID → list of tag configs (multiple tags can use same column)
97
- sameTableTagOIDs map[string][]ddprofiledefinition.MetricTagConfig
98
-
99
- // staticTags contains tags that apply to all metrics from this table
100
- // Parsed from config.StaticTags (e.g., "source:network")
101
- staticTags map[string]string
102
-
103
- // rows contains PDUs organized by row index
104
- // Created by organizePDUsByRow from flat PDU list
105
- // Key: row index (e.g., "1", "2.3") → map[column OID]PDU
106
- rows map[string]map[string]gosnmp.SnmpPDU
107
-
108
- // oidCache stores the mapping of column OID to full OID for each row
109
- // Used for caching table structure
110
- // Key: row index → map[column OID]full OID
111
- oidCache map[string]map[string]string
67
+type (
68
+ tableProcessingContext struct {
69
+ // === Input data (set when context is created) ===
70
+
71
+ // config is the metric configuration for this table
72
+ // Contains table OID, symbols to collect, and tag configurations
73
+ config ddprofiledefinition.MetricsConfig
74
+
75
+ // pdus contains all PDUs from walking this specific table
76
+ // Key: full OID (e.g., "1.3.6.1.2.1.2.2.1.10.1"), Value: SNMP PDU
77
+ pdus map[string]gosnmp.SnmpPDU
78
+
79
+ // walkedData contains PDUs from ALL walked tables in this collection
80
+ // Used for cross-table tag resolution
81
+ // Key: table OID → map[full OID]PDU
82
+ walkedData map[string]map[string]gosnmp.SnmpPDU
83
+
84
+ // tableNameToOID maps table names to their OIDs
85
+ // Used to resolve cross-table references (e.g., "ifXTable" → "1.3.6.1.2.1.31.1.1")
86
+ tableNameToOID map[string]string
87
+
88
+ // === Computed during processing (set by various methods) ===
89
+
90
+ // columnOIDs maps column OIDs to their symbol configurations
91
+ // Built from config.Symbols, used to identify which columns contain metrics
92
+ // Key: column OID (e.g., "1.3.6.1.2.1.2.2.1.10"), Value: symbol config
93
+ columnOIDs map[string]ddprofiledefinition.SymbolConfig
94
+
95
+ // staticTags contains tags that apply to all metrics from this table
96
+ // Parsed from config.StaticTags (e.g., "source:network")
97
+ staticTags map[string]string
98
+
99
+ // rows contains PDUs organized by row index
100
+ // Created by organizePDUsByRow from flat PDU list
101
+ // Key: row index (e.g., "1", "2.3") → map[column OID]PDU
102
+ rows map[string]map[string]gosnmp.SnmpPDU
103
+
104
+ // oidCache stores the mapping of column OID to full OID for each row
105
+ // Used for caching table structure
106
+ // Key: row index → map[column OID]full OID
107
+ oidCache map[string]map[string]string
108
+
109
+ // tagCache stores computed tag values for each row
110
+ // Populated during row processing, used for caching
111
+ // Key: row index → map[tag name]tag value
112
+ tagCache map[string]map[string]string
113
+
114
+ // orderedTags contains metric tags in profile-defined order to ensure correct
115
+ // precedence when multiple tags share the same name (first non-empty wins)
116
+ orderedTags []orderedTagConfig
117
+ }
118
+ orderedTagConfig struct {
119
+ config ddprofiledefinition.MetricTagConfig
120
+ tagType tagType
121
+ }
122
+ tagType int
123
+)
124
113
- // tagCache stores computed tag values for each row
114
- // Populated during row processing, used for caching
115
- // Key: row index → map[tag name]tag value
116
- tagCache map[string]map[string]string
117
-}
125
+const (
126
+ tagTypeSameTable tagType = iota
127
+ tagTypeCrossTable
128
+ tagTypeIndex
129
+)
130
131
type cacheProcessingContext struct {
132
// config is the metric configuration for this table
@@ -343,7 +355,8 @@ func (tc *tableCollector) tryCollectFromCache(cfg ddprofiledefinition.MetricsCon
355
// processTableData processes walked table data
356
func (tc *tableCollector) processTableData(ctx *tableProcessingContext) ([]ddsnmp.Metric, error) {
357
ctx.columnOIDs = buildColumnOIDs(ctx.config)
346
- ctx.sameTableTagOIDs = buildSameTableTagOIDs(ctx.config)
358
+
359
+ ctx.orderedTags = buildOrderedTags(ctx.config)
360
361
ctx.rows, ctx.oidCache, ctx.tagCache = tc.organizePDUsByRow(ctx)
362
@@ -362,12 +375,16 @@ func (tc *tableCollector) processTableData(ctx *tableProcessingContext) ([]ddsnm
375
// organizePDUsByRow groups PDUs by their row index
376
func (tc *tableCollector) organizePDUsByRow(ctx *tableProcessingContext) (rows map[string]map[string]gosnmp.SnmpPDU, oidCache, tagCache map[string]map[string]string) {
377
// Combine all column OIDs
365
- allColumnOIDs := make([]string, 0, len(ctx.columnOIDs)+len(ctx.sameTableTagOIDs))
378
+ allColumnOIDs := make([]string, 0, len(ctx.columnOIDs)+len(ctx.orderedTags))
379
+
380
for oid := range ctx.columnOIDs {
381
allColumnOIDs = append(allColumnOIDs, oid)
382
}
369
- for oid := range ctx.sameTableTagOIDs {
370
- allColumnOIDs = append(allColumnOIDs, oid)
383
+ for _, orderedTag := range ctx.orderedTags {
384
+ if orderedTag.tagType == tagTypeSameTable && orderedTag.config.Symbol.OID != "" {
385
+ oid := trimOID(orderedTag.config.Symbol.OID)
386
+ allColumnOIDs = append(allColumnOIDs, oid)
387
+ }
388
}
389
390
rows = make(map[string]map[string]gosnmp.SnmpPDU)
@@ -414,10 +431,10 @@ func (tc *tableCollector) processRows(ctx *tableProcessingContext) ([]ddsnmp.Met
431
crossTableCtx.rowTags = row.tags
432
433
rowCtx := &tableRowProcessingContext{
417
- config: ctx.config,
418
- columnOIDs: ctx.columnOIDs,
419
- sameTableTagOIDs: ctx.sameTableTagOIDs,
420
- crossTableCtx: crossTableCtx,
434
+ config: ctx.config,
435
+ columnOIDs: ctx.columnOIDs,
436
+ crossTableCtx: crossTableCtx,
437
+ orderedTags: ctx.orderedTags,
438
}
439
rowMetrics, err := tc.rowProcessor.processRow(row, rowCtx)
440
if err != nil {
@@ -592,17 +609,6 @@ func buildColumnOIDs(cfg ddprofiledefinition.MetricsConfig) map[string]ddprofile
609
return columnOIDs
610
}
611
595
-func buildSameTableTagOIDs(cfg ddprofiledefinition.MetricsConfig) map[string][]ddprofiledefinition.MetricTagConfig {
596
- tagColumnOIDs := make(map[string][]ddprofiledefinition.MetricTagConfig)
597
- for _, tagCfg := range cfg.MetricTags {
598
- if tagCfg.Table == "" || tagCfg.Table == cfg.Table.Name {
599
- oid := trimOID(tagCfg.Symbol.OID)
600
- tagColumnOIDs[oid] = append(tagColumnOIDs[oid], tagCfg)
601
- }
602
- }
603
- return tagColumnOIDs
604
-}
605
-
612
func extractTableDependencies(cfg ddprofiledefinition.MetricsConfig, tableNameToOID map[string]string) []string {
613
deps := make(map[string]bool)
614
@@ -624,3 +630,26 @@ func extractTableDependencies(cfg ddprofiledefinition.MetricsConfig, tableNameTo
630
631
return result
632
}
633
+
634
+func buildOrderedTags(cfg ddprofiledefinition.MetricsConfig) []orderedTagConfig {
635
+ var ordered []orderedTagConfig
636
+
637
+ for _, tagCfg := range cfg.MetricTags {
638
+ var tt tagType
639
+ switch {
640
+ case tagCfg.Index != 0:
641
+ tt = tagTypeIndex
642
+ case tagCfg.Table != "" && tagCfg.Table != cfg.Table.Name:
643
+ tt = tagTypeCrossTable
644
+ default:
645
+ tt = tagTypeSameTable
646
+ }
647
+
648
+ ordered = append(ordered, orderedTagConfig{
649
+ config: tagCfg,
650
+ tagType: tt,
651
+ })
652
+ }
653
+
654
+ return ordered
655
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_table_test.go
+323
@@ -2451,6 +2451,329 @@ func TestTableCollector_Collect(t *testing.T) {
2451
},
2452
expectedError: false,
2453
},
2454
+ "tag precedence - cross-table before same-table": {
2455
+ profile: &ddsnmp.Profile{
2456
+ SourceFile: "test-profile.yaml",
2457
+ Definition: &ddprofiledefinition.ProfileDefinition{
2458
+ Metrics: []ddprofiledefinition.MetricsConfig{
2459
+ {
2460
+ Table: ddprofiledefinition.SymbolConfig{
2461
+ OID: "1.3.6.1.2.1.2.2",
2462
+ Name: "ifTable",
2463
+ },
2464
+ Symbols: []ddprofiledefinition.SymbolConfig{
2465
+ {
2466
+ OID: "1.3.6.1.2.1.2.2.1.14",
2467
+ Name: "ifInErrors",
2468
+ },
2469
+ },
2470
+ MetricTags: []ddprofiledefinition.MetricTagConfig{
2471
+ {
2472
+ Tag: "interface",
2473
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
2474
+ OID: "1.3.6.1.2.1.31.1.1.1.1",
2475
+ Name: "ifName",
2476
+ },
2477
+ Table: "ifXTable",
2478
+ },
2479
+ {
2480
+ Tag: "interface",
2481
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
2482
+ OID: "1.3.6.1.2.1.2.2.1.2",
2483
+ Name: "ifDescr",
2484
+ },
2485
+ // Same table tag
2486
+ },
2487
+ },
2488
+ },
2489
+ },
2490
+ },
2491
+ },
2492
+ setupMock: func(m *snmpmock.MockHandler) {
2493
+ // Walk ifTable
2494
+ expectSNMPWalk(m, gosnmp.Version2c, "1.3.6.1.2.1.2.2", []gosnmp.SnmpPDU{
2495
+ createCounter32PDU("1.3.6.1.2.1.2.2.1.14.1", 10),
2496
+ createStringPDU("1.3.6.1.2.1.2.2.1.2.1", "eth0-description"),
2497
+ createCounter32PDU("1.3.6.1.2.1.2.2.1.14.2", 20),
2498
+ createStringPDU("1.3.6.1.2.1.2.2.1.2.2", "eth1-description"),
2499
+ createCounter32PDU("1.3.6.1.2.1.2.2.1.14.3", 30),
2500
+ createStringPDU("1.3.6.1.2.1.2.2.1.2.3", "eth2-description"),
2501
+ })
2502
+ // Walk ifName column from ifXTable
2503
+ expectSNMPWalk(m, gosnmp.Version2c, "1.3.6.1.2.1.31.1.1.1.1", []gosnmp.SnmpPDU{
2504
+ createStringPDU("1.3.6.1.2.1.31.1.1.1.1.1", "GigabitEthernet0/0"),
2505
+ createStringPDU("1.3.6.1.2.1.31.1.1.1.1.2", "GigabitEthernet0/1"),
2506
+ // No entry for index 3 - missing cross-table data
2507
+ })
2508
+ },
2509
+ expectedResult: []ddsnmp.Metric{
2510
+ {
2511
+ Name: "ifInErrors",
2512
+ Value: 10,
2513
+ Tags: map[string]string{"interface": "GigabitEthernet0/0"}, // Uses ifName (cross-table)
2514
+ MetricType: "rate",
2515
+ IsTable: true,
2516
+ },
2517
+ {
2518
+ Name: "ifInErrors",
2519
+ Value: 20,
2520
+ Tags: map[string]string{"interface": "GigabitEthernet0/1"}, // Uses ifName (cross-table)
2521
+ MetricType: "rate",
2522
+ IsTable: true,
2523
+ },
2524
+ {
2525
+ Name: "ifInErrors",
2526
+ Value: 30,
2527
+ Tags: map[string]string{"interface": "eth2-description"}, // Falls back to ifDescr (same-table)
2528
+ MetricType: "rate",
2529
+ IsTable: true,
2530
+ },
2531
+ },
2532
+ expectedError: false,
2533
+ },
2534
+ "tag precedence with same name - respects profile order": {
2535
+ profile: &ddsnmp.Profile{
2536
+ SourceFile: "test-profile.yaml",
2537
+ Definition: &ddprofiledefinition.ProfileDefinition{
2538
+ Metrics: []ddprofiledefinition.MetricsConfig{
2539
+ {
2540
+ Table: ddprofiledefinition.SymbolConfig{
2541
+ OID: "1.3.6.1.2.1.2.2",
2542
+ Name: "ifTable",
2543
+ },
2544
+ Symbols: []ddprofiledefinition.SymbolConfig{
2545
+ {
2546
+ OID: "1.3.6.1.2.1.2.2.1.10",
2547
+ Name: "ifInOctets",
2548
+ },
2549
+ },
2550
+ MetricTags: []ddprofiledefinition.MetricTagConfig{
2551
+ {
2552
+ Tag: "interface",
2553
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
2554
+ OID: "1.3.6.1.2.1.31.1.1.1.1",
2555
+ Name: "ifName",
2556
+ },
2557
+ Table: "ifXTable",
2558
+ },
2559
+ {
2560
+ Tag: "interface",
2561
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
2562
+ OID: "1.3.6.1.2.1.2.2.1.2",
2563
+ Name: "ifDescr",
2564
+ },
2565
+ },
2566
+ {
2567
+ Tag: "if_type",
2568
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
2569
+ OID: "1.3.6.1.2.1.2.2.1.3",
2570
+ Name: "ifType",
2571
+ },
2572
+ },
2573
+ },
2574
+ },
2575
+ },
2576
+ },
2577
+ },
2578
+ setupMock: func(m *snmpmock.MockHandler) {
2579
+ // Walk ifTable
2580
+ expectSNMPWalk(m, gosnmp.Version2c, "1.3.6.1.2.1.2.2", []gosnmp.SnmpPDU{
2581
+ // Interface 1 - has both ifName and ifDescr
2582
+ createCounter32PDU("1.3.6.1.2.1.2.2.1.10.1", 1000),
2583
+ createStringPDU("1.3.6.1.2.1.2.2.1.2.1", "eth0-description"),
2584
+ createIntegerPDU("1.3.6.1.2.1.2.2.1.3.1", 6),
2585
+ // Interface 2 - has only ifDescr
2586
+ createCounter32PDU("1.3.6.1.2.1.2.2.1.10.2", 2000),
2587
+ createStringPDU("1.3.6.1.2.1.2.2.1.2.2", "eth1-description"),
2588
+ createIntegerPDU("1.3.6.1.2.1.2.2.1.3.2", 6),
2589
+ })
2590
+ // Walk ifName column
2591
+ expectSNMPWalk(m, gosnmp.Version2c, "1.3.6.1.2.1.31.1.1.1.1", []gosnmp.SnmpPDU{
2592
+ createStringPDU("1.3.6.1.2.1.31.1.1.1.1.1", "GigE0/0"),
2593
+ // No entry for index 2 - simulating missing ifName
2594
+ })
2595
+ },
2596
+ expectedResult: []ddsnmp.Metric{
2597
+ {
2598
+ Name: "ifInOctets",
2599
+ Value: 1000,
2600
+ Tags: map[string]string{
2601
+ "interface": "GigE0/0", // Uses ifName (first in order)
2602
+ "if_type": "6",
2603
+ },
2604
+ MetricType: "rate",
2605
+ IsTable: true,
2606
+ },
2607
+ {
2608
+ Name: "ifInOctets",
2609
+ Value: 2000,
2610
+ Tags: map[string]string{
2611
+ "interface": "eth1-description", // Falls back to ifDescr
2612
+ "if_type": "6",
2613
+ },
2614
+ MetricType: "rate",
2615
+ IsTable: true,
2616
+ },
2617
+ },
2618
+ expectedError: false,
2619
+ },
2620
+ "tag precedence with same name - swapped order": {
2621
+ profile: &ddsnmp.Profile{
2622
+ SourceFile: "test-profile.yaml",
2623
+ Definition: &ddprofiledefinition.ProfileDefinition{
2624
+ Metrics: []ddprofiledefinition.MetricsConfig{
2625
+ {
2626
+ Table: ddprofiledefinition.SymbolConfig{
2627
+ OID: "1.3.6.1.2.1.2.2",
2628
+ Name: "ifTable",
2629
+ },
2630
+ Symbols: []ddprofiledefinition.SymbolConfig{
2631
+ {
2632
+ OID: "1.3.6.1.2.1.2.2.1.10",
2633
+ Name: "ifInOctets",
2634
+ },
2635
+ },
2636
+ MetricTags: []ddprofiledefinition.MetricTagConfig{
2637
+ {
2638
+ Tag: "interface",
2639
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
2640
+ OID: "1.3.6.1.2.1.2.2.1.2",
2641
+ Name: "ifDescr",
2642
+ },
2643
+ // Same table tag FIRST this time
2644
+ },
2645
+ {
2646
+ Tag: "interface",
2647
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
2648
+ OID: "1.3.6.1.2.1.31.1.1.1.1",
2649
+ Name: "ifName",
2650
+ },
2651
+ Table: "ifXTable",
2652
+ // Cross-table tag SECOND
2653
+ },
2654
+ },
2655
+ },
2656
+ },
2657
+ },
2658
+ },
2659
+ setupMock: func(m *snmpmock.MockHandler) {
2660
+ // Walk ifTable
2661
+ expectSNMPWalk(m, gosnmp.Version2c, "1.3.6.1.2.1.2.2", []gosnmp.SnmpPDU{
2662
+ createCounter32PDU("1.3.6.1.2.1.2.2.1.10.1", 1000),
2663
+ createStringPDU("1.3.6.1.2.1.2.2.1.2.1", "eth0-description"),
2664
+ createCounter32PDU("1.3.6.1.2.1.2.2.1.10.2", 2000),
2665
+ createStringPDU("1.3.6.1.2.1.2.2.1.2.2", ""), // Empty ifDescr
2666
+ })
2667
+ // Walk ifName column
2668
+ expectSNMPWalk(m, gosnmp.Version2c, "1.3.6.1.2.1.31.1.1.1.1", []gosnmp.SnmpPDU{
2669
+ createStringPDU("1.3.6.1.2.1.31.1.1.1.1.1", "GigE0/0"),
2670
+ createStringPDU("1.3.6.1.2.1.31.1.1.1.1.2", "GigE0/1"),
2671
+ })
2672
+ },
2673
+ expectedResult: []ddsnmp.Metric{
2674
+ {
2675
+ Name: "ifInOctets",
2676
+ Value: 1000,
2677
+ Tags: map[string]string{"interface": "eth0-description"}, // Uses ifDescr (first in order)
2678
+ MetricType: "rate",
2679
+ IsTable: true,
2680
+ },
2681
+ {
2682
+ Name: "ifInOctets",
2683
+ Value: 2000,
2684
+ Tags: map[string]string{"interface": "GigE0/1"}, // Falls back to ifName when ifDescr is empty
2685
+ MetricType: "rate",
2686
+ IsTable: true,
2687
+ },
2688
+ },
2689
+ expectedError: false,
2690
+ },
2691
+ "tag precedence with index fallback": {
2692
+ profile: &ddsnmp.Profile{
2693
+ SourceFile: "test-profile.yaml",
2694
+ Definition: &ddprofiledefinition.ProfileDefinition{
2695
+ Metrics: []ddprofiledefinition.MetricsConfig{
2696
+ {
2697
+ Table: ddprofiledefinition.SymbolConfig{
2698
+ OID: "1.3.6.1.2.1.2.2",
2699
+ Name: "ifTable",
2700
+ },
2701
+ Symbols: []ddprofiledefinition.SymbolConfig{
2702
+ {
2703
+ OID: "1.3.6.1.2.1.2.2.1.10",
2704
+ Name: "ifInOctets",
2705
+ },
2706
+ },
2707
+ MetricTags: []ddprofiledefinition.MetricTagConfig{
2708
+ {
2709
+ Tag: "interface_name",
2710
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
2711
+ OID: "1.3.6.1.2.1.31.1.1.1.1",
2712
+ Name: "ifName",
2713
+ },
2714
+ Table: "ifXTable",
2715
+ },
2716
+ {
2717
+ Tag: "interface_name",
2718
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
2719
+ OID: "1.3.6.1.2.1.2.2.1.2",
2720
+ Name: "ifDescr",
2721
+ },
2722
+ },
2723
+ {
2724
+ Index: 1,
2725
+ Tag: "interface_name",
2726
+ },
2727
+ },
2728
+ },
2729
+ },
2730
+ },
2731
+ },
2732
+ setupMock: func(m *snmpmock.MockHandler) {
2733
+ // Walk ifTable
2734
+ expectSNMPWalk(m, gosnmp.Version2c, "1.3.6.1.2.1.2.2", []gosnmp.SnmpPDU{
2735
+ // Interface 1 - has both ifName and ifDescr
2736
+ createCounter32PDU("1.3.6.1.2.1.2.2.1.10.1", 1000),
2737
+ createStringPDU("1.3.6.1.2.1.2.2.1.2.1", "eth0"),
2738
+ // Interface 2 - has only ifDescr
2739
+ createCounter32PDU("1.3.6.1.2.1.2.2.1.10.2", 2000),
2740
+ createStringPDU("1.3.6.1.2.1.2.2.1.2.2", "eth1"),
2741
+ // Interface 3 - has empty ifDescr
2742
+ createCounter32PDU("1.3.6.1.2.1.2.2.1.10.3", 3000),
2743
+ createStringPDU("1.3.6.1.2.1.2.2.1.2.3", ""),
2744
+ })
2745
+ // Walk ifName column
2746
+ expectSNMPWalk(m, gosnmp.Version2c, "1.3.6.1.2.1.31.1.1.1.1", []gosnmp.SnmpPDU{
2747
+ createStringPDU("1.3.6.1.2.1.31.1.1.1.1.1", "GigE0/0"),
2748
+ // No entry for index 2
2749
+ // No entry for index 3
2750
+ })
2751
+ },
2752
+ expectedResult: []ddsnmp.Metric{
2753
+ {
2754
+ Name: "ifInOctets",
2755
+ Value: 1000,
2756
+ Tags: map[string]string{"interface_name": "GigE0/0"}, // Uses ifName
2757
+ MetricType: "rate",
2758
+ IsTable: true,
2759
+ },
2760
+ {
2761
+ Name: "ifInOctets",
2762
+ Value: 2000,
2763
+ Tags: map[string]string{"interface_name": "eth1"}, // Falls back to ifDescr
2764
+ MetricType: "rate",
2765
+ IsTable: true,
2766
+ },
2767
+ {
2768
+ Name: "ifInOctets",
2769
+ Value: 3000,
2770
+ Tags: map[string]string{"interface_name": "3"}, // Falls back to index
2771
+ MetricType: "rate",
2772
+ IsTable: true,
2773
+ },
2774
+ },
2775
+ expectedError: false,
2776
+ },
2777
2778
"basic index tag": {
2779
profile: &ddsnmp.Profile{
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/table_row_processor.go
+36
-47
@@ -28,10 +28,10 @@ type (
28
}
29
// tableRowProcessingContext contains context needed for processing a row
30
tableRowProcessingContext struct {
31
- config ddprofiledefinition.MetricsConfig
32
- columnOIDs map[string]ddprofiledefinition.SymbolConfig
33
- sameTableTagOIDs map[string][]ddprofiledefinition.MetricTagConfig
34
- crossTableCtx *crossTableContext
31
+ config ddprofiledefinition.MetricsConfig
32
+ columnOIDs map[string]ddprofiledefinition.SymbolConfig
33
+ crossTableCtx *crossTableContext
34
+ orderedTags []orderedTagConfig
35
}
36
)
37
@@ -53,62 +53,51 @@ func (p *tableRowProcessor) processRow(row *tableRowData, ctx *tableRowProcessin
53
}
54
55
func (p *tableRowProcessor) processRowTags(row *tableRowData, ctx *tableRowProcessingContext) error {
56
- p.processSameTableTags(row, ctx.sameTableTagOIDs)
57
-
58
- if ctx.crossTableCtx != nil {
59
- p.processCrossTableTags(row, ctx)
56
+ // Process tags in the order they appear in the profile
57
+ for _, orderedTag := range ctx.orderedTags {
58
+ switch orderedTag.tagType {
59
+ case tagTypeSameTable:
60
+ p.processSingleSameTableTag(row, orderedTag.config)
61
+ case tagTypeCrossTable:
62
+ if ctx.crossTableCtx != nil {
63
+ p.processSingleCrossTableTag(row, orderedTag.config, ctx)
64
+ }
65
+ case tagTypeIndex:
66
+ p.processSingleIndexTag(row, orderedTag.config)
67
+ }
68
}
69
62
- p.processIndexBasedTags(row, ctx.config.MetricTags)
63
-
70
return nil
71
}
72
67
-func (p *tableRowProcessor) processSameTableTags(row *tableRowData, tagColumnOIDs map[string][]ddprofiledefinition.MetricTagConfig) {
68
- for columnOID, tagConfigs := range tagColumnOIDs {
69
- pdu, ok := row.pdus[columnOID]
70
- if !ok {
71
- continue
72
- }
73
-
74
- ta := tagAdder{tags: row.tags}
73
+func (p *tableRowProcessor) processSingleSameTableTag(row *tableRowData, tagCfg ddprofiledefinition.MetricTagConfig) {
74
+ columnOID := trimOID(tagCfg.Symbol.OID)
75
+ pdu, ok := row.pdus[columnOID]
76
+ if !ok {
77
+ return
78
+ }
79
76
- for _, tagCfg := range tagConfigs {
77
- if err := p.tagProc.processTag(tagCfg, pdu, ta); err != nil {
78
- p.log.Debugf("Error processing tag %s: %v", tagCfg.Tag, err)
79
- continue
80
- }
81
- }
80
+ ta := tagAdder{tags: row.tags}
81
+ if err := p.tagProc.processTag(tagCfg, pdu, ta); err != nil {
82
+ p.log.Debugf("Error processing tag %s: %v", tagCfg.Tag, err)
83
}
84
}
85
85
-func (p *tableRowProcessor) processCrossTableTags(row *tableRowData, ctx *tableRowProcessingContext) {
86
- for _, tagCfg := range ctx.config.MetricTags {
87
- if !p.crossTableResolver.isCrossTable(tagCfg, ctx.config.Table.Name) {
88
- continue
89
- }
90
-
91
- if err := p.crossTableResolver.resolveCrossTableTag(tagCfg, row.index, ctx.crossTableCtx); err != nil {
92
- p.log.Debugf("Error resolving cross-table tag %s: %v", tagCfg.Tag, err)
93
- continue
94
- }
86
+func (p *tableRowProcessor) processSingleCrossTableTag(row *tableRowData, tagCfg ddprofiledefinition.MetricTagConfig, ctx *tableRowProcessingContext) {
87
+ if err := p.crossTableResolver.resolveCrossTableTag(tagCfg, row.index, ctx.crossTableCtx); err != nil {
88
+ p.log.Debugf("Error resolving cross-table tag %s: %v", tagCfg.Tag, err)
89
}
90
}
91
98
-func (p *tableRowProcessor) processIndexBasedTags(row *tableRowData, metricTags []ddprofiledefinition.MetricTagConfig) {
99
- for _, tagCfg := range metricTags {
100
- if tagCfg.Index == 0 {
101
- continue
102
- }
103
-
104
- tagName, indexValue, ok := p.processIndexTag(tagCfg, row.index)
105
- if !ok {
106
- p.log.Debugf("Cannot extract position %d from index %s", tagCfg.Index, row.index)
107
- continue
108
- }
109
-
110
- row.tags[tagName] = indexValue
92
+func (p *tableRowProcessor) processSingleIndexTag(row *tableRowData, tagCfg ddprofiledefinition.MetricTagConfig) {
93
+ tagName, indexValue, ok := p.processIndexTag(tagCfg, row.index)
94
+ if !ok {
95
+ p.log.Debugf("Cannot extract position %d from index %s", tagCfg.Index, row.index)
96
+ return
97
}
98
+
99
+ ta := tagAdder{tags: row.tags}
100
+ ta.addTag(tagName, indexValue)
101
}
102
103
func (p *tableRowProcessor) processIndexTag(cfg ddprofiledefinition.MetricTagConfig, index string) (string, string, bool) {