@cryptotaxi247 / netdata / commits / 28ec237e2

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) {