@cryptotaxi247 / netdata-1 / commits / a27bd2409

Initial commit with snmp profile code (#19813)

* initial commit with snmp profile code that does not affect functionality * remove old and possibly useless functions * move loading dd snmp profile to ddsnmp subpkg --------- Co-authored-by: Ilya Mashchenko <ilya@netdata.cloud>

Fotis Voutsas committed Mar 14, 2025 at 15:18 UTC a27bd240989b86cde5c31e23f760c38af84a4e25
9 files changed +1501
src/go/plugin/go.d/collector/snmp/collect.go
+27
@@ -51,6 +51,33 @@ func (c *Collector) collect() (map[string]int64, error) {
51 return mx, nil
52 }
53
54 +func (c *Collector) getSysObjectID(oid string) (string, error) {
55 + resp, err := c.snmpClient.Get([]string{oid})
56 + if err != nil {
57 + return "", err
58 + }
59 +
60 + return strings.Replace(resp.Variables[0].Value.(string), ".", "", 1), nil
61 +}
62 +
63 +func (c *Collector) makeChartsFromMetricMap(mx map[string]int64, metricMap map[string]processedMetric) error {
64 +
65 + for _, metric := range metricMap {
66 + if metric.tableName == "" {
67 + switch s := metric.value.(type) {
68 + case int:
69 +
70 + // log.Println(metric)
71 +
72 + // c.addSNMPChart(metric)
73 + mx[metric.name] = int64(s)
74 +
75 + }
76 + }
77 + }
78 + return nil
79 +}
80 +
81 func (c *Collector) collectSysUptime(mx map[string]int64) error {
82 resp, err := c.snmpClient.Get([]string{snmpsd.OidSysUptime})
83 if err != nil {
src/go/plugin/go.d/collector/snmp/command_func.go new
+43
@@ -0,0 +1,43 @@
1 +package snmp
2 +
3 +import (
4 + "fmt"
5 + "log"
6 + "strings"
7 +)
8 +
9 +func (c *Collector) walkOIDTree(baseOID string) (map[string]processedMetric, error) {
10 + tableRows := make(map[string]processedMetric)
11 +
12 + currentOID := baseOID
13 + for {
14 + result, err := c.snmpClient.GetNext([]string{currentOID})
15 + if err != nil {
16 + return tableRows, fmt.Errorf("snmpgetnext failed: %v", err)
17 + }
18 + if len(result.Variables) == 0 {
19 + log.Println("No OID returned, ending walk.")
20 + return tableRows, nil
21 + }
22 + pdu := result.Variables[0]
23 +
24 + nextOID := strings.Replace(pdu.Name, ".", "", 1) //remove dot at the start of the OID
25 + // fmt.Println(nextOID, baseOID)
26 +
27 + // If the next OID does not start with the base OID, we've reached the end of the subtree.
28 + if !strings.HasPrefix(nextOID, baseOID) {
29 + return tableRows, nil
30 + }
31 +
32 + metricType := pdu.Type
33 + value := fmt.Sprintf("%v", pdu.Value)
34 +
35 + tableRows[nextOID] = processedMetric{
36 + oid: nextOID,
37 + value: value,
38 + metric_type: metricType,
39 + }
40 +
41 + currentOID = nextOID
42 + }
43 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/load.go new
+77
@@ -0,0 +1,77 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package ddsnmp
4 +
5 +import (
6 + "io/fs"
7 + "os"
8 + "path/filepath"
9 + "strings"
10 +
11 + "gopkg.in/yaml.v2"
12 +)
13 +
14 +func load(dirpath string) ([]*Profile, error) {
15 + var profiles []*Profile
16 +
17 + if err := filepath.WalkDir(dirpath, func(path string, d fs.DirEntry, err error) error {
18 + if !(strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")) {
19 + return nil
20 + }
21 + profile, err := loadYAML(path)
22 + if err != nil {
23 + return err
24 + }
25 + profiles = append(profiles, profile)
26 + return nil
27 + }); err != nil {
28 + return nil, err
29 + }
30 +
31 + return profiles, nil
32 +}
33 +
34 +func loadYAML(filename string) (*Profile, error) {
35 + content, err := os.ReadFile(filename)
36 + if err != nil {
37 + return nil, err
38 + }
39 +
40 + var prof Profile
41 + if err := yaml.Unmarshal(content, &prof); err != nil {
42 + return nil, err
43 + }
44 +
45 + if prof.SourceFile == "" {
46 + prof.SourceFile, _ = filepath.Abs(filename)
47 + }
48 +
49 + dir := filepath.Dir(filename)
50 +
51 + for _, name := range prof.Extends {
52 + baseProf, err := loadYAML(filepath.Join(dir, name))
53 + if err != nil {
54 + return nil, err
55 + }
56 + mergeProfiles(&prof, baseProf)
57 + }
58 +
59 + return &prof, nil
60 +}
61 +
62 +func mergeProfiles(child, parent *Profile) {
63 + child.Metrics = append(parent.Metrics, child.Metrics...)
64 + //
65 + //if child.Metadata == nil || len(child.Metadata.Device) == 0 {
66 + // return
67 + //}
68 + //if child.Metadata.Device.Fields == nil {
69 + // child.Metadata.Device.Fields = make(map[string]Symbol)
70 + //}
71 + //
72 + //for key, value := range parent.Metadata.Device.Fields {
73 + // if _, exists := child.Metadata.Device.Fields[key]; !exists {
74 + // child.Metadata.Device.Fields[key] = value
75 + // }
76 + //}
77 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/load_test.go new
+27
@@ -0,0 +1,27 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package ddsnmp
4 +
5 +import (
6 + "os"
7 + "testing"
8 +
9 + "github.com/stretchr/testify/assert"
10 + "github.com/stretchr/testify/require"
11 +)
12 +
13 +func Test_loadDDSnmpProfiles(t *testing.T) {
14 + dir := "../../../config/go.d/snmp.profiles/default"
15 +
16 + f, err := os.Open(dir)
17 + require.NoError(t, err)
18 + defer f.Close()
19 +
20 + profiles, err := load(dir)
21 + require.NoError(t, err)
22 +
23 + names, err := f.Readdirnames(-1)
24 + require.NoError(t, err)
25 +
26 + assert.Equal(t, len(names)-1 /*README.md*/, len(profiles))
27 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/profile.go new
+133
@@ -0,0 +1,133 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package ddsnmp
4 +
5 +import (
6 + "fmt"
7 +)
8 +
9 +const (
10 + MetricTypeGauge = "gauge"
11 + MetricTypeRate = "rate"
12 + MetricTypePercent = "percent"
13 + MetricTypeMonotonicCount = "monotonic_count"
14 + MetricTypeMonotonicCountAndRate = "monotonic_count_and_rate"
15 + MetricTypeFlagStream = "flag_stream" // https://datadoghq.dev/integrations-core/tutorials/snmp/profile-format/#flag-stream
16 +)
17 +
18 +// Profile is Datadog SNMP profile (format: https://datadoghq.dev/integrations-core/tutorials/snmp/profile-format/)
19 +type Profile struct {
20 + SourceFile string
21 +
22 + Extends []string `yaml:"extends"` // +done
23 + SysObjectID SysObjectIDs `yaml:"sysobjectid"` // +done
24 + Metrics []Metric `yaml:"metrics"`
25 + Metadata *Metadata `yaml:"metadata"` // +done
26 + MetricTags []GlobalMetricTag `yaml:"metric_tags"` // +done
27 +}
28 +
29 +type SysObjectIDs []string
30 +
31 +type (
32 + // Metric defines which metrics will be collected by the profile.
33 + // Can reference either a single OID (a.k.a symbol), or an SNMP table.
34 + // https://datadoghq.dev/integrations-core/tutorials/snmp/profile-format/#metrics
35 + Metric struct {
36 + Name string `yaml:"name"`
37 + OID string `yaml:"OID"`
38 +
39 + // Typically a symbol will be inferred from the SNMP type
40 + // https://datadoghq.dev/integrations-core/tutorials/snmp/profile-format/#metric-type-inference
41 + // Can be overwritten using "metric_type"
42 + // https://datadoghq.dev/integrations-core/tutorials/snmp/profile-format/#forced-metric-types
43 + MetricType string `yaml:"metric_type"`
44 +
45 + Options map[string]string
46 +
47 + MIB string `yaml:"MIB"`
48 +
49 + // Symbol metric
50 + // https://datadoghq.dev/integrations-core/tutorials/snmp/profile-format/#symbol-metrics
51 + Symbol *Symbol `yaml:"symbol"`
52 +
53 + // Table metric
54 + // https://datadoghq.dev/integrations-core/tutorials/snmp/profile-format/#table-metrics
55 + Table *MetricTable `yaml:"table"`
56 + Symbols []Symbol `yaml:"symbols"`
57 +
58 + MetricTags []MetricTag `yaml:"metric_tags"` //TODO check for only name existing in metric tag, as there is some case for that
59 + }
60 + MetricTable struct {
61 + OID string `yaml:"OID"`
62 + Name string `yaml:"name"`
63 + }
64 + // MetricTag used for Table metrics to identify each row's metric.
65 + // https://datadoghq.dev/integrations-core/tutorials/snmp/profile-format/#table-metrics-tagging
66 + MetricTag struct {
67 + MIB string `yaml:"mib"`
68 + Table string `yaml:"table"`
69 + Tag string `yaml:"tag"`
70 + Symbol Symbol `yaml:"symbol"`
71 + IndexTransform []IndexSlice `yaml:"index_transform"`
72 +
73 + Mapping map[int]string `yaml:"mapping"`
74 + Index int `yaml:"index"`
75 + }
76 + Symbol struct {
77 + OID string `yaml:"OID"`
78 + Name string `yaml:"name"`
79 + ExtractValue string `yaml:"extract_value"`
80 + MatchPattern string `yaml:"match_pattern"`
81 + MatchValue string `yaml:"match_value"`
82 + Format string `yaml:"format"`
83 + ScaleFactor float64 `yaml:"scale_factor"`
84 + }
85 + IndexSlice struct {
86 + Start int `yaml:"start"`
87 + End int `yaml:"end"`
88 + }
89 +)
90 +
91 +type (
92 + // Metadata used to declare where and how metadata should be collected
93 + // https://datadoghq.dev/integrations-core/tutorials/snmp/profile-format/#metadata
94 + Metadata struct {
95 + Device DeviceMetadata `yaml:"device"`
96 + }
97 + DeviceMetadata struct {
98 + Fields map[string]MetadataField `yaml:"fields"`
99 + }
100 + MetadataField struct {
101 + Value *string `yaml:"value"`
102 + Symbol *Symbol `yaml:"symbol"`
103 + Symbols []Symbol `yaml:"symbols"`
104 + }
105 +)
106 +
107 +// GlobalMetricTag used to apply tags to all metrics collected by the profile
108 +// https://datadoghq.dev/integrations-core/tutorials/snmp/profile-format/#metric_tags
109 +type GlobalMetricTag struct {
110 + OID string `yaml:"OID"`
111 + Symbol string `yaml:"symbol"`
112 + Tag string `yaml:"tag"`
113 +
114 + Match string `yaml:"match"`
115 + Tags map[string]string `yaml:"tags"`
116 + Mapping map[int]string `yaml:"mapping"`
117 +}
118 +
119 +func (s *SysObjectIDs) UnmarshalYAML(unmarshal func(any) error) error {
120 + var single string
121 + if err := unmarshal(&single); err == nil {
122 + *s = []string{single}
123 + return nil
124 + }
125 +
126 + var multiple []string
127 + if err := unmarshal(&multiple); err == nil {
128 + *s = multiple
129 + return nil
130 + }
131 +
132 + return fmt.Errorf("invalid sysobjectid format")
133 +}
src/go/plugin/go.d/collector/snmp/helpers.go new
+73
@@ -0,0 +1,73 @@
1 +package snmp
2 +
3 +func sliceToStrings(items []interface{}) []string {
4 + var strs []string
5 + for _, v := range items {
6 + s, ok := v.(string)
7 + if !ok {
8 + // Handle error if an element is not a string.
9 + continue
10 + }
11 + strs = append(strs, s)
12 + }
13 + return strs
14 +}
15 +
16 +func sliceToTableMetricTags(items []interface{}) []TableMetricTag {
17 + var metricTag []TableMetricTag
18 + for _, v := range items {
19 + s, ok := v.(TableMetricTag)
20 + if !ok {
21 + // Handle error if an element is not a string.
22 + continue
23 + }
24 + metricTag = append(metricTag, s)
25 + }
26 + return metricTag
27 +}
28 +
29 +func mergeTableBatches(target tableBatches, source tableBatches) tableBatches {
30 + merged := tableBatches{}
31 +
32 + // Extend batches in `target` with OIDs from `source` that share the same key.
33 + for key, batch := range target {
34 +
35 + if srcBatch, ok := source[key]; ok {
36 + mergedOids := append(batch.oids, srcBatch.oids...)
37 + merged[key] = tableBatch{
38 + tableOID: batch.tableOID,
39 + oids: mergedOids,
40 + }
41 + }
42 + }
43 +
44 + for key := range source {
45 + if _, ok := target[key]; !ok {
46 + merged[key] = source[key]
47 + }
48 + }
49 +
50 + return merged
51 +}
52 +
53 +func mergeStringMaps(m1 map[string]string, m2 map[string]string) map[string]string {
54 + merged := make(map[string]string)
55 + for k, v := range m1 {
56 + merged[k] = v
57 + }
58 + for key, value := range m2 {
59 + merged[key] = value
60 + }
61 + return merged
62 +}
63 +
64 +func mergeProcessedMetricMaps(m1 map[string]processedMetric, m2 map[string]processedMetric) map[string]processedMetric {
65 + merged := make(map[string]processedMetric)
66 + for k, v := range m1 {
67 + merged[k] = v
68 + }
69 + for key, value := range m2 {
70 + merged[key] = value
71 + }
72 + return merged
73 +}
src/go/plugin/go.d/collector/snmp/parsing.go new
+700
@@ -0,0 +1,700 @@
1 +package snmp
2 +
3 +import (
4 + "errors"
5 + "fmt"
6 + "log"
7 + "reflect"
8 + "regexp"
9 +)
10 +
11 +func parseMetrics(metrics []Metric) (parsedResult, error) {
12 + oids := []string{}
13 + next_oids := []string{}
14 + bulk_oids := []string{}
15 + parsed_metrics := []parsedMetric{}
16 + oids_to_resolve := []map[string]string{}
17 + indexes_to_resolve := []indexMapping{}
18 + bulk_threshold := 0
19 + for _, metric := range metrics {
20 + result, err := parseMetric(metric)
21 +
22 + if err != nil {
23 + return parsedResult{}, err
24 + }
25 +
26 + oids = append(oids, result.oidsToFetch...)
27 +
28 + for name, oid := range result.oidsToResolve {
29 + // here in the python implementation a registration happens to their OIDResolver. I will not support this atm
30 + oids_to_resolve = append(oids_to_resolve, map[string]string{name: oid})
31 + }
32 +
33 + // here in the python implementation a registration happens to their OIDResolver. I will not support this atm
34 + indexes_to_resolve = append(indexes_to_resolve, result.indexMappings...)
35 +
36 + for _, batch := range result.tableBatches {
37 + should_query_in_bulk := bulk_threshold > 0 && len(batch.oids) > bulk_threshold
38 + if should_query_in_bulk {
39 + bulk_oids = append(bulk_oids, batch.tableOID)
40 + } else {
41 + next_oids = append(next_oids, batch.oids...)
42 + }
43 + }
44 +
45 + parsed_metrics = append(parsed_metrics, result.parsedMetrics...)
46 +
47 + }
48 + return parsedResult{oids: oids,
49 + next_oids: next_oids, bulk_oids: bulk_oids, parsed_metrics: parsed_metrics}, nil
50 +}
51 +
52 +func parseMetric(metric Metric) (metricParseResult, error) {
53 + /*Can either be:
54 +
55 + * An OID metric:
56 +
57 + ```
58 + metrics:
59 + - OID: 1.3.6.1.2.1.2.2.1.14
60 + name: ifInErrors
61 + ```
62 +
63 + * A symbol metric:
64 +
65 + ```
66 + metrics:
67 + - MIB: IF-MIB
68 + symbol: ifInErrors
69 + # OR:
70 + symbol:
71 + OID: 1.3.6.1.2.1.2.2.1.14
72 + name: ifInErrors
73 + ```
74 +
75 + * A table metric (see parsing for table metrics for all possible options):
76 +
77 + ```
78 + metrics:
79 + - MIB: IF-MIB
80 + table: ifTable
81 + symbols:
82 + - OID: 1.3.6.1.2.1.2.2.1.14
83 + name: ifInErrors
84 + ```*/
85 +
86 + // Cast the tags to either array
87 + castedStringMetricTags := []string{}
88 + castedTableMetricTags := []TableMetricTag{}
89 +
90 + if len(metric.MetricTags) > 0 {
91 + /*//TODO investigate if there are metric tags and not only table metric tags
92 + fmt.Println("parseMetric MetricTags switch, have:")
93 + spew.Dump(metric.MetricTags)
94 + switch metric.MetricTags[0].(type) {
95 + case string:
96 + os.Exit(-40)
97 + castedStringMetricTags = sliceToStrings(metric.MetricTags)
98 +
99 + case MetricTag:
100 + os.Exit(-40)
101 + castedTableMetricTags = sliceToTableMetricTags(metric.MetricTags)
102 + }*/
103 + }
104 +
105 + if len(metric.OID) > 0 {
106 + // TODO investigate if this exists in the yamls
107 + return (parseOIDMetric(oidMetric{name: metric.Name, oid: metric.OID, metricTags: castedStringMetricTags, forcedType: metric.MetricType, options: metric.Options})), nil
108 + } else if len(metric.MIB) == 0 {
109 + return metricParseResult{}, fmt.Errorf("unsupported metric {%v}", metric)
110 + } else if metric.Symbol != (Symbol{}) {
111 + // Single Metric
112 + return (parseSymbolMetric(symbolMetric{mib: metric.MIB, symbol: metric.Symbol, forcedType: metric.MetricType, metricTags: castedStringMetricTags, options: metric.Options}))
113 + } else if metric.Table != nil {
114 + // Table
115 + if len(metric.MetricTags) > 0 {
116 +
117 + /*//This will be cleared out when tags are supported
118 + // for _, rawItem := range metric.MetricTags {
119 + // item, ok := rawItem.(map[string]interface{})
120 + // if !ok {
121 + // continue
122 + // }
123 + // fmt.Println("ITEM", item["symbol"])
124 +
125 + // var index int
126 + // if val, exists := item["Index"]; exists {
127 + // if i, ok := val.(int); ok {
128 + // index = i
129 + // } else {
130 + // index = -1
131 + // }
132 + // }
133 +
134 + // var mapping map[int]string
135 + // if val, exists := item["mapping"]; exists {
136 + // if m, ok := val.(map[int]string); ok {
137 + // fmt.Print("IN")
138 + // os.Exit(-90)
139 + // mapping = m
140 + // }
141 + // }
142 +
143 + // var tag string
144 + // if val, exists := item["Tag"]; exists {
145 + // if s, ok := val.(string); ok {
146 + // tag = s
147 + // }
148 + // }
149 +
150 + // var symbol Symbol
151 + // if rawSymbol, exists := item["symbol"]; exists {
152 + // if symMap, ok := rawSymbol.(map[string]interface{}); ok {
153 + // var oid, name string
154 +
155 + // if v, exists := symMap["OID"]; exists {
156 + // if oidStr, ok := v.(string); ok {
157 + // oid = oidStr
158 + // }
159 + // }
160 +
161 + // if v, exists := symMap["name"]; exists {
162 + // if nameStr, ok := v.(string); ok {
163 + // name = nameStr
164 + // }
165 + // }
166 +
167 + // symbol = Symbol{OID: oid, Name: name}
168 + // } else {
169 + // fmt.Println("symbol is not a map[string]interface{}")
170 + // }
171 + // }
172 +
173 + // var table string
174 + // if val, exists := item["Table"]; exists {
175 + // if s, ok := val.(string); ok {
176 + // table = s
177 + // } else {
178 + // table = ""
179 + // }
180 + // }
181 +
182 + // var mib string
183 + // if val, exists := item["MIB"]; exists {
184 + // if s, ok := val.(string); ok {
185 + // mib = s
186 + // } else {
187 + // mib = ""
188 + // }
189 + // }
190 +
191 + // var indexTransform []IndexSlice
192 + // if val, exists := item["IndexTransform"]; exists {
193 + // if xs, ok := val.([]IndexSlice); ok {
194 + // indexTransform = xs
195 + // }
196 + // }*/
197 +
198 + castedTableMetricTags = append(castedTableMetricTags, metric.MetricTags...)
199 + }
200 +
201 + if metric.Symbols == nil {
202 + return metricParseResult{}, fmt.Errorf("when specifying a table, you must specify a list of symbols %v", metric)
203 + }
204 +
205 + return (parseTableMetric(tableMetric{
206 + mib: metric.MIB,
207 + table: metric.Table,
208 + symbols: metric.Symbols,
209 + forcedType: metric.MetricType,
210 + metricTags: castedTableMetricTags,
211 + options: metric.Options,
212 + }))
213 +
214 + }
215 + return metricParseResult{}, fmt.Errorf("unsupported metric {%v}", metric)
216 +}
217 +
218 +// TODO error outs on functions
219 +func parseOIDMetric(metric oidMetric) metricParseResult {
220 + /*Parse a fully resolved OID/name metric.
221 +
222 + Note: This `OID/name` syntax is deprecated in favour of `symbol` syntax.
223 +
224 + Example:
225 +
226 + ```
227 + metrics:
228 + - OID: 1.3.6.1.2.1.2.1
229 + name: ifNumber
230 + ```
231 + */
232 + name := metric.name
233 + oid := metric.oid
234 +
235 + // TODO can't find a profile with this metric type
236 +
237 + parsed_symbol_metric := parsedSymbolMetric{
238 + name: name,
239 + tags: metric.metricTags,
240 + forcedType: metric.forcedType,
241 + enforceScalar: true,
242 + options: metric.options,
243 + baseoid: oid,
244 + }
245 +
246 + return metricParseResult{
247 + oidsToFetch: []string{oid},
248 + oidsToResolve: map[string]string{name: oid},
249 + parsedMetrics: []parsedMetric{parsed_symbol_metric},
250 + tableBatches: nil,
251 + indexMappings: nil,
252 + }
253 +}
254 +
255 +// TODO error outs on functions
256 +func parseSymbolMetric(metric symbolMetric) (metricParseResult, error) {
257 + /* Parse a symbol metric (= an OID in a MIB).
258 + Example:
259 +
260 + ```
261 + metrics:
262 + - MIB: IF-MIB
263 + symbol: <string or OID/name object>
264 + - MIB: IF-MIB
265 + symbol: # MIB-less syntax
266 + OID: 1.3.6.1.2.1.6.5.0
267 + name: tcpActiveOpens
268 + - MIB: IF-MIB
269 + symbol: tcpActiveOpens # require MIB syntax
270 + ```*/
271 + mib := metric.mib
272 + symbol := metric.symbol
273 + parsed_symbol, err := parseSymbol(mib, symbol)
274 + if err != nil {
275 + return metricParseResult{}, err
276 + }
277 +
278 + parsed_symbol_metric := parsedSymbolMetric{
279 + name: parsed_symbol.name,
280 + tags: metric.metricTags,
281 + forcedType: metric.forcedType,
282 + enforceScalar: false,
283 + options: metric.options,
284 + extractValuePattern: parsed_symbol.extractValuePattern,
285 + baseoid: parsed_symbol.oid,
286 + }
287 +
288 + return metricParseResult{
289 + oidsToFetch: []string{parsed_symbol.oid},
290 + oidsToResolve: parsed_symbol.oidsToResolve,
291 + parsedMetrics: []parsedMetric{parsed_symbol_metric},
292 + tableBatches: nil,
293 + indexMappings: nil,
294 + }, nil
295 +}
296 +
297 +// TODO error outs on functions
298 +func parseTableMetric(metric tableMetric) (metricParseResult, error) {
299 +
300 + mib := metric.mib
301 + parsed_table, err := parseSymbol(mib, metric.table)
302 + if err != nil {
303 + return metricParseResult{}, err
304 + }
305 +
306 + table_name := parsed_table.name
307 + table_oid := parsed_table.oid
308 +
309 + oids_to_resolve := parsed_table.oidsToResolve
310 +
311 + var index_tags []indexTag
312 + var column_tags []columnTag
313 + var index_mappings []indexMapping
314 + var table_batches map[tableBatchKey]tableBatch
315 +
316 + if metric.metricTags != nil {
317 + for _, metric_tag := range metric.metricTags {
318 + parsed_table_metric_tag, err := parseTableMetricTag(mib, parsed_table, metric_tag)
319 + if err != nil {
320 + return metricParseResult{}, err
321 + }
322 +
323 + if parsed_table_metric_tag.oidsToResolve != nil {
324 + oids_to_resolve = mergeStringMaps(oids_to_resolve, parsed_table_metric_tag.oidsToResolve)
325 +
326 + column_tags = append(column_tags, parsed_table_metric_tag.columnTags...)
327 +
328 + table_batches = mergeTableBatches(table_batches, parsed_table_metric_tag.tableBatches)
329 + } else {
330 +
331 + index_tags = append(index_tags, parsed_table_metric_tag.indexTags...)
332 +
333 + for index, mapping := range parsed_table_metric_tag.indexMappings {
334 + for _, symbol := range metric.symbols {
335 + index_mappings = append(index_mappings, indexMapping{tag: symbol.Name, index: index, mapping: mapping})
336 + }
337 +
338 + for _, tag := range metric.metricTags {
339 + if reflect.DeepEqual(tag.Symbol, Symbol{}) {
340 + tag = TableMetricTag{
341 + Tag: tag.Tag,
342 + Symbol: tag.Symbol,
343 + }
344 + index_mappings = append(index_mappings, indexMapping{
345 + tag: tag.Symbol.Name,
346 + index: index,
347 + mapping: mapping,
348 + })
349 + }
350 + }
351 + }
352 + }
353 + }
354 + }
355 +
356 + table_oids := []string{}
357 + parsed_metrics := []parsedMetric{}
358 +
359 + for _, symbol := range metric.symbols {
360 + parsed_symbol, err := parseSymbol(mib, symbol)
361 + if err != nil {
362 + return metricParseResult{}, nil
363 + }
364 +
365 + for key, value := range parsed_symbol.oidsToResolve {
366 + oids_to_resolve[key] = value
367 + }
368 +
369 + table_oids = append(table_oids, parsed_symbol.oid)
370 +
371 + parsed_table_metric := parsedTableMetric{
372 + name: parsed_symbol.name,
373 + indexTags: index_tags,
374 + columnTags: column_tags,
375 + forcedType: metric.forcedType,
376 + options: metric.options,
377 + extractValuePattern: parsed_symbol.extractValuePattern,
378 + rowOID: parsed_symbol.oid,
379 + tableName: table_name,
380 + tableOID: table_oid,
381 + }
382 +
383 + parsed_metrics = append(parsed_metrics, parsed_table_metric)
384 + }
385 +
386 + table_batches = mergeTableBatches(table_batches, map[tableBatchKey]tableBatch{{mib: mib, table: parsed_table.name}: {tableOID: parsed_table.oid, oids: table_oids}})
387 +
388 + return metricParseResult{
389 + oidsToFetch: []string{},
390 + oidsToResolve: oids_to_resolve,
391 + tableBatches: table_batches,
392 + indexMappings: index_mappings,
393 + parsedMetrics: parsed_metrics,
394 + }, nil
395 +}
396 +
397 +func parseTableMetricTag(mib string, parsed_table parsedSymbol, metric_tag TableMetricTag) (parsedTableMetricTag, error) {
398 + /*
399 + Parse an item of the `metric_tags` section of a table metric.
400 +
401 + Items can be:
402 +
403 + * A reference to a column in the same table.
404 +
405 + Example using entPhySensorTable in ENTITY-SENSOR-MIB:
406 +
407 + ```
408 + metric_tags:
409 + - tag: sensor_type
410 + column: entPhySensorType
411 + # OR
412 + column:
413 + OID: 1.3.6.1.2.1.99.1.1.1.1
414 + name: entPhySensorType
415 +
416 + ```
417 +
418 + * A reference to a column in a different table.
419 +
420 + Example:
421 +
422 + ```
423 + metric_tags:
424 + - tag: adapter
425 + table: genericAdaptersAttrTable
426 + column: adapterName
427 + # OR
428 + column:
429 + OID: 1.3.6.1.4.1.343.2.7.2.2.1.1.1.2
430 + name: adapterName
431 +
432 + ```
433 +
434 + * A reference to an OID by its index in the table entry.
435 +
436 + An optional `mapping` can be used to map index values to human-readable strings.
437 +
438 + Example using ipIfStatsTable in IP-MIB:
439 +
440 + ```
441 + metric_tags:
442 + - # ipIfStatsIPVersion (1.3.6.1.2.1.4.21.3.1.1)
443 + tag: ip_version
444 + index: 1
445 + mapping:
446 + 0: unknown
447 + 1: ipv4
448 + 2: ipv6
449 + 3: ipv4z
450 + 4: ipv6z
451 + 16: dns
452 + - # ipIfStatsIfIndex (1.3.6.1.2.1.4.21.3.1.2)
453 + tag: interface
454 + index: 2
455 + ```
456 + */
457 + if metric_tag.Symbol != (Symbol{}) {
458 + metric_tag_mib := metric_tag.MIB
459 +
460 + if metric_tag.Table != "" {
461 + return parseOtherTableColumnMetricTag(metric_tag_mib, metric_tag.Table, metric_tag)
462 + }
463 +
464 + if mib != metric_tag_mib && metric_tag_mib != "" {
465 + return parsedTableMetricTag{}, fmt.Errorf("when tagging from a different MIB, the table must be specified, TABLE_MAME: %s MIB: %s METRIC_TAG_MIB: %s", parsed_table.name, mib, metric_tag_mib)
466 + }
467 + return parseColumnMetricTag(mib, parsed_table, metric_tag)
468 + } else if &metric_tag.Index != nil { //TODO, this is tautological condition, need to return a "-1" on index if it does not exist or something similar
469 + return parseIndexMetricTag(metric_tag)
470 + } else {
471 + return parsedTableMetricTag{}, errors.New("symbol is empty")
472 + }
473 +
474 +}
475 +
476 +func parseIndexMetricTag(metric_tag TableMetricTag) (parsedTableMetricTag, error) {
477 + parsed_metric_tag, err := parseMetricTag(
478 + MetricTag{
479 + Tag: metric_tag.Tag,
480 + })
481 + if err != nil {
482 + return parsedTableMetricTag{}, err
483 + }
484 +
485 + index_tags := []indexTag{{
486 + parsedMetricTag: parsed_metric_tag,
487 + index: metric_tag.Index,
488 + }}
489 +
490 + index_mappings := map[int]map[int]string{}
491 +
492 + if metric_tag.Mapping != nil {
493 + index_mappings = map[int]map[int]string{metric_tag.Index: metric_tag.Mapping}
494 + }
495 +
496 + return parsedTableMetricTag{
497 + indexTags: index_tags,
498 + indexMappings: index_mappings,
499 + }, nil
500 +}
501 +
502 +func parseOtherTableColumnMetricTag(mib string, table string, metric_tag TableMetricTag) (parsedTableMetricTag, error) {
503 + parsed_table, err := parseSymbol(mib, &table)
504 + if err != nil {
505 + return parsedTableMetricTag{}, err
506 + }
507 + parsed_metric_tag, err := parseColumnMetricTag(mib, parsed_table, metric_tag)
508 + if err != nil {
509 + return parsedTableMetricTag{}, err
510 + }
511 +
512 + oids_to_resolve := parsed_metric_tag.oidsToResolve
513 + oids_to_resolve = mergeStringMaps(oids_to_resolve, parsed_table.oidsToResolve)
514 +
515 + return parsedTableMetricTag{
516 + oidsToResolve: oids_to_resolve,
517 + tableBatches: parsed_metric_tag.tableBatches,
518 + columnTags: parsed_metric_tag.columnTags,
519 + }, nil
520 +}
521 +
522 +func parseColumnMetricTag(mib string, parsed_table parsedSymbol, metric_tag TableMetricTag) (parsedTableMetricTag, error) {
523 + parsed_column, err := parseSymbol(mib, metric_tag.Symbol)
524 + if err != nil {
525 + return parsedTableMetricTag{}, err
526 + }
527 +
528 + batches := map[tableBatchKey]tableBatch{
529 + {mib: mib, table: parsed_table.name}: {tableOID: parsed_table.oid, oids: []string{parsed_column.oid}},
530 + }
531 +
532 + parsed_metric_tag, err := parseMetricTag(MetricTag{MIB: metric_tag.MIB, OID: "", Tag: metric_tag.Tag, Symbol: metric_tag.Symbol})
533 + if err != nil {
534 + return parsedTableMetricTag{}, err
535 + }
536 +
537 + return parsedTableMetricTag{
538 + oidsToResolve: parsed_column.oidsToResolve,
539 + columnTags: []columnTag{{
540 + parsedMetricTag: parsed_metric_tag,
541 + column: parsed_column.name,
542 + indexSlices: parseIndexSlices(metric_tag),
543 + },
544 + },
545 + tableBatches: batches,
546 + }, nil
547 +}
548 +
549 +func parseIndexSlices(metric_tag TableMetricTag) []IndexSlice {
550 + /*
551 + // Transform index_transform into list of index slices.
552 +
553 + // `index_transform` is needed to support tagging using another table with different indexes.
554 +
555 + // Example: TableB have two indexes indexX (1 digit) and indexY (3 digits).
556 + // We want to tag by an external TableA that have indexY (3 digits).
557 +
558 + // For example TableB has a row with full index `1.2.3.4`, indexX is `1` and indexY is `2.3.4`.
559 + // TableA has a row with full index `2.3.4`, indexY is `2.3.4` (matches indexY of TableB).
560 +
561 + // SNMP integration doesn't know how to compare the full indexes from TableB and TableA.
562 + // We need to extract a subset of the full index of TableB to match with TableA full index.
563 +
564 + // Using the below `index_transform` we provide enough info to extract a subset of index that
565 + // will be used to match TableA's full index.
566 +
567 + // ```yaml
568 + // index_transform:
569 + // - start: 1
570 + // - end: 3
571 + //
572 + // ```
573 + */
574 + raw_index_slices := metric_tag.IndexTransform
575 + index_slices := []IndexSlice{}
576 +
577 + for _, rule := range raw_index_slices {
578 + start, end := rule.Start, rule.End
579 + if start > end {
580 + log.Println("start bigger than end")
581 + return nil
582 + }
583 + if start < 0 {
584 + log.Println("start is negative")
585 + return nil
586 + }
587 + index_slices = append(index_slices, IndexSlice{start, end + 1})
588 +
589 + }
590 +
591 + return index_slices
592 +}
593 +
594 +func parseMetricTag(metric_tag MetricTag) (parsedMetricTag, error) {
595 + parsed_metric_tag := parsedMetricTag{}
596 +
597 + if metric_tag.Tag != "" {
598 + parsed_metric_tag = parseSimpleMetricTag(metric_tag)
599 + } else if metric_tag.Match != "" && metric_tag.Tags != nil {
600 + tmp, err := parseRegexMetricTag(metric_tag)
601 + if err != nil {
602 + return parsedMetricTag{}, err
603 + } else {
604 + parsed_metric_tag = tmp
605 + }
606 + } else {
607 + return parsedMetricTag{}, fmt.Errorf("a metric tag must specify either a tag, or a mapping of tags and a regular expression %v", metric_tag)
608 + }
609 + return parsed_metric_tag, nil
610 +}
611 +
612 +func parseRegexMetricTag(metric_tag MetricTag) (parsedMetricTag, error) {
613 + match := metric_tag.Match
614 + tags := metric_tag.Tags
615 +
616 + // To be supported once tags are supported
617 + // if reflect.TypeOf(tags) != reflect.TypeOf(map[string]string{}) {
618 + // }
619 +
620 + pattern, err := regexp.Compile(match)
621 + if err != nil {
622 + return parsedMetricTag{}, err
623 + }
624 +
625 + return parsedMetricTag{tags: tags, pattern: pattern}, nil
626 +}
627 +
628 +func parseSimpleMetricTag(metric_tag MetricTag) parsedMetricTag {
629 + return parsedMetricTag{name: metric_tag.Tag}
630 +}
631 +
632 +func parseSymbol(mib string, symbol interface{}) (parsedSymbol, error) {
633 + /*
634 + Parse an OID symbol.
635 +
636 + This can either be the unresolved name of a symbol:
637 +
638 + ```
639 + symbol: ifNumber
640 + ```
641 +
642 + Or a resolved OID/name object:
643 +
644 + ```
645 + symbol:
646 + OID: 1.3.6.1.2.1.2.1
647 + name: ifNumber
648 + ```
649 + */
650 + // if reflect.TypeOf(symbol) == reflect.TypeOf(string) {
651 + // // TODO, here they use ObjectIdentity(mib,symbol) to resolve the symbol. this is not straightfowrard in Go, it is a pysnmp function.
652 + // // oid:=
653 +
654 + switch s := symbol.(type) {
655 + case Symbol:
656 + oid := s.OID
657 + name := s.Name
658 + if s.ExtractValue != "" {
659 + extractValuePattern, err := regexp.Compile(s.ExtractValue)
660 + if err != nil {
661 +
662 + return parsedSymbol{}, err
663 + }
664 + return parsedSymbol{
665 + name,
666 + oid,
667 + extractValuePattern,
668 + map[string]string{name: oid},
669 + }, nil
670 + } else {
671 + return parsedSymbol{
672 + name,
673 + oid,
674 + nil,
675 + map[string]string{name: oid},
676 + }, nil
677 + }
678 + case string:
679 + return parsedSymbol{}, errors.New("string only symbol, can't support yet")
680 + case map[string]interface{}:
681 + oid, okOID := s["OID"].(string)
682 + name, okName := s["name"].(string)
683 +
684 + if !okOID || !okName {
685 +
686 + return parsedSymbol{}, fmt.Errorf("invalid symbol format: %+v", s)
687 + }
688 +
689 + return parsedSymbol{
690 + name: name,
691 + oid: oid,
692 + extractValuePattern: nil,
693 + oidsToResolve: map[string]string{name: oid},
694 + }, nil
695 +
696 + default:
697 + return parsedSymbol{}, fmt.Errorf("unsupported symbol type: %T", symbol)
698 + }
699 +
700 +}
src/go/plugin/go.d/collector/snmp/profile.go new
+183
@@ -0,0 +1,183 @@
1 +package snmp
2 +
3 +import (
4 + "fmt"
5 + "log"
6 + "os"
7 + "path/filepath"
8 + "strings"
9 +
10 + "github.com/gosnmp/gosnmp"
11 + "gopkg.in/yaml.v3"
12 +)
13 +
14 +func (s *SysObjectIDs) UnmarshalYAML(unmarshal func(any) error) error {
15 + var single string
16 + if err := unmarshal(&single); err == nil {
17 + *s = []string{single}
18 + return nil
19 + }
20 +
21 + var multiple []string
22 + if err := unmarshal(&multiple); err == nil {
23 + *s = multiple
24 + return nil
25 + }
26 +
27 + return fmt.Errorf("invalid sysobjectid format")
28 +}
29 +
30 +func (c *Collector) parseMetricsFromProfiles(matchingProfiles []*Profile) (map[string]processedMetric, error) {
31 + metricMap := map[string]processedMetric{}
32 + for _, profile := range matchingProfiles {
33 + results, err := parseMetrics(profile.Metrics)
34 + if err != nil {
35 + return nil, err
36 + }
37 +
38 + for _, oid := range results.oids {
39 + response, err := c.snmpClient.Get([]string{oid})
40 + if err != nil {
41 + return nil, err
42 + }
43 + if (response != &gosnmp.SnmpPacket{}) {
44 + for _, metric := range results.parsed_metrics {
45 + switch s := metric.(type) {
46 + case parsedSymbolMetric:
47 + // find a matching metric
48 + if s.baseoid == oid {
49 + metricName := s.name
50 + metricType := response.Variables[0].Type
51 + metricValue := response.Variables[0].Value
52 +
53 + metricMap[oid] = processedMetric{oid: oid, name: metricName, value: metricValue, metric_type: metricType}
54 + }
55 + }
56 + }
57 +
58 + }
59 + }
60 +
61 + for _, oid := range results.next_oids {
62 + if len(oid) < 1 {
63 + continue
64 + }
65 + if tableRows, err := c.walkOIDTree(oid); err != nil {
66 + log.Fatalf("Error walking OID tree: %v, oid %s", err, oid)
67 + } else {
68 + for _, metric := range results.parsed_metrics {
69 + switch s := metric.(type) {
70 + case parsedTableMetric:
71 + // find a matching metric
72 + if s.rowOID == oid {
73 + for key, value := range tableRows {
74 + value.name = s.name
75 + value.tableName = s.tableName
76 + tableRows[key] = value
77 + }
78 + metricMap = mergeProcessedMetricMaps(metricMap, tableRows)
79 + }
80 + }
81 + }
82 + }
83 +
84 + }
85 +
86 + }
87 + return metricMap, nil
88 +}
89 +
90 +func (s *Symbol) UnmarshalYAML(node *yaml.Node) error {
91 + // If scalar node, assume the value is the name.
92 + if node.Kind == yaml.ScalarNode {
93 + s.Name = node.Value
94 + return nil
95 + }
96 +
97 + // Otherwise, decode normally
98 + type plainSymbol Symbol
99 + var ps plainSymbol
100 + if err := node.Decode(&ps); err != nil {
101 + return err
102 + }
103 + *s = Symbol(ps)
104 + return nil
105 +}
106 +
107 +func LoadAllProfiles(profileDir string) (map[string]*Profile, error) {
108 + profiles := make(map[string]*Profile)
109 + err := filepath.Walk(profileDir, func(path string, info os.FileInfo, err error) error {
110 + if err != nil {
111 + return err
112 + }
113 + if strings.HasSuffix(info.Name(), ".yaml") {
114 + profile, err := LoadYAML(path, profileDir)
115 + if err == nil {
116 + profiles[path] = profile
117 + } else {
118 + log.Printf("Skipping invalid YAML: %s (%v)\n", path, err)
119 + }
120 + }
121 + return nil
122 + })
123 +
124 + if err != nil {
125 + return nil, err
126 + }
127 +
128 + return profiles, nil
129 +}
130 +
131 +func LoadYAML(filename string, basePath string) (*Profile, error) {
132 + data, err := os.ReadFile(filename)
133 + if err != nil {
134 + return nil, err
135 + }
136 +
137 + var profile Profile
138 + err = yaml.Unmarshal(data, &profile)
139 + if err != nil {
140 + return nil, err
141 + }
142 +
143 + // If the profile extends other files, load and merge them
144 + for _, parentFile := range profile.Extends {
145 + parentProfile, err := LoadYAML(filepath.Join(basePath, parentFile), basePath)
146 + if err != nil {
147 + return nil, err
148 + }
149 + MergeProfiles(&profile, parentProfile)
150 + }
151 +
152 + return &profile, nil
153 +}
154 +
155 +// Merge two profiles, giving priority to the child profile
156 +func MergeProfiles(child, parent *Profile) {
157 + if child.Metadata.Device.Fields == nil {
158 + child.Metadata.Device.Fields = make(map[string]Symbol)
159 + }
160 +
161 + for key, value := range parent.Metadata.Device.Fields {
162 + if _, exists := child.Metadata.Device.Fields[key]; !exists {
163 + child.Metadata.Device.Fields[key] = value
164 + }
165 + }
166 + child.Metrics = append(parent.Metrics, child.Metrics...)
167 +}
168 +
169 +// Find the matching profile based on sysObjectID
170 +func FindMatchingProfiles(profiles map[string]*Profile, deviceOID string) []*Profile {
171 + var matchedProfiles []*Profile
172 +
173 + for _, profile := range profiles {
174 + for _, oidPattern := range profile.SysObjectID {
175 + if strings.HasPrefix(deviceOID, strings.Split(oidPattern, "*")[0]) {
176 + matchedProfiles = append(matchedProfiles, profile)
177 + break
178 + }
179 + }
180 + }
181 +
182 + return matchedProfiles
183 +}
src/go/plugin/go.d/collector/snmp/types.go new
+238
@@ -0,0 +1,238 @@
1 +package snmp
2 +
3 +import (
4 + "regexp"
5 +
6 + "github.com/gosnmp/gosnmp"
7 +)
8 +
9 +type snmpPDU struct {
10 + value interface{}
11 + oid string
12 + metric_type gosnmp.Asn1BER
13 +}
14 +
15 +type Profile struct {
16 + Extends []string `yaml:"extends"`
17 + SysObjectID SysObjectIDs `yaml:"sysobjectid"`
18 + Metadata Metadata `yaml:"metadata"`
19 + Metrics []Metric `yaml:"metrics"`
20 +}
21 +
22 +type SysObjectIDs []string
23 +
24 +type Metadata struct {
25 + Device DeviceMetadata `yaml:"device"`
26 +}
27 +
28 +type DeviceMetadata struct {
29 + Fields map[string]Symbol `yaml:"fields"`
30 +}
31 +
32 +type Symbol struct {
33 + OID string `yaml:"OID,omitempty"`
34 + Name string `yaml:"name,omitempty"`
35 + MatchPattern string `yaml:"match_pattern,omitempty"`
36 + MatchValue string `yaml:"match_value,omitempty"`
37 + ExtractValue string `yaml:"extract_value,omitempty"`
38 +}
39 +
40 +// superset of OIDMetric, SymbolMetric and TableMetric
41 +type Metric struct {
42 + Name string `yaml:"name,omitempty"`
43 + OID string `yaml:"OID,omitempty"`
44 + //TODO check for only name existing in metric tag, as there is some case for that
45 + MetricTags []TableMetricTag `yaml:"metric_tags,omitempty"`
46 + MetricType string `yaml:"metric_type,omitempty"`
47 + Options map[string]string
48 +
49 + MIB string `yaml:"MIB,omitempty"`
50 + Symbol Symbol `yaml:"symbol,omitempty"` //can be either string or Symbol
51 +
52 + Table interface{} `yaml:"table,omitempty"` // can be either a string or Symbol
53 + Symbols []Symbol `yaml:"symbols,omitempty"`
54 +}
55 +
56 +type TableMetricTag struct {
57 + Index int `yaml:"index"`
58 + Mapping map[int]string `yaml:"mapping"`
59 +
60 + Tag string `yaml:"tag"`
61 +
62 + MIB string `yaml:"mib"`
63 + Symbol Symbol `yaml:"symbol"`
64 + Table string `yaml:"table"`
65 + IndexTransform []IndexSlice `yaml:"index_transform"`
66 +}
67 +
68 +type oidMetric struct {
69 + name string
70 + oid string
71 + metricTags []string
72 + forcedType string
73 + options map[string]string
74 +}
75 +
76 +type symbolMetric struct {
77 + mib string
78 + symbol interface{} //can be either string or Symbol
79 + forcedType string
80 + metricTags []string
81 + options map[string]string
82 +}
83 +
84 +type tableMetric struct {
85 + mib string
86 + table interface{} // can be either a string or Symbol
87 + symbols []Symbol
88 + forcedType string
89 + metricTags []TableMetricTag
90 + options map[string]string
91 +}
92 +
93 +type parsedResult struct {
94 + oids []string
95 + next_oids []string
96 + bulk_oids []string
97 + parsed_metrics []parsedMetric
98 +}
99 +
100 +type tableBatchKey struct {
101 + mib string
102 + table string
103 +}
104 +
105 +type tableBatch struct {
106 + tableOID string
107 + oids []string
108 +}
109 +
110 +type tableBatches map[tableBatchKey]tableBatch
111 +
112 +type indexTag struct {
113 + parsedMetricTag parsedMetricTag
114 + index int
115 +}
116 +
117 +type columnTag struct {
118 + parsedMetricTag parsedMetricTag
119 + column string
120 + indexSlices []IndexSlice
121 +}
122 +
123 +type indexMapping struct {
124 + tag string
125 + index int
126 + mapping map[int]string
127 +}
128 +
129 +type parsedSymbol struct {
130 + name string
131 + oid string
132 + extractValuePattern *regexp.Regexp
133 + oidsToResolve map[string]string
134 +}
135 +
136 +type parsedColumnMetricTag struct {
137 + oidsToResolve map[string]string
138 + tableBatches tableBatches
139 + columnTags []columnTag
140 +}
141 +type parsedIndexMetricTag struct {
142 + indexTags []indexTag
143 + indexMappings map[int]map[string]string
144 +}
145 +
146 +type parsedTableMetricTag struct {
147 + oidsToResolve map[string]string
148 + tableBatches tableBatches
149 + columnTags []columnTag
150 + indexTags []indexTag
151 + indexMappings map[int]map[int]string
152 +}
153 +
154 +type parsedSymbolMetric struct {
155 + name string
156 + tags []string
157 + forcedType string
158 + enforceScalar bool
159 + options map[string]string
160 + extractValuePattern *regexp.Regexp
161 + baseoid string //TODO consider changing this to OID, it will not have nested OIDs as it is a symbol
162 +}
163 +
164 +type parsedTableMetric struct {
165 + name string
166 + indexTags []indexTag
167 + columnTags []columnTag
168 + forcedType string
169 + options map[string]string
170 + extractValuePattern *regexp.Regexp
171 + rowOID string
172 + tableName string
173 + tableOID string
174 +}
175 +
176 +// union of two above
177 +type parsedMetric any
178 +
179 +// Not supported yet
180 +/*type parsedSimpleMetricTag struct {
181 + name string
182 +}
183 +
184 +type parsedMatchMetricTag struct {
185 +tags []string
186 +symbol Symbol
187 +pattern *regexp.Regexp
188 +}
189 +
190 +type symbolTag struct {
191 + parsedMetricTag parsedMetricTag
192 + symbol string
193 +}
194 +
195 +type parsedSymbolTagsResult struct {
196 + oids []string
197 + parsedSymbolTags []symbolTag
198 +}
199 +*/
200 +type parsedMetricTag struct {
201 + name string
202 +
203 + tags []string
204 + pattern *regexp.Regexp
205 + // symbol Symbol not used yet
206 +}
207 +
208 +type metricParseResult struct {
209 + oidsToFetch []string
210 + oidsToResolve map[string]string
211 + indexMappings []indexMapping
212 + tableBatches tableBatches
213 + parsedMetrics []parsedMetric
214 +}
215 +
216 +type MetricTag struct {
217 + OID string
218 + MIB string
219 + Symbol Symbol
220 + // simple tag
221 + Tag string
222 + // regex matching
223 + Match string
224 + Tags []string
225 +}
226 +
227 +type IndexSlice struct {
228 + Start int
229 + End int
230 +}
231 +
232 +type processedMetric struct {
233 + oid string
234 + name string
235 + value interface{}
236 + metric_type gosnmp.Asn1BER
237 + tableName string
238 +}