@cryptotaxi247 / netdata / commits / c61dce679

improve(go.d/snmp): add Go template-based metric transformations for SNMP profiles (#20528)

Ilya Mashchenko committed Jun 20, 2025 at 09:04 UTC c61dce679027ecd590d10154f421552fafd2a823
12 files changed +938 -186
src/go/plugin/go.d/collector/snmp/charts.go
+7 -4
@@ -8,8 +8,8 @@ import (
8 "strings"
9
10 "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
11 + "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
12 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
12 - "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector"
13 )
14
15 const (
@@ -310,7 +310,7 @@ func newUserInputChart(cfg ChartConfig) (*module.Chart, error) {
310 return chart, nil
311 }
312
313 -func (c *Collector) addProfileScalarMetricChart(m ddsnmpcollector.Metric) {
313 +func (c *Collector) addProfileScalarMetricChart(m ddsnmp.Metric) {
314 if m.Name == "" {
315 return
316 }
@@ -333,6 +333,9 @@ func (c *Collector) addProfileScalarMetricChart(m ddsnmpcollector.Metric) {
333 if chart.Fam == "" {
334 chart.Fam = m.Name
335 }
336 + if chart.Units == "bits/s" {
337 + chart.Type = module.Area
338 + }
339
340 tags := map[string]string{
341 "vendor": c.sysInfo.Organization,
@@ -361,7 +364,7 @@ func (c *Collector) addProfileScalarMetricChart(m ddsnmpcollector.Metric) {
364 }
365 }
366
364 -func (c *Collector) addProfileTableMetricChart(m ddsnmpcollector.Metric) {
367 +func (c *Collector) addProfileTableMetricChart(m ddsnmp.Metric) {
368 if m.Name == "" {
369 return
370 }
@@ -415,7 +418,7 @@ func (c *Collector) addProfileTableMetricChart(m ddsnmpcollector.Metric) {
418 }
419 }
420
418 -func dimAlgoFromDdSnmpType(m ddsnmpcollector.Metric) module.DimAlgo {
421 +func dimAlgoFromDdSnmpType(m ddsnmp.Metric) module.DimAlgo {
422 if m.MetricType == ddprofiledefinition.ProfileMetricTypeGauge {
423 return module.Absolute
424 }
src/go/plugin/go.d/collector/snmp/collect_profiles.go
+4 -3
@@ -7,6 +7,7 @@ import (
7 "sort"
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/collector/snmp/ddsnmp/ddsnmpcollector"
12 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/metrix"
13 )
@@ -31,7 +32,7 @@ func (c *Collector) collectProfiles(mx map[string]int64) error {
32 return nil
33 }
34
34 -func (c *Collector) collectProfileScalarMetrics(mx map[string]int64, pms []*ddsnmpcollector.ProfileMetrics) {
35 +func (c *Collector) collectProfileScalarMetrics(mx map[string]int64, pms []*ddsnmp.ProfileMetrics) {
36 for _, pm := range pms {
37 for _, m := range pm.Metrics {
38 if m.IsTable || m.Name == "" {
@@ -56,7 +57,7 @@ func (c *Collector) collectProfileScalarMetrics(mx map[string]int64, pms []*ddsn
57 }
58 }
59
59 -func (c *Collector) collectProfileTableMetrics(mx map[string]int64, pms []*ddsnmpcollector.ProfileMetrics) {
60 +func (c *Collector) collectProfileTableMetrics(mx map[string]int64, pms []*ddsnmp.ProfileMetrics) {
61 seen := make(map[string]bool)
62
63 for _, pm := range pms {
@@ -92,7 +93,7 @@ func (c *Collector) collectProfileTableMetrics(mx map[string]int64, pms []*ddsnm
93 }
94 }
95
95 -func tableMetricKey(m ddsnmpcollector.Metric) string {
96 +func tableMetricKey(m ddsnmp.Metric) string {
97 keys := make([]string, 0, len(m.Tags))
98 for k := range m.Tags {
99 keys = append(keys, k)
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/metrics.go
+10 -5
@@ -9,6 +9,7 @@ import (
9 "maps"
10 "regexp"
11 "slices"
12 + "text/template"
13 )
14
15 // ProfileMetricType metric type used to override default type of the metric
@@ -78,11 +79,15 @@ type SymbolConfig struct {
79 // When empty, by default, the metric type is derived from SNMP OID value type.
80 // Valid `metric_type` types: `gauge`, `rate`, `monotonic_count`, `monotonic_count_and_rate`
81 // Deprecated types: `counter` (use `rate` instead), percent (use `scale_factor` instead)
81 - MetricType ProfileMetricType `yaml:"metric_type,omitempty" json:"metric_type,omitempty"`
82 - Unit string `yaml:"unit,omitempty" json:"unit,omitempty"`
83 - Description string `yaml:"description,omitempty" json:"description,omitempty"`
84 - Family string `yaml:"family,omitempty" json:"family,omitempty"`
85 - Mapping map[string]string `yaml:"mapping,omitempty" json:"mapping,omitempty"`
82 + MetricType ProfileMetricType `yaml:"metric_type,omitempty" json:"metric_type,omitempty"`
83 +
84 + Unit string `yaml:"unit,omitempty" json:"unit,omitempty"`
85 + Description string `yaml:"description,omitempty" json:"description,omitempty"`
86 + Family string `yaml:"family,omitempty" json:"family,omitempty"`
87 +
88 + Mapping map[string]string `yaml:"mapping,omitempty" json:"mapping,omitempty"`
89 + Transform string `yaml:"transform,omitempty" json:"transform,omitempty"`
90 + TransformCompiled *template.Template `yaml:"-" json:"-"`
91 }
92
93 // Clone creates a duplicate of this SymbolConfig
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collect_scalar.go
+13 -5
@@ -15,7 +15,7 @@ import (
15 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
16 )
17
18 -func (c *Collector) collectScalarMetrics(prof *ddsnmp.Profile) ([]Metric, error) {
18 +func (c *Collector) collectScalarMetrics(prof *ddsnmp.Profile) ([]ddsnmp.Metric, error) {
19 var oids []string
20 var missingOIDs []string
21
@@ -46,7 +46,7 @@ func (c *Collector) collectScalarMetrics(prof *ddsnmp.Profile) ([]Metric, error)
46 return nil, err
47 }
48
49 - metrics := make([]Metric, 0, len(prof.Definition.Metrics))
49 + metrics := make([]ddsnmp.Metric, 0, len(prof.Definition.Metrics))
50 var errs []error
51
52 for _, cfg := range prof.Definition.Metrics {
@@ -73,7 +73,7 @@ func (c *Collector) collectScalarMetrics(prof *ddsnmp.Profile) ([]Metric, error)
73 return metrics, nil
74 }
75
76 -func (c *Collector) collectScalarMetric(cfg ddprofiledefinition.MetricsConfig, pdus map[string]gosnmp.SnmpPDU) (*Metric, error) {
76 +func (c *Collector) collectScalarMetric(cfg ddprofiledefinition.MetricsConfig, pdus map[string]gosnmp.SnmpPDU) (*ddsnmp.Metric, error) {
77 pdu, ok := pdus[trimOID(cfg.Symbol.OID)]
78 if !ok {
79 return nil, nil
@@ -92,7 +92,7 @@ func (c *Collector) collectScalarMetric(cfg ddprofiledefinition.MetricsConfig, p
92 }
93 }
94
95 - return &Metric{
95 + m := &ddsnmp.Metric{
96 Name: cfg.Symbol.Name,
97 Value: value,
98 StaticTags: ternary(len(staticTags) > 0, staticTags, nil),
@@ -101,7 +101,15 @@ func (c *Collector) collectScalarMetric(cfg ddprofiledefinition.MetricsConfig, p
101 Family: cfg.Symbol.Family,
102 Mappings: convSymMappingToNumeric(cfg.Symbol),
103 MetricType: getMetricType(cfg.Symbol, pdu),
104 - }, nil
104 + }
105 +
106 + if cfg.Symbol.TransformCompiled != nil {
107 + if err := applyTransform(m, cfg.Symbol); err != nil {
108 + return nil, fmt.Errorf("failed to apply transform for scalar metric '%s': %w", cfg.Symbol.Name, err)
109 + }
110 + }
111 +
112 + return m, nil
113 }
114
115 func processSymbolValue(sym ddprofiledefinition.SymbolConfig, pdu gosnmp.SnmpPDU) (int64, error) {
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collect_table.go
+39 -9
@@ -21,7 +21,7 @@ type tableWalkResult struct {
21 config ddprofiledefinition.MetricsConfig
22 }
23
24 -func (c *Collector) collectTableMetrics(prof *ddsnmp.Profile) ([]Metric, error) {
24 +func (c *Collector) collectTableMetrics(prof *ddsnmp.Profile) ([]ddsnmp.Metric, error) {
25 walkResults, err := c.walkTablesAsNeeded(prof)
26 if err != nil {
27 return nil, err
@@ -118,8 +118,8 @@ func (c *Collector) walkTablesAsNeeded(prof *ddsnmp.Profile) ([]tableWalkResult,
118 }
119
120 // Phase 2: Process walked data
121 -func (c *Collector) processTableWalkResults(walkResults []tableWalkResult) ([]Metric, error) {
122 - var metrics []Metric
121 +func (c *Collector) processTableWalkResults(walkResults []tableWalkResult) ([]ddsnmp.Metric, error) {
122 + var metrics []ddsnmp.Metric
123 var errs []error
124
125 // Build a map for quick lookup of walked data by table OID
@@ -171,7 +171,11 @@ func (c *Collector) processTableWalkResults(walkResults []tableWalkResult) ([]Me
171 }
172
173 // Process a single table's data
174 -func (c *Collector) processTableData(cfg ddprofiledefinition.MetricsConfig, pdus map[string]gosnmp.SnmpPDU, allWalkedData map[string]map[string]gosnmp.SnmpPDU, tableNameToOID map[string]string) ([]Metric, error) {
174 +func (c *Collector) processTableData(
175 + cfg ddprofiledefinition.MetricsConfig,
176 + pdus map[string]gosnmp.SnmpPDU,
177 + allWalkedData map[string]map[string]gosnmp.SnmpPDU,
178 + tableNameToOID map[string]string) ([]ddsnmp.Metric, error) {
179 columnOIDs := buildColumnOIDs(cfg)
180 tagColumnOIDs := buildTagColumnOIDs(cfg)
181
@@ -212,7 +216,8 @@ func (c *Collector) processTableData(cfg ddprofiledefinition.MetricsConfig, pdus
216 }
217 }
218
215 - var metrics []Metric
219 + var metrics []ddsnmp.Metric
220 + var errs []error
221
222 for index, rowPDUs := range rows {
223 rowTags := make(map[string]string)
@@ -328,7 +333,7 @@ func (c *Collector) processTableData(cfg ddprofiledefinition.MetricsConfig, pdus
333 continue
334 }
335
331 - metric := Metric{
336 + metric := ddsnmp.Metric{
337 Name: sym.Name,
338 Value: value,
339 StaticTags: ternary(len(rowStaticTags) > 0, rowStaticTags, nil),
@@ -341,10 +346,22 @@ func (c *Collector) processTableData(cfg ddprofiledefinition.MetricsConfig, pdus
346 IsTable: true,
347 }
348
349 + if sym.TransformCompiled != nil {
350 + if err := applyTransform(&metric, sym); err != nil {
351 + errs = append(errs, fmt.Errorf("failed to apply transform for table metric '%s:%s': %w",
352 + cfg.Table.Name, sym.Name, err))
353 + continue
354 + }
355 + }
356 +
357 metrics = append(metrics, metric)
358 }
359 }
360
361 + if len(errs) > 0 {
362 + c.log.Warningf("failed to collect table metrics: %v", errors.Join(errs...))
363 + }
364 +
365 deps := extractTableDependencies(cfg, tableNameToOID)
366
367 // Cache the processed data
@@ -378,7 +395,7 @@ func (c *Collector) collectTableWithCache(
395 cachedOIDs map[string]map[string]string,
396 cachedTags map[string]map[string]string,
397 columnOIDs map[string]ddprofiledefinition.SymbolConfig,
381 -) ([]Metric, error) {
398 +) ([]ddsnmp.Metric, error) {
399 var oidsToGet []string
400
401 for _, columns := range cachedOIDs {
@@ -411,7 +428,8 @@ func (c *Collector) collectTableWithCache(
428 }
429 }
430
414 - var metrics []Metric
431 + var metrics []ddsnmp.Metric
432 + var errs []error
433
434 for index, columns := range cachedOIDs {
435 rowTags := make(map[string]string)
@@ -440,7 +458,7 @@ func (c *Collector) collectTableWithCache(
458 continue
459 }
460
443 - metric := Metric{
461 + metric := ddsnmp.Metric{
462 Name: sym.Name,
463 Value: value,
464 StaticTags: ternary(len(rowStaticTags) > 0, rowStaticTags, nil),
@@ -453,10 +471,22 @@ func (c *Collector) collectTableWithCache(
471 IsTable: true,
472 }
473
474 + if sym.TransformCompiled != nil {
475 + if err := applyTransform(&metric, sym); err != nil {
476 + errs = append(errs, fmt.Errorf("failed to apply transform for metric '%s:%s': %w",
477 + cfg.Table.Name, sym.Name, err))
478 + continue
479 + }
480 + }
481 +
482 metrics = append(metrics, metric)
483 }
484 }
485
486 + if len(errs) > 0 {
487 + c.log.Warningf("failed to collect table metrics: %v", errors.Join(errs...))
488 + }
489 +
490 return metrics, nil
491 }
492
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector.go
+6 -37
@@ -15,29 +15,6 @@ import (
15
16 "github.com/netdata/netdata/go/plugins/logger"
17 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
18 - "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
19 -)
20 -
21 -type (
22 - ProfileMetrics struct {
23 - Source string
24 - DeviceMetadata map[string]string
25 - Tags map[string]string
26 - Metrics []Metric
27 - }
28 - Metric struct {
29 - Profile *ProfileMetrics
30 - Name string
31 - Description string
32 - Family string
33 - Unit string
34 - MetricType ddprofiledefinition.ProfileMetricType
35 - StaticTags map[string]string
36 - Tags map[string]string
37 - Mappings map[int64]string
38 - IsTable bool
39 - Value int64
40 - }
18 )
19
20 func New(snmpClient gosnmp.Handler, profiles []*ddsnmp.Profile, log *logger.Logger) *Collector {
@@ -75,8 +52,8 @@ type (
52 }
53 )
54
78 -func (c *Collector) Collect() ([]*ProfileMetrics, error) {
79 - var metrics []*ProfileMetrics
55 +func (c *Collector) Collect() ([]*ddsnmp.ProfileMetrics, error) {
56 + var metrics []*ddsnmp.ProfileMetrics
57 var errs []error
58
59 if expired := c.tableCache.clearExpired(); len(expired) > 0 {
@@ -103,7 +80,7 @@ func (c *Collector) Collect() ([]*ProfileMetrics, error) {
80 return metrics, nil
81 }
82
106 -func (c *Collector) collectProfile(ps *profileState) (*ProfileMetrics, error) {
83 +func (c *Collector) collectProfile(ps *profileState) (*ddsnmp.ProfileMetrics, error) {
84 if !ps.initialized {
85 globalTag, err := c.collectGlobalTags(ps.profile)
86 if err != nil {
@@ -120,7 +97,7 @@ func (c *Collector) collectProfile(ps *profileState) (*ProfileMetrics, error) {
97 ps.initialized = true
98 }
99
123 - var metrics []Metric
100 + var metrics []ddsnmp.Metric
101
102 scalarMetrics, err := c.collectScalarMetrics(ps.profile)
103 if err != nil {
@@ -136,7 +113,7 @@ func (c *Collector) collectProfile(ps *profileState) (*ProfileMetrics, error) {
113 metrics = append(metrics, tableMetrics...)
114 }
115
139 - pm := &ProfileMetrics{
116 + pm := &ddsnmp.ProfileMetrics{
117 Source: ps.profile.SourceFile,
118 DeviceMetadata: maps.Clone(ps.deviceMetadata),
119 Tags: maps.Clone(ps.globalTags),
@@ -149,7 +126,7 @@ func (c *Collector) collectProfile(ps *profileState) (*ProfileMetrics, error) {
126 return pm, nil
127 }
128
152 -func (c *Collector) updateMetrics(pms []*ProfileMetrics) {
129 +func (c *Collector) updateMetrics(pms []*ddsnmp.ProfileMetrics) {
130 // Find device vendor and type from any profile that has them.
131 // Multiple profiles can be loaded for a single device (e.g., base profiles, generic MIB profiles),
132 // but only device-specific profiles contain vendor/type information.
@@ -185,14 +162,6 @@ func (c *Collector) updateMetrics(pms []*ProfileMetrics) {
162 for k, v := range m.Tags {
163 m.Tags[k] = metricMetaReplacer.Replace(v)
164 }
188 - if v, ok := m.Tags["_unit"]; ok && v != "" {
189 - delete(m.Tags, "_unit")
190 - m.Unit = metricMetaReplacer.Replace(v)
191 - }
192 - if v, ok := m.Tags["_metric_suffix"]; ok && v != "" {
193 - delete(m.Tags, "_metric_suffix")
194 - m.Name += "_" + metricMetaReplacer.Replace(v)
195 - }
165 }
166 }
167 }
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_test.go
+654 -100
@@ -5,6 +5,7 @@ package ddsnmpcollector
5 import (
6 "errors"
7 "regexp"
8 + "strings"
9 "testing"
10 "time"
11
@@ -24,7 +25,7 @@ func TestCollector_Collect_ScalarMetrics(t *testing.T) {
25 tests := map[string]struct {
26 profiles []*ddsnmp.Profile
27 setupMock func(m *snmpmock.MockHandler)
27 - expectedResult []*ProfileMetrics
28 + expectedResult []*ddsnmp.ProfileMetrics
29 expectedError bool
30 errorContains string
31 enableCache bool // Whether to enable cache for this test
@@ -70,11 +71,11 @@ func TestCollector_Collect_ScalarMetrics(t *testing.T) {
71 }, nil,
72 )
73 },
73 - expectedResult: []*ProfileMetrics{
74 + expectedResult: []*ddsnmp.ProfileMetrics{
75 {
76 Source: "test-profile.yaml",
77 DeviceMetadata: nil,
77 - Metrics: []Metric{
78 + Metrics: []ddsnmp.Metric{
79 {
80 Name: "sysUpTime",
81 Value: 123456,
@@ -138,12 +139,12 @@ func TestCollector_Collect_ScalarMetrics(t *testing.T) {
139 }, nil,
140 )
141 },
141 - expectedResult: []*ProfileMetrics{
142 + expectedResult: []*ddsnmp.ProfileMetrics{
143 {
144 Source: "test-profile.yaml",
145 DeviceMetadata: nil,
146 Tags: map[string]string{"device_vendor": "Cisco IOS"},
146 - Metrics: []Metric{
147 + Metrics: []ddsnmp.Metric{
148 {
149 Name: "sysUpTime",
150 Value: 123456,
@@ -212,14 +213,14 @@ func TestCollector_Collect_ScalarMetrics(t *testing.T) {
213 }, nil,
214 )
215 },
215 - expectedResult: []*ProfileMetrics{
216 + expectedResult: []*ddsnmp.ProfileMetrics{
217 {
218 Source: "test-profile.yaml",
219 DeviceMetadata: map[string]string{
220 "vendor": "dell",
221 "serial_number": "ABC123",
222 },
222 - Metrics: []Metric{
223 + Metrics: []ddsnmp.Metric{
224 {
225 Name: "sysUpTime",
226 Value: 123456,
@@ -261,11 +262,11 @@ func TestCollector_Collect_ScalarMetrics(t *testing.T) {
262 }, nil,
263 )
264 },
264 - expectedResult: []*ProfileMetrics{
265 + expectedResult: []*ddsnmp.ProfileMetrics{
266 {
267 Source: "test-profile.yaml",
268 DeviceMetadata: nil,
268 - Metrics: []Metric{
269 + Metrics: []ddsnmp.Metric{
270 {
271 Name: "memoryKilobytes",
272 Value: 1024000, // 1024 * 1000
@@ -307,11 +308,11 @@ func TestCollector_Collect_ScalarMetrics(t *testing.T) {
308 }, nil,
309 )
310 },
310 - expectedResult: []*ProfileMetrics{
311 + expectedResult: []*ddsnmp.ProfileMetrics{
312 {
313 Source: "test-profile.yaml",
314 DeviceMetadata: nil,
314 - Metrics: []Metric{
315 + Metrics: []ddsnmp.Metric{
316 {
317 Name: "temperature",
318 Value: 25,
@@ -378,12 +379,12 @@ func TestCollector_Collect_ScalarMetrics(t *testing.T) {
379 }, nil,
380 )
381 },
381 - expectedResult: []*ProfileMetrics{
382 + expectedResult: []*ddsnmp.ProfileMetrics{
383 {
384 Source: "test-profile.yaml",
385 DeviceMetadata: nil,
386 Tags: map[string]string{"device_type": "router"},
386 - Metrics: []Metric{
387 + Metrics: []ddsnmp.Metric{
388 {
389 Name: "sysUpTime",
390 Value: 123456,
@@ -424,10 +425,10 @@ func TestCollector_Collect_ScalarMetrics(t *testing.T) {
425 }, nil,
426 )
427 },
427 - expectedResult: []*ProfileMetrics{
428 + expectedResult: []*ddsnmp.ProfileMetrics{
429 {
430 DeviceMetadata: nil,
430 - Metrics: []Metric{},
431 + Metrics: []ddsnmp.Metric{},
432 },
433 },
434 expectedError: false,
@@ -471,10 +472,10 @@ func TestCollector_Collect_ScalarMetrics(t *testing.T) {
472 setupMock: func(m *snmpmock.MockHandler) {
473 m.EXPECT().MaxOids().Return(10).AnyTimes()
474 },
474 - expectedResult: []*ProfileMetrics{
475 + expectedResult: []*ddsnmp.ProfileMetrics{
476 {
477 DeviceMetadata: nil,
477 - Metrics: []Metric{},
478 + Metrics: []ddsnmp.Metric{},
479 },
480 },
481 expectedError: false,
@@ -528,11 +529,11 @@ func TestCollector_Collect_ScalarMetrics(t *testing.T) {
529 errors.New("connection refused"),
530 )
531 },
531 - expectedResult: []*ProfileMetrics{
532 + expectedResult: []*ddsnmp.ProfileMetrics{
533 {
534 Source: "profile1.yaml",
535 DeviceMetadata: nil,
535 - Metrics: []Metric{
536 + Metrics: []ddsnmp.Metric{
537 {
538 Name: "sysUpTime",
539 Value: 123456,
@@ -599,7 +600,7 @@ func TestCollector_Collect_ValueMappings(t *testing.T) {
600 tests := map[string]struct {
601 profiles []*ddsnmp.Profile
602 setupMock func(m *snmpmock.MockHandler)
602 - expectedResult []*ProfileMetrics
603 + expectedResult []*ddsnmp.ProfileMetrics
604 expectedError bool
605 errorContains string
606 }{
@@ -638,11 +639,11 @@ func TestCollector_Collect_ValueMappings(t *testing.T) {
639 }, nil,
640 )
641 },
641 - expectedResult: []*ProfileMetrics{
642 + expectedResult: []*ddsnmp.ProfileMetrics{
643 {
644 Source: "test-profile.yaml",
645 DeviceMetadata: nil,
645 - Metrics: []Metric{
646 + Metrics: []ddsnmp.Metric{
647 {
648 Name: "clusterHealth",
649 Value: 1,
@@ -695,11 +696,11 @@ func TestCollector_Collect_ValueMappings(t *testing.T) {
696 }, nil,
697 )
698 },
698 - expectedResult: []*ProfileMetrics{
699 + expectedResult: []*ddsnmp.ProfileMetrics{
700 {
701 Source: "test-profile.yaml",
702 DeviceMetadata: nil,
702 - Metrics: []Metric{
703 + Metrics: []ddsnmp.Metric{
704 {
705 Name: "ifOperStatus",
706 Value: 2,
@@ -753,11 +754,11 @@ func TestCollector_Collect_ValueMappings(t *testing.T) {
754 }, nil,
755 )
756 },
756 - expectedResult: []*ProfileMetrics{
757 + expectedResult: []*ddsnmp.ProfileMetrics{
758 {
759 Source: "test-profile.yaml",
760 DeviceMetadata: nil,
760 - Metrics: []Metric{
761 + Metrics: []ddsnmp.Metric{
762 {
763 Name: "fanStatus",
764 Value: 2,
@@ -808,11 +809,11 @@ func TestCollector_Collect_ValueMappings(t *testing.T) {
809 }, nil,
810 )
811 },
811 - expectedResult: []*ProfileMetrics{
812 + expectedResult: []*ddsnmp.ProfileMetrics{
813 {
814 Source: "test-profile.yaml",
815 DeviceMetadata: nil,
815 - Metrics: []Metric{
816 + Metrics: []ddsnmp.Metric{
817 {
818 Name: "ifAdminStatus",
819 Value: 0, // mapped from 2 -> 0
@@ -936,11 +937,11 @@ func TestCollector_Collect_ValueMappings(t *testing.T) {
937 }, nil,
938 )
939 },
939 - expectedResult: []*ProfileMetrics{
940 + expectedResult: []*ddsnmp.ProfileMetrics{
941 {
942 Source: "test-profile.yaml",
943 DeviceMetadata: nil,
943 - Metrics: []Metric{
944 + Metrics: []ddsnmp.Metric{
945 {
946 Name: "sysUpTime",
947 Value: 123456,
@@ -987,11 +988,11 @@ func TestCollector_Collect_ValueMappings(t *testing.T) {
988 }, nil,
989 )
990 },
990 - expectedResult: []*ProfileMetrics{
991 + expectedResult: []*ddsnmp.ProfileMetrics{
992 {
993 Source: "test-profile.yaml",
994 DeviceMetadata: nil,
994 - Metrics: []Metric{
995 + Metrics: []ddsnmp.Metric{
996 {
997 Name: "ifAdminStatus",
998 Value: 1,
@@ -1043,11 +1044,11 @@ func TestCollector_Collect_ValueMappings(t *testing.T) {
1044 }, nil,
1045 )
1046 },
1046 - expectedResult: []*ProfileMetrics{
1047 + expectedResult: []*ddsnmp.ProfileMetrics{
1048 {
1049 Source: "test-profile.yaml",
1050 DeviceMetadata: nil,
1050 - Metrics: []Metric{
1051 + Metrics: []ddsnmp.Metric{
1052 {
1053 Name: "upsBasicBatteryStatus",
1054 Value: 1,
@@ -1114,7 +1115,7 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
1115 tests := map[string]struct {
1116 profiles []*ddsnmp.Profile
1117 setupMock func(m *snmpmock.MockHandler)
1117 - expectedResult []*ProfileMetrics
1118 + expectedResult []*ddsnmp.ProfileMetrics
1119 expectedError bool
1120 errorContains string
1121 enableCache bool
@@ -1178,11 +1179,11 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
1179 }, nil,
1180 )
1181 },
1181 - expectedResult: []*ProfileMetrics{
1182 + expectedResult: []*ddsnmp.ProfileMetrics{
1183 {
1184 Source: "test-profile.yaml",
1185 DeviceMetadata: nil,
1185 - Metrics: []Metric{
1186 + Metrics: []ddsnmp.Metric{
1187 {
1188 Name: "ifInOctets",
1189 Value: 1000,
@@ -1291,11 +1292,11 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
1292 }, nil,
1293 )
1294 },
1294 - expectedResult: []*ProfileMetrics{
1295 + expectedResult: []*ddsnmp.ProfileMetrics{
1296 {
1297 Source: "test-profile.yaml",
1298 DeviceMetadata: nil,
1298 - Metrics: []Metric{
1299 + Metrics: []ddsnmp.Metric{
1300 // Row 1
1301 {
1302 Name: "ifInOctets",
@@ -1426,11 +1427,11 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
1427 }, nil,
1428 )
1429 },
1429 - expectedResult: []*ProfileMetrics{
1430 + expectedResult: []*ddsnmp.ProfileMetrics{
1431 {
1432 Source: "test-profile.yaml",
1433 DeviceMetadata: nil,
1433 - Metrics: []Metric{
1434 + Metrics: []ddsnmp.Metric{
1435 {
1436 Name: "ifInErrors",
1437 Value: 10,
@@ -1531,11 +1532,11 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
1532 }, nil,
1533 )
1534 },
1534 - expectedResult: []*ProfileMetrics{
1535 + expectedResult: []*ddsnmp.ProfileMetrics{
1536 {
1537 Source: "test-profile.yaml",
1538 DeviceMetadata: nil,
1538 - Metrics: []Metric{
1539 + Metrics: []ddsnmp.Metric{
1540 // Row 1 - complete
1541 {
1542 Name: "ifInOctets",
@@ -1632,11 +1633,11 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
1633 }, nil,
1634 )
1635 },
1635 - expectedResult: []*ProfileMetrics{
1636 + expectedResult: []*ddsnmp.ProfileMetrics{
1637 {
1638 Source: "test-profile.yaml",
1639 DeviceMetadata: nil,
1639 - Metrics: []Metric{
1640 + Metrics: []ddsnmp.Metric{
1641 {
1642 Name: "ifInOctets",
1643 Value: 1000,
@@ -1713,11 +1714,11 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
1714 }, nil,
1715 )
1716 },
1716 - expectedResult: []*ProfileMetrics{
1717 + expectedResult: []*ddsnmp.ProfileMetrics{
1718 {
1719 Source: "test-profile.yaml",
1720 DeviceMetadata: nil,
1720 - Metrics: []Metric{
1721 + Metrics: []ddsnmp.Metric{
1722 {
1723 Name: "ifInErrors",
1724 Value: 10,
@@ -1815,11 +1816,11 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
1816 }, nil,
1817 )
1818 },
1818 - expectedResult: []*ProfileMetrics{
1819 + expectedResult: []*ddsnmp.ProfileMetrics{
1820 {
1821 Source: "test-profile.yaml",
1822 DeviceMetadata: nil,
1822 - Metrics: []Metric{
1823 + Metrics: []ddsnmp.Metric{
1824 {
1825 Name: "ifInOctets",
1826 Value: 1000,
@@ -1874,11 +1875,11 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
1875 []gosnmp.SnmpPDU{}, nil,
1876 )
1877 },
1877 - expectedResult: []*ProfileMetrics{
1878 + expectedResult: []*ddsnmp.ProfileMetrics{
1879 {
1880 Source: "test-profile.yaml",
1881 DeviceMetadata: nil,
1881 - Metrics: []Metric{},
1882 + Metrics: []ddsnmp.Metric{},
1883 },
1884 },
1885 expectedError: false,
@@ -1967,11 +1968,11 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
1968 }, nil,
1969 )
1970 },
1970 - expectedResult: []*ProfileMetrics{
1971 + expectedResult: []*ddsnmp.ProfileMetrics{
1972 {
1973 Source: "test-profile.yaml",
1974 DeviceMetadata: nil,
1974 - Metrics: []Metric{
1975 + Metrics: []ddsnmp.Metric{
1976 {
1977 Name: "cpmCPUMemoryUsed",
1978 Value: 2097152, // 2048 * 1024
@@ -2033,11 +2034,11 @@ func TestCollector_Collect_TableMetricsBasic(t *testing.T) {
2034 }, nil,
2035 )
2036 },
2036 - expectedResult: []*ProfileMetrics{
2037 + expectedResult: []*ddsnmp.ProfileMetrics{
2038 {
2039 Source: "test-profile.yaml",
2040 DeviceMetadata: nil,
2040 - Metrics: []Metric{
2041 + Metrics: []ddsnmp.Metric{
2042 {
2043 Name: "ifInOctets",
2044 Value: 1000,
@@ -2106,7 +2107,7 @@ func TestCollector_Collect_CrossTableTags(t *testing.T) {
2107 tests := map[string]struct {
2108 profiles []*ddsnmp.Profile
2109 setupMock func(m *snmpmock.MockHandler)
2109 - expectedResult []*ProfileMetrics
2110 + expectedResult []*ddsnmp.ProfileMetrics
2111 expectedError bool
2112 errorContains string
2113 enableCache bool
@@ -2190,11 +2191,11 @@ func TestCollector_Collect_CrossTableTags(t *testing.T) {
2191 }, nil,
2192 )
2193 },
2193 - expectedResult: []*ProfileMetrics{
2194 + expectedResult: []*ddsnmp.ProfileMetrics{
2195 {
2196 Source: "test-profile.yaml",
2197 DeviceMetadata: nil,
2197 - Metrics: []Metric{
2198 + Metrics: []ddsnmp.Metric{
2199 {
2200 Name: "ifInErrors",
2201 Value: 10,
@@ -2298,11 +2299,11 @@ func TestCollector_Collect_CrossTableTags(t *testing.T) {
2299 }, nil,
2300 )
2301 },
2301 - expectedResult: []*ProfileMetrics{
2302 + expectedResult: []*ddsnmp.ProfileMetrics{
2303 {
2304 Source: "test-profile.yaml",
2305 DeviceMetadata: nil,
2305 - Metrics: []Metric{
2306 + Metrics: []ddsnmp.Metric{
2307 {
2308 Name: "ifInErrors",
2309 Value: 10,
@@ -2427,11 +2428,11 @@ func TestCollector_Collect_CrossTableTags(t *testing.T) {
2428 }, nil,
2429 )
2430 },
2430 - expectedResult: []*ProfileMetrics{
2431 + expectedResult: []*ddsnmp.ProfileMetrics{
2432 {
2433 Source: "test-profile.yaml",
2434 DeviceMetadata: nil,
2434 - Metrics: []Metric{
2435 + Metrics: []ddsnmp.Metric{
2436 {
2437 Name: "ifInErrors",
2438 Value: 10,
@@ -2568,11 +2569,11 @@ func TestCollector_Collect_CrossTableTags(t *testing.T) {
2569 }, nil,
2570 )
2571 },
2571 - expectedResult: []*ProfileMetrics{
2572 + expectedResult: []*ddsnmp.ProfileMetrics{
2573 {
2574 Source: "test-profile.yaml",
2575 DeviceMetadata: nil,
2575 - Metrics: []Metric{
2576 + Metrics: []ddsnmp.Metric{
2577 {
2578 Name: "cieIfResetCount",
2579 Value: 5,
@@ -2662,11 +2663,11 @@ func TestCollector_Collect_CrossTableTags(t *testing.T) {
2663 }, nil,
2664 )
2665 },
2665 - expectedResult: []*ProfileMetrics{
2666 + expectedResult: []*ddsnmp.ProfileMetrics{
2667 {
2668 Source: "test-profile.yaml",
2669 DeviceMetadata: nil,
2669 - Metrics: []Metric{
2670 + Metrics: []ddsnmp.Metric{
2671 {
2672 Name: "ifInErrors",
2673 Value: 10,
@@ -2756,11 +2757,11 @@ func TestCollector_Collect_CrossTableTags(t *testing.T) {
2757 }, nil,
2758 )
2759 },
2759 - expectedResult: []*ProfileMetrics{
2760 + expectedResult: []*ddsnmp.ProfileMetrics{
2761 {
2762 Source: "test-profile.yaml",
2763 DeviceMetadata: nil,
2763 - Metrics: []Metric{
2764 + Metrics: []ddsnmp.Metric{
2765 {
2766 Name: "ifInOctets",
2767 Value: 1000,
@@ -2872,11 +2873,11 @@ func TestCollector_Collect_CrossTableTags(t *testing.T) {
2873 }, nil,
2874 )
2875 },
2875 - expectedResult: []*ProfileMetrics{
2876 + expectedResult: []*ddsnmp.ProfileMetrics{
2877 {
2878 Source: "test-profile.yaml",
2879 DeviceMetadata: nil,
2879 - Metrics: []Metric{
2880 + Metrics: []ddsnmp.Metric{
2881 {
2882 Name: "ifInErrors",
2883 Value: 10,
@@ -2962,11 +2963,11 @@ func TestCollector_Collect_CrossTableTags(t *testing.T) {
2963 }, nil,
2964 )
2965 },
2965 - expectedResult: []*ProfileMetrics{
2966 + expectedResult: []*ddsnmp.ProfileMetrics{
2967 {
2968 Source: "test-profile.yaml",
2969 DeviceMetadata: nil,
2969 - Metrics: []Metric{
2970 + Metrics: []ddsnmp.Metric{
2971 {
2972 Name: "ifInErrors",
2973 Value: 10,
@@ -3036,7 +3037,7 @@ func TestCollector_Collect_IndexBasedAndTransforms(t *testing.T) {
3037 tests := map[string]struct {
3038 profiles []*ddsnmp.Profile
3039 setupMock func(m *snmpmock.MockHandler)
3039 - expectedResult []*ProfileMetrics
3040 + expectedResult []*ddsnmp.ProfileMetrics
3041 expectedError bool
3042 errorContains string
3043 enableCache bool
@@ -3088,11 +3089,11 @@ func TestCollector_Collect_IndexBasedAndTransforms(t *testing.T) {
3089 }, nil,
3090 )
3091 },
3091 - expectedResult: []*ProfileMetrics{
3092 + expectedResult: []*ddsnmp.ProfileMetrics{
3093 {
3094 Source: "test-profile.yaml",
3095 DeviceMetadata: nil,
3095 - Metrics: []Metric{
3096 + Metrics: []ddsnmp.Metric{
3097 {
3098 Name: "ipSystemStatsHCInReceives",
3099 Value: 1000,
@@ -3167,11 +3168,11 @@ func TestCollector_Collect_IndexBasedAndTransforms(t *testing.T) {
3168 }, nil,
3169 )
3170 },
3170 - expectedResult: []*ProfileMetrics{
3171 + expectedResult: []*ddsnmp.ProfileMetrics{
3172 {
3173 Source: "test-profile.yaml",
3174 DeviceMetadata: nil,
3174 - Metrics: []Metric{
3175 + Metrics: []ddsnmp.Metric{
3176 {
3177 Name: "cfwConnectionStatValue",
3178 Value: 100,
@@ -3256,11 +3257,11 @@ func TestCollector_Collect_IndexBasedAndTransforms(t *testing.T) {
3257 }, nil,
3258 )
3259 },
3259 - expectedResult: []*ProfileMetrics{
3260 + expectedResult: []*ddsnmp.ProfileMetrics{
3261 {
3262 Source: "test-profile.yaml",
3263 DeviceMetadata: nil,
3263 - Metrics: []Metric{
3264 + Metrics: []ddsnmp.Metric{
3265 {
3266 Name: "ipSystemStatsHCInReceives",
3267 Value: 1000,
@@ -3337,11 +3338,11 @@ func TestCollector_Collect_IndexBasedAndTransforms(t *testing.T) {
3338 }, nil,
3339 )
3340 },
3340 - expectedResult: []*ProfileMetrics{
3341 + expectedResult: []*ddsnmp.ProfileMetrics{
3342 {
3343 Source: "test-profile.yaml",
3344 DeviceMetadata: nil,
3344 - Metrics: []Metric{
3345 + Metrics: []ddsnmp.Metric{
3346 {
3347 Name: "ifInOctets",
3348 Value: 1000,
@@ -3435,11 +3436,11 @@ func TestCollector_Collect_IndexBasedAndTransforms(t *testing.T) {
3436 }, nil,
3437 )
3438 },
3438 - expectedResult: []*ProfileMetrics{
3439 + expectedResult: []*ddsnmp.ProfileMetrics{
3440 {
3441 Source: "test-profile.yaml",
3442 DeviceMetadata: nil,
3442 - Metrics: []Metric{
3443 + Metrics: []ddsnmp.Metric{
3444 {
3445 Name: "cpiPduBranchCurrent",
3446 Value: 150,
@@ -3525,11 +3526,11 @@ func TestCollector_Collect_IndexBasedAndTransforms(t *testing.T) {
3526 }, nil,
3527 )
3528 },
3528 - expectedResult: []*ProfileMetrics{
3529 + expectedResult: []*ddsnmp.ProfileMetrics{
3530 {
3531 Source: "test-profile.yaml",
3532 DeviceMetadata: nil,
3532 - Metrics: []Metric{
3533 + Metrics: []ddsnmp.Metric{
3534 {
3535 Name: "customMetric",
3536 Value: 500,
@@ -3619,11 +3620,11 @@ func TestCollector_Collect_IndexBasedAndTransforms(t *testing.T) {
3620 }, nil,
3621 )
3622 },
3622 - expectedResult: []*ProfileMetrics{
3623 + expectedResult: []*ddsnmp.ProfileMetrics{
3624 {
3625 Source: "test-profile.yaml",
3626 DeviceMetadata: nil,
3626 - Metrics: []Metric{
3627 + Metrics: []ddsnmp.Metric{
3628 {
3629 Name: "ifInOctets",
3630 Value: 1000,
@@ -3699,7 +3700,7 @@ func TestCollector_Collect_OpaqueTypes(t *testing.T) {
3700 tests := map[string]struct {
3701 profiles []*ddsnmp.Profile
3702 setupMock func(m *snmpmock.MockHandler)
3702 - expectedResult []*ProfileMetrics
3703 + expectedResult []*ddsnmp.ProfileMetrics
3704 expectedError bool
3705 errorContains string
3706 enableCache bool
@@ -3760,11 +3761,11 @@ func TestCollector_Collect_OpaqueTypes(t *testing.T) {
3761 }, nil,
3762 )
3763 },
3763 - expectedResult: []*ProfileMetrics{
3764 + expectedResult: []*ddsnmp.ProfileMetrics{
3765 {
3766 Source: "test-profile.yaml",
3767 DeviceMetadata: nil,
3767 - Metrics: []Metric{
3768 + Metrics: []ddsnmp.Metric{
3769 {
3770 Name: "cpmCPULoadAvg1min",
3771 Value: 0, // 0.75 truncated to int64
@@ -3841,11 +3842,11 @@ func TestCollector_Collect_OpaqueTypes(t *testing.T) {
3842 }, nil,
3843 )
3844 },
3844 - expectedResult: []*ProfileMetrics{
3845 + expectedResult: []*ddsnmp.ProfileMetrics{
3846 {
3847 Source: "test-profile.yaml",
3848 DeviceMetadata: nil,
3848 - Metrics: []Metric{
3849 + Metrics: []ddsnmp.Metric{
3850 {
3851 Name: "cpqHeSysBatteryVoltage",
3852 Value: 12, // 12.6 truncated to int64
@@ -3922,7 +3923,7 @@ func TestCollector_Collect_TableCaching(t *testing.T) {
3923 tests := map[string]struct {
3924 profiles []*ddsnmp.Profile
3925 setupMock func(m *snmpmock.MockHandler)
3925 - expectedResult []*ProfileMetrics
3926 + expectedResult []*ddsnmp.ProfileMetrics
3927 expectedError bool
3928 errorContains string
3929 enableCache bool
@@ -4071,11 +4072,11 @@ func TestCollector_Collect_TableCaching(t *testing.T) {
4072 }, nil,
4073 ).Times(1)
4074 },
4074 - expectedResult: []*ProfileMetrics{
4075 + expectedResult: []*ddsnmp.ProfileMetrics{
4076 {
4077 Source: "test-profile.yaml",
4078 DeviceMetadata: nil,
4078 - Metrics: []Metric{
4079 + Metrics: []ddsnmp.Metric{
4080 {
4081 Name: "ifInOctets",
4082 Value: 1200, // Latest value
@@ -4205,11 +4206,11 @@ func TestCollector_Collect_TableCaching(t *testing.T) {
4206 ),
4207 )
4208 },
4208 - expectedResult: []*ProfileMetrics{
4209 + expectedResult: []*ddsnmp.ProfileMetrics{
4210 {
4211 Source: "test-profile.yaml",
4212 DeviceMetadata: nil,
4212 - Metrics: []Metric{
4213 + Metrics: []ddsnmp.Metric{
4214 {
4215 Name: "ifInOctets",
4216 Value: 1200,
@@ -4372,11 +4373,11 @@ func TestCollector_Collect_TableCaching(t *testing.T) {
4373 }, nil,
4374 ).Times(1)
4375 },
4375 - expectedResult: []*ProfileMetrics{
4376 + expectedResult: []*ddsnmp.ProfileMetrics{
4377 {
4378 Source: "test-profile.yaml",
4379 DeviceMetadata: nil,
4379 - Metrics: []Metric{
4380 + Metrics: []ddsnmp.Metric{
4381 // From first config
4382 {
4383 Name: "ifInOctets",
@@ -4436,7 +4437,7 @@ func TestCollector_Collect_TableCaching(t *testing.T) {
4437 collector.tableCache.setTTL(0, 0) // Disable cache
4438 }
4439
4439 - var result []*ProfileMetrics
4440 + var result []*ddsnmp.ProfileMetrics
4441 var err error
4442
4443 // Perform multiple collections to test caching behavior
@@ -4483,6 +4484,559 @@ func TestCollector_Collect_TableCaching(t *testing.T) {
4484 }
4485 }
4486
4487 +func TestCollector_Collect_MetricTransforms(t *testing.T) {
4488 + tests := map[string]struct {
4489 + profiles []*ddsnmp.Profile
4490 + setupMock func(m *snmpmock.MockHandler)
4491 + expectedResult []*ddsnmp.ProfileMetrics
4492 + expectedError bool
4493 + errorContains string
4494 + }{
4495 + "basic metric name transformation": {
4496 + profiles: []*ddsnmp.Profile{
4497 + {
4498 + SourceFile: "test-profile.yaml",
4499 + Definition: &ddprofiledefinition.ProfileDefinition{
4500 + Metrics: []ddprofiledefinition.MetricsConfig{
4501 + {
4502 + Symbol: ddprofiledefinition.SymbolConfig{
4503 + OID: "1.3.6.1.4.1.12345.1.1",
4504 + Name: "sensorValue",
4505 + Transform: `{{- setName .Metric "customName" -}}`,
4506 + },
4507 + },
4508 + },
4509 + },
4510 + },
4511 + },
4512 + setupMock: func(m *snmpmock.MockHandler) {
4513 + m.EXPECT().MaxOids().Return(10).AnyTimes()
4514 + m.EXPECT().Get([]string{"1.3.6.1.4.1.12345.1.1"}).Return(
4515 + &gosnmp.SnmpPacket{
4516 + Variables: []gosnmp.SnmpPDU{
4517 + {
4518 + Name: "1.3.6.1.4.1.12345.1.1",
4519 + Type: gosnmp.Gauge32,
4520 + Value: uint(100),
4521 + },
4522 + },
4523 + }, nil,
4524 + )
4525 + },
4526 + expectedResult: []*ddsnmp.ProfileMetrics{
4527 + {
4528 + Source: "test-profile.yaml",
4529 + DeviceMetadata: nil,
4530 + Metrics: []ddsnmp.Metric{
4531 + {
4532 + Name: "customName",
4533 + Value: 100,
4534 + MetricType: "gauge",
4535 + },
4536 + },
4537 + },
4538 + },
4539 + expectedError: false,
4540 + },
4541 + "table metric with sensor type transformation": {
4542 + profiles: []*ddsnmp.Profile{
4543 + {
4544 + SourceFile: "test-profile.yaml",
4545 + Definition: &ddprofiledefinition.ProfileDefinition{
4546 + Metrics: []ddprofiledefinition.MetricsConfig{
4547 + {
4548 + Table: ddprofiledefinition.SymbolConfig{
4549 + OID: "1.3.6.1.4.1.14988.1.1.3.100",
4550 + Name: "mtxrHlTable",
4551 + },
4552 + Symbols: []ddprofiledefinition.SymbolConfig{
4553 + {
4554 + OID: "1.3.6.1.4.1.14988.1.1.3.100.1.3",
4555 + Name: "mtxrHlSensorValue",
4556 + Transform: `
4557 +{{- $sensorType := index .Metric.Tags "sensor_type" | default "" -}}
4558 +{{- if eq $sensorType "1" -}}
4559 + {{- setName .Metric (printf "%s_temperature" .Metric.Name) -}}
4560 + {{- setUnit .Metric "celsius" -}}
4561 + {{- setFamily .Metric "Health/Temperature" -}}
4562 +{{- else if eq $sensorType "3" -}}
4563 + {{- setName .Metric (printf "%s_voltage" .Metric.Name) -}}
4564 + {{- setUnit .Metric "volts" -}}
4565 + {{- setValue .Metric (int64 (div (float64 .Metric.Value) 10.0)) -}}
4566 + {{- setFamily .Metric "Health/Power" -}}
4567 +{{- end -}}
4568 +{{- deleteTag .Metric "sensor_type" -}}`,
4569 + },
4570 + },
4571 + MetricTags: []ddprofiledefinition.MetricTagConfig{
4572 + {
4573 + Tag: "sensor_name",
4574 + Symbol: ddprofiledefinition.SymbolConfigCompat{
4575 + OID: "1.3.6.1.4.1.14988.1.1.3.100.1.2",
4576 + Name: "mtxrHlSensorName",
4577 + },
4578 + },
4579 + {
4580 + Tag: "sensor_type",
4581 + Symbol: ddprofiledefinition.SymbolConfigCompat{
4582 + OID: "1.3.6.1.4.1.14988.1.1.3.100.1.4",
4583 + Name: "mtxrHlSensorType",
4584 + },
4585 + },
4586 + },
4587 + },
4588 + },
4589 + },
4590 + },
4591 + },
4592 + setupMock: func(m *snmpmock.MockHandler) {
4593 + m.EXPECT().MaxOids().Return(10).AnyTimes()
4594 + m.EXPECT().Version().Return(gosnmp.Version2c)
4595 + m.EXPECT().BulkWalkAll("1.3.6.1.4.1.14988.1.1.3.100").Return(
4596 + []gosnmp.SnmpPDU{
4597 + // Temperature sensor (type 1)
4598 + {
4599 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.3.1",
4600 + Type: gosnmp.Integer,
4601 + Value: 25,
4602 + },
4603 + {
4604 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.2.1",
4605 + Type: gosnmp.OctetString,
4606 + Value: []byte("cpu-temperature"),
4607 + },
4608 + {
4609 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.4.1",
4610 + Type: gosnmp.Integer,
4611 + Value: 1,
4612 + },
4613 + // Voltage sensor (type 3)
4614 + {
4615 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.3.2",
4616 + Type: gosnmp.Integer,
4617 + Value: 120, // 12.0V in decivolts
4618 + },
4619 + {
4620 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.2.2",
4621 + Type: gosnmp.OctetString,
4622 + Value: []byte("input-voltage"),
4623 + },
4624 + {
4625 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.4.2",
4626 + Type: gosnmp.Integer,
4627 + Value: 3,
4628 + },
4629 + }, nil,
4630 + )
4631 + },
4632 + expectedResult: []*ddsnmp.ProfileMetrics{
4633 + {
4634 + Source: "test-profile.yaml",
4635 + DeviceMetadata: nil,
4636 + Metrics: []ddsnmp.Metric{
4637 + {
4638 + Name: "mtxrHlSensorValue_temperature",
4639 + Value: 25,
4640 + Tags: map[string]string{"sensor_name": "cpu-temperature"},
4641 + Unit: "celsius",
4642 + Family: "Health/Temperature",
4643 + MetricType: "gauge",
4644 + IsTable: true,
4645 + },
4646 + {
4647 + Name: "mtxrHlSensorValue_voltage",
4648 + Value: 12, // Converted from decivolts
4649 + Tags: map[string]string{"sensor_name": "input-voltage"},
4650 + Unit: "volts",
4651 + Family: "Health/Power",
4652 + MetricType: "gauge",
4653 + IsTable: true,
4654 + },
4655 + },
4656 + },
4657 + },
4658 + expectedError: false,
4659 + },
4660 + "transformation with value mappings": {
4661 + profiles: []*ddsnmp.Profile{
4662 + {
4663 + SourceFile: "test-profile.yaml",
4664 + Definition: &ddprofiledefinition.ProfileDefinition{
4665 + Metrics: []ddprofiledefinition.MetricsConfig{
4666 + {
4667 + Symbol: ddprofiledefinition.SymbolConfig{
4668 + OID: "1.3.6.1.4.1.12345.1.1",
4669 + Name: "deviceStatus",
4670 + Transform: `
4671 +{{- setMappings .Metric (i64map 0 "down" 1 "up" 2 "testing") -}}`,
4672 + },
4673 + },
4674 + },
4675 + },
4676 + },
4677 + },
4678 + setupMock: func(m *snmpmock.MockHandler) {
4679 + m.EXPECT().MaxOids().Return(10).AnyTimes()
4680 + m.EXPECT().Get([]string{"1.3.6.1.4.1.12345.1.1"}).Return(
4681 + &gosnmp.SnmpPacket{
4682 + Variables: []gosnmp.SnmpPDU{
4683 + {
4684 + Name: "1.3.6.1.4.1.12345.1.1",
4685 + Type: gosnmp.Integer,
4686 + Value: 1,
4687 + },
4688 + },
4689 + }, nil,
4690 + )
4691 + },
4692 + expectedResult: []*ddsnmp.ProfileMetrics{
4693 + {
4694 + Source: "test-profile.yaml",
4695 + DeviceMetadata: nil,
4696 + Metrics: []ddsnmp.Metric{
4697 + {
4698 + Name: "deviceStatus",
4699 + Value: 1,
4700 + Mappings: map[int64]string{
4701 + 0: "down",
4702 + 1: "up",
4703 + 2: "testing",
4704 + },
4705 + MetricType: "gauge",
4706 + },
4707 + },
4708 + },
4709 + },
4710 + expectedError: false,
4711 + },
4712 + "complex transformation with sprig functions": {
4713 + profiles: []*ddsnmp.Profile{
4714 + {
4715 + SourceFile: "test-profile.yaml",
4716 + Definition: &ddprofiledefinition.ProfileDefinition{
4717 + Metrics: []ddprofiledefinition.MetricsConfig{
4718 + {
4719 + Table: ddprofiledefinition.SymbolConfig{
4720 + OID: "1.3.6.1.2.1.2.2",
4721 + Name: "ifTable",
4722 + },
4723 + Symbols: []ddprofiledefinition.SymbolConfig{
4724 + {
4725 + OID: "1.3.6.1.2.1.2.2.1.10",
4726 + Name: "ifInOctets",
4727 + Transform: `
4728 +{{- $ifName := index .Metric.Tags "interface" | lower -}}
4729 +{{- if contains "eth" $ifName -}}
4730 + {{- setFamily .Metric "Interfaces/Ethernet" -}}
4731 +{{- else if contains "lo" $ifName -}}
4732 + {{- setFamily .Metric "Interfaces/Loopback" -}}
4733 +{{- end -}}
4734 +{{- setDesc .Metric (printf "Traffic on %s interface" (index .Metric.Tags "interface")) -}}`,
4735 + },
4736 + },
4737 + MetricTags: []ddprofiledefinition.MetricTagConfig{
4738 + {
4739 + Tag: "interface",
4740 + Symbol: ddprofiledefinition.SymbolConfigCompat{
4741 + OID: "1.3.6.1.2.1.2.2.1.2",
4742 + Name: "ifDescr",
4743 + },
4744 + },
4745 + },
4746 + },
4747 + },
4748 + },
4749 + },
4750 + },
4751 + setupMock: func(m *snmpmock.MockHandler) {
4752 + m.EXPECT().MaxOids().Return(10).AnyTimes()
4753 + m.EXPECT().Version().Return(gosnmp.Version2c)
4754 + m.EXPECT().BulkWalkAll("1.3.6.1.2.1.2.2").Return(
4755 + []gosnmp.SnmpPDU{
4756 + {
4757 + Name: "1.3.6.1.2.1.2.2.1.10.1",
4758 + Type: gosnmp.Counter32,
4759 + Value: uint(1000),
4760 + },
4761 + {
4762 + Name: "1.3.6.1.2.1.2.2.1.2.1",
4763 + Type: gosnmp.OctetString,
4764 + Value: []byte("eth0"),
4765 + },
4766 + {
4767 + Name: "1.3.6.1.2.1.2.2.1.10.2",
4768 + Type: gosnmp.Counter32,
4769 + Value: uint(2000),
4770 + },
4771 + {
4772 + Name: "1.3.6.1.2.1.2.2.1.2.2",
4773 + Type: gosnmp.OctetString,
4774 + Value: []byte("lo0"),
4775 + },
4776 + }, nil,
4777 + )
4778 + },
4779 + expectedResult: []*ddsnmp.ProfileMetrics{
4780 + {
4781 + Source: "test-profile.yaml",
4782 + DeviceMetadata: nil,
4783 + Metrics: []ddsnmp.Metric{
4784 + {
4785 + Name: "ifInOctets",
4786 + Value: 1000,
4787 + Tags: map[string]string{"interface": "eth0"},
4788 + Family: "Interfaces/Ethernet",
4789 + Description: "Traffic on eth0 interface",
4790 + MetricType: "rate",
4791 + IsTable: true,
4792 + },
4793 + {
4794 + Name: "ifInOctets",
4795 + Value: 2000,
4796 + Tags: map[string]string{"interface": "lo0"},
4797 + Family: "Interfaces/Loopback",
4798 + Description: "Traffic on lo0 interface",
4799 + MetricType: "rate",
4800 + IsTable: true,
4801 + },
4802 + },
4803 + },
4804 + },
4805 + expectedError: false,
4806 + },
4807 + "transformation with invalid template": {
4808 + profiles: []*ddsnmp.Profile{
4809 + {
4810 + SourceFile: "test-profile.yaml",
4811 + Definition: &ddprofiledefinition.ProfileDefinition{
4812 + Metrics: []ddprofiledefinition.MetricsConfig{
4813 + {
4814 + Symbol: ddprofiledefinition.SymbolConfig{
4815 + OID: "1.3.6.1.4.1.12345.1.1",
4816 + Name: "testMetric",
4817 + Transform: `{{- invalid template syntax {{`,
4818 + TransformCompiled: nil, // Will fail compilation
4819 + },
4820 + },
4821 + },
4822 + },
4823 + },
4824 + },
4825 + setupMock: func(m *snmpmock.MockHandler) {
4826 + // This test should fail during profile validation, not during collection
4827 + },
4828 + expectedResult: nil,
4829 + expectedError: true,
4830 + errorContains: "template",
4831 + },
4832 + "mikrotik sensor table real-world example": {
4833 + profiles: []*ddsnmp.Profile{
4834 + {
4835 + SourceFile: "mikrotik-profile.yaml",
4836 + Definition: &ddprofiledefinition.ProfileDefinition{
4837 + Metrics: []ddprofiledefinition.MetricsConfig{
4838 + {
4839 + Table: ddprofiledefinition.SymbolConfig{
4840 + OID: "1.3.6.1.4.1.14988.1.1.3.100",
4841 + Name: "mtxrHlTable",
4842 + },
4843 + Symbols: []ddprofiledefinition.SymbolConfig{
4844 + {
4845 + OID: "1.3.6.1.4.1.14988.1.1.3.100.1.3",
4846 + Name: "mtxrHlSensorValue",
4847 + Transform: `
4848 +{{- $config := get (dict
4849 + "1" (dict "name" "temperature" "unit" "celsius" "family" "Health/Temperature")
4850 + "2" (dict "name" "fan_speed" "unit" "rpm" "family" "Health/Cooling")
4851 + "3" (dict "name" "voltage" "unit" "volts" "family" "Health/Power" "divisor" 10.0)
4852 + "6" (dict "name" "sensor_status" "family" "Health/Status"
4853 + "mapping" (i64map 0 "not_ok" 1 "ok"))
4854 +) (index .Metric.Tags "sensor_type" | default "") -}}
4855 +
4856 +{{- if $config -}}
4857 + {{- setName .Metric (printf "%s_%s" .Metric.Name (get $config "name")) -}}
4858 + {{- setFamily .Metric (get $config "family") -}}
4859 + {{- with get $config "unit" -}}{{- setUnit $.Metric . -}}{{- end -}}
4860 + {{- with get $config "divisor" -}}{{- setValue $.Metric (int64 (div (float64 $.Metric.Value) .)) -}}{{- end -}}
4861 + {{- with get $config "mapping" -}}{{- setMappings $.Metric . -}}{{- end -}}
4862 +{{- end -}}
4863 +
4864 +{{- deleteTag .Metric "sensor_type" -}}`,
4865 + },
4866 + },
4867 + MetricTags: []ddprofiledefinition.MetricTagConfig{
4868 + {
4869 + Tag: "sensor_name",
4870 + Symbol: ddprofiledefinition.SymbolConfigCompat{
4871 + OID: "1.3.6.1.4.1.14988.1.1.3.100.1.2",
4872 + Name: "mtxrHlSensorName",
4873 + },
4874 + },
4875 + {
4876 + Tag: "sensor_type",
4877 + Symbol: ddprofiledefinition.SymbolConfigCompat{
4878 + OID: "1.3.6.1.4.1.14988.1.1.3.100.1.4",
4879 + Name: "mtxrHlSensorType",
4880 + },
4881 + },
4882 + },
4883 + },
4884 + },
4885 + },
4886 + },
4887 + },
4888 + setupMock: func(m *snmpmock.MockHandler) {
4889 + m.EXPECT().MaxOids().Return(10).AnyTimes()
4890 + m.EXPECT().Version().Return(gosnmp.Version2c)
4891 + m.EXPECT().BulkWalkAll("1.3.6.1.4.1.14988.1.1.3.100").Return(
4892 + []gosnmp.SnmpPDU{
4893 + // Temperature sensor
4894 + {
4895 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.3.1",
4896 + Type: gosnmp.Integer,
4897 + Value: 45,
4898 + },
4899 + {
4900 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.2.1",
4901 + Type: gosnmp.OctetString,
4902 + Value: []byte("cpu-temperature"),
4903 + },
4904 + {
4905 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.4.1",
4906 + Type: gosnmp.Integer,
4907 + Value: 1,
4908 + },
4909 + // PSU status sensor
4910 + {
4911 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.3.2",
4912 + Type: gosnmp.Integer,
4913 + Value: 1,
4914 + },
4915 + {
4916 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.2.2",
4917 + Type: gosnmp.OctetString,
4918 + Value: []byte("psu1-state"),
4919 + },
4920 + {
4921 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.4.2",
4922 + Type: gosnmp.Integer,
4923 + Value: 6,
4924 + },
4925 + // Voltage sensor
4926 + {
4927 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.3.3",
4928 + Type: gosnmp.Integer,
4929 + Value: 240, // 24.0V
4930 + },
4931 + {
4932 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.2.3",
4933 + Type: gosnmp.OctetString,
4934 + Value: []byte("psu-voltage"),
4935 + },
4936 + {
4937 + Name: "1.3.6.1.4.1.14988.1.1.3.100.1.4.3",
4938 + Type: gosnmp.Integer,
4939 + Value: 3,
4940 + },
4941 + }, nil,
4942 + )
4943 + },
4944 + expectedResult: []*ddsnmp.ProfileMetrics{
4945 + {
4946 + Source: "mikrotik-profile.yaml",
4947 + DeviceMetadata: nil,
4948 + Metrics: []ddsnmp.Metric{
4949 + {
4950 + Name: "mtxrHlSensorValue_temperature",
4951 + Value: 45,
4952 + Tags: map[string]string{"sensor_name": "cpu-temperature"},
4953 + Unit: "celsius",
4954 + Family: "Health/Temperature",
4955 + MetricType: "gauge",
4956 + IsTable: true,
4957 + },
4958 + {
4959 + Name: "mtxrHlSensorValue_sensor_status",
4960 + Value: 1,
4961 + Tags: map[string]string{"sensor_name": "psu1-state"},
4962 + Family: "Health/Status",
4963 + Mappings: map[int64]string{
4964 + 0: "not_ok",
4965 + 1: "ok",
4966 + },
4967 + MetricType: "gauge",
4968 + IsTable: true,
4969 + },
4970 + {
4971 + Name: "mtxrHlSensorValue_voltage",
4972 + Value: 24,
4973 + Tags: map[string]string{"sensor_name": "psu-voltage"},
4974 + Unit: "volts",
4975 + Family: "Health/Power",
4976 + MetricType: "gauge",
4977 + IsTable: true,
4978 + },
4979 + },
4980 + },
4981 + },
4982 + expectedError: false,
4983 + },
4984 + }
4985 +
4986 + for name, tc := range tests {
4987 + t.Run(name, func(t *testing.T) {
4988 + ctrl := gomock.NewController(t)
4989 + defer ctrl.Finish()
4990 +
4991 + mockHandler := snmpmock.NewMockHandler(ctrl)
4992 + tc.setupMock(mockHandler)
4993 +
4994 + // Compile transforms for profiles
4995 + for _, profile := range tc.profiles {
4996 + if err := ddsnmp.CompileTransforms(profile); err != nil {
4997 + if tc.expectedError && tc.errorContains != "" && strings.Contains(err.Error(), tc.errorContains) {
4998 + return // Expected error during compilation
4999 + }
5000 + t.Fatalf("Failed to compile transforms: %v", err)
5001 + }
5002 + }
5003 +
5004 + collector := New(mockHandler, tc.profiles, logger.New())
5005 + collector.DoTableMetrics = true
5006 + collector.tableCache.setTTL(0, 0) // Disable cache
5007 +
5008 + result, err := collector.Collect()
5009 +
5010 + // Clear circular references
5011 + for _, profile := range result {
5012 + for i := range profile.Metrics {
5013 + profile.Metrics[i].Profile = nil
5014 + }
5015 + }
5016 +
5017 + if tc.expectedError {
5018 + assert.Error(t, err)
5019 + if tc.errorContains != "" {
5020 + assert.Contains(t, err.Error(), tc.errorContains)
5021 + }
5022 + } else {
5023 + assert.NoError(t, err)
5024 + }
5025 +
5026 + if tc.expectedResult != nil {
5027 + require.Equal(t, len(tc.expectedResult), len(result))
5028 + for i := range tc.expectedResult {
5029 + assert.Equal(t, tc.expectedResult[i].DeviceMetadata, result[i].DeviceMetadata)
5030 + assert.Equal(t, tc.expectedResult[i].Tags, result[i].Tags)
5031 + assert.ElementsMatch(t, tc.expectedResult[i].Metrics, result[i].Metrics)
5032 + }
5033 + } else {
5034 + assert.Nil(t, result)
5035 + }
5036 + })
5037 + }
5038 +}
5039 +
5040 func mustCompileRegex(pattern string) *regexp.Regexp {
5041 re, err := regexp.Compile(pattern)
5042 if err != nil {
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/utils.go
+19
@@ -3,6 +3,7 @@
3 package ddsnmpcollector
4
5 import (
6 + "bytes"
7 "encoding/hex"
8 "fmt"
9 "regexp"
@@ -12,6 +13,7 @@ import (
13
14 "github.com/gosnmp/gosnmp"
15
16 + "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
17 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
18 )
19
@@ -260,3 +262,20 @@ func mergeTagsWithEmptyFallback(dest, src map[string]string) {
262 }
263 }
264 }
265 +
266 +func applyTransform(metric *ddsnmp.Metric, sym ddprofiledefinition.SymbolConfig) error {
267 + if sym.TransformCompiled == nil {
268 + return nil
269 + }
270 +
271 + type transformCtx struct{ Metric *ddsnmp.Metric }
272 +
273 + ctx := &transformCtx{Metric: metric}
274 +
275 + var buf bytes.Buffer
276 + if err := sym.TransformCompiled.Execute(&buf, ctx); err != nil {
277 + return fmt.Errorf("failed to execute transform template: %w", err)
278 + }
279 +
280 + return nil
281 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/load.go
+4
@@ -93,6 +93,10 @@ func loadProfilesFromDir(dirpath string, extendsPaths multipath.MultiPath) ([]*P
93 log.Warningf("invalid profile '%s': %v", path, err)
94 return nil
95 }
96 + if err := CompileTransforms(profile); err != nil {
97 + log.Warningf("invalid profile '%s': %v", path, err)
98 + return nil
99 + }
100
101 profile.removeConstantMetrics()
102
src/go/plugin/go.d/collector/snmp/ddsnmp/metric.go new
+26
@@ -0,0 +1,26 @@
1 +package ddsnmp
2 +
3 +import (
4 + "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
5 +)
6 +
7 +type ProfileMetrics struct {
8 + Source string
9 + DeviceMetadata map[string]string
10 + Tags map[string]string
11 + Metrics []Metric
12 +}
13 +
14 +type Metric struct {
15 + Profile *ProfileMetrics
16 + Name string
17 + Description string
18 + Family string
19 + Unit string
20 + MetricType ddprofiledefinition.ProfileMetricType
21 + StaticTags map[string]string
22 + Tags map[string]string
23 + Mappings map[int64]string
24 + IsTable bool
25 + Value int64
26 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/transform.go new
+122
@@ -0,0 +1,122 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package ddsnmp
4 +
5 +import (
6 + "errors"
7 + "fmt"
8 + "text/template"
9 +
10 + "github.com/Masterminds/sprig/v3"
11 +)
12 +
13 +// CompileTransforms exported for testing purposes only
14 +func CompileTransforms(prof *Profile) error {
15 + var errs []error
16 +
17 + for i := range prof.Definition.Metrics {
18 + metric := &prof.Definition.Metrics[i]
19 +
20 + switch {
21 + case metric.IsScalar():
22 + if metric.Symbol.Transform == "" {
23 + continue
24 + }
25 + tmpl, err := compileTransform(metric.Symbol.Transform)
26 + if err != nil {
27 + errs = append(errs, fmt.Errorf("metric_transform parsing failed for scalar metric '%s': %v",
28 + metric.Symbol.Name, err))
29 + continue
30 + }
31 + metric.Symbol.TransformCompiled = tmpl
32 + case metric.IsColumn():
33 + for j := range metric.Symbols {
34 + sym := &metric.Symbols[j]
35 + if sym.Transform == "" {
36 + continue
37 + }
38 + tmpl, err := compileTransform(sym.Transform)
39 + if err != nil {
40 + errs = append(errs, fmt.Errorf("metric_transform parsing failed for table '%s' metric '%s': %v",
41 + metric.Table.Name, sym.Name, err))
42 + continue
43 + }
44 + sym.TransformCompiled = tmpl
45 + }
46 + }
47 + }
48 +
49 + if len(errs) > 0 {
50 + return errors.Join(errs...)
51 +
52 + }
53 +
54 + return nil
55 +}
56 +
57 +func compileTransform(transform string) (*template.Template, error) {
58 + return template.New("metric_transform").
59 + Option("missingkey=zero").
60 + Funcs(newMetricTransformFuncMap()).
61 + Parse(transform)
62 +}
63 +
64 +func newMetricTransformFuncMap() template.FuncMap {
65 + fm := sprig.TxtFuncMap()
66 +
67 + extra := map[string]any{
68 + "deleteTag": func(m *Metric, key string) interface{} {
69 + delete(m.Tags, key)
70 + return nil
71 + },
72 + "setName": func(m *Metric, name string) string {
73 + m.Name = name
74 + return ""
75 + },
76 + "setUnit": func(m *Metric, unit string) string {
77 + m.Unit = unit
78 + return ""
79 + },
80 + "setFamily": func(m *Metric, family string) string {
81 + m.Family = family
82 + return ""
83 + },
84 + "setDesc": func(m *Metric, desc string) string {
85 + m.Description = desc
86 + return ""
87 + },
88 + "setValue": func(m *Metric, value int64) string {
89 + m.Value = value
90 + return ""
91 + },
92 + "setMappings": func(m *Metric, mappings map[int64]string) string {
93 + m.Mappings = mappings
94 + return ""
95 + },
96 + "i64map": func(pairs ...any) map[int64]string {
97 + m := make(map[int64]string)
98 + for i := 0; i < len(pairs)-1; i += 2 {
99 + key := int64(0)
100 + switch k := pairs[i].(type) {
101 + case int:
102 + key = int64(k)
103 + case int64:
104 + key = k
105 + case float64:
106 + key = int64(k)
107 + }
108 +
109 + if val, ok := pairs[i+1].(string); ok {
110 + m[key] = val
111 + }
112 + }
113 + return m
114 + },
115 + }
116 +
117 + for name, fn := range extra {
118 + fm[name] = fn
119 + }
120 +
121 + return fm
122 +}
src/go/plugin/go.d/config/go.d/snmp.profiles/default/mikrotik-router.yaml
+34 -23
@@ -174,37 +174,48 @@ metrics:
174 name: mtxrHlSensorValue
175 description: Sensor value
176 family: Health/Sensors
177 + transform: |
178 + {{- $sensorType := index .Metric.Tags "sensor_type" | default "" -}}
179 + {{- $config := get (dict
180 + "1" (dict "name" "temperature" "unit" "celsius" "family" "Temperature"
181 + "desc" "Temperature reading")
182 + "2" (dict "name" "fan_speed" "unit" "rpm" "family" "Fan"
183 + "desc" "Fan rotation speed")
184 + "3" (dict "name" "voltage" "unit" "volts" "family" "Power" "divisor" 10.0
185 + "desc" "Voltage measurement")
186 + "4" (dict "name" "current" "unit" "amperes" "family" "Power" "divisor" 10.0
187 + "desc" "Current draw")
188 + "5" (dict "name" "power" "unit" "watts" "family" "Power" "divisor" 10.0
189 + "desc" "Power consumption")
190 + "6" (dict "name" "sensor_status" "family" "Status"
191 + "mapping" (i64map 0 "absent_or_faulty" 1 "present_and_ok")
192 + "desc" "Component presence and operational status")
193 + "7" (dict "name" "sensor_state" "family" "Status"
194 + "mapping" (i64map 0 "false" 1 "true")
195 + "desc" "Boolean sensor state")
196 + "8" (dict "name" "usage_percentage" "unit" "percentage" "family" "Usage"
197 + "desc" "Resource utilization percentage")
198 + ) $sensorType -}}
199 +
200 + {{- if $config -}}
201 + {{- setName .Metric (printf "%s_%s" .Metric.Name (get $config "name")) -}}
202 + {{- setFamily .Metric (printf "Health/Sensors/%s" (get $config "family")) -}}
203 + {{- with get $config "desc" -}}{{- setDesc $.Metric . -}}{{- end -}}
204 + {{- with get $config "unit" -}}{{- setUnit $.Metric . -}}{{- end -}}
205 + {{- with get $config "divisor" -}}{{- setValue $.Metric (int64 (div (float64 $.Value) .)) -}}{{- end -}}
206 + {{- with get $config "mapping" -}}{{- setMappings $.Metric . -}}{{- end -}}
207 + {{- end -}}
208 +
209 + {{- deleteTag .Metric "sensor_type" -}}
210 metric_tags:
211 - tag: sensor_name
212 symbol:
213 OID: 1.3.6.1.4.1.14988.1.1.3.100.1.2
214 name: mtxrHlSensorName
182 - - tag: "_unit"
215 + - tag: sensor_type
216 symbol:
217 OID: 1.3.6.1.4.1.14988.1.1.3.100.1.4
218 name: mtxrHlSensorUnit
186 - mapping:
187 - 1: celsius
188 - 2: rpm
189 - 3: dV # decivolts
190 - 4: dA # deciamps
191 - 5: dW # deciwatts
192 - 6: status
193 - 7: boolean
194 - 8: percentage
195 - - tag: "_metric_suffix"
196 - symbol:
197 - OID: 1.3.6.1.4.1.14988.1.1.3.100.1.4
198 - name: mtxrHlSensorType
199 - mapping:
200 - 1: temperature
201 - 2: fan_speed
202 - 3: voltage
203 - 4: current
204 - 5: power
205 - 6: state
206 - 7: boolean
207 - 8: percentage
219
220 metric_tags:
221 - OID: 1.3.6.1.4.1.14988.1.1.4.1.0