@cryptotaxi247 / netdata-1 / commits / 126e58eeb

SNMP, Custom descriptions and units (#20100)

* work so far, units and description * tgo mod tidy * use custom descriptions and units, customized profiledefinition module added * go mod tidy * replace log.fatal with proper chain error * remove snake_case and unnecessary prints * format file * expect custom ID on profiledefinition tests * move files, add license * update go mod * fix tests --------- Co-authored-by: ilyam8 <ilya@netdata.cloud>

Fotis Voutsas committed Apr 14, 2025 at 18:49 UTC 126e58eeb832d6bb163bbefecc21f266dc631904
59 files changed +4117 -88
REDISTRIBUTED.md
+5 -4
@@ -37,7 +37,8 @@ We have decided to redistribute all these, instead of using them through a CDN,
37
38 **go.d.plugin**:
39
40 -| Name | Copyright | License |
41 -|--------------------------------------------------------------------------------------------------------------------------|-----------------------------------|-------------------------------------------------------------------------------------------------------------|
42 -| [lmsensors](https://github.com/mdlayher/lmsensors) | Copyright 2016, Matt Layher | [MIT](https://github.com/mdlayher/lmsensors/blob/master/LICENSE.md) |
43 -| [snmp profiles](https://github.com/DataDog/integrations-core/tree/master/snmp/datadog_checks/snmp/data/default_profiles) | Copyright (c) 2016, Datadog, Inc. | [BSD 3-Clause "New" or "Revised" License](https://github.com/DataDog/integrations-core/blob/master/LICENSE) |
40 +| Name | Copyright | License |
41 +|------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|-------------------------------------------------------------------------------------------------------------|
42 +| [lmsensors](https://github.com/mdlayher/lmsensors) | Copyright 2016, Matt Layher | [MIT](https://github.com/mdlayher/lmsensors/blob/master/LICENSE.md) |
43 +| [snmp profiles](https://github.com/DataDog/integrations-core/tree/master/snmp/datadog_checks/snmp/data/default_profiles) | Copyright (c) 2016, Datadog, Inc. | [BSD 3-Clause "New" or "Revised" License](https://github.com/DataDog/integrations-core/blob/master/LICENSE) |
44 +| [snmp networkdevice/profile/profiledefinition](https://github.com/DataDog/datadog-agent/tree/main/pkg/networkdevice/profile/profiledefinition) | Copyright 2016-present Datadog, Inc. | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
src/go/go.mod
+4 -1
@@ -29,6 +29,7 @@ require (
29 github.com/google/uuid v1.6.0
30 github.com/gorcon/rcon v1.4.0
31 github.com/gosnmp/gosnmp v1.39.0
32 + github.com/invopop/jsonschema v0.12.0
33 github.com/jackc/pgx/v4 v4.18.3
34 github.com/jackc/pgx/v5 v5.7.4
35 github.com/jessevdk/go-flags v1.6.1
@@ -44,8 +45,10 @@ require (
45 github.com/prometheus/common v0.63.0
46 github.com/prometheus/prometheus v2.55.1+incompatible
47 github.com/redis/go-redis/v9 v9.7.3
48 + github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
49 github.com/sijms/go-ora/v2 v2.8.24
50 github.com/sourcegraph/conc v0.3.0
51 + github.com/spf13/cobra v1.9.1
52 github.com/stretchr/testify v1.10.0
53 github.com/tidwall/gjson v1.18.0
54 github.com/valyala/fastjson v1.6.4
@@ -101,7 +104,7 @@ require (
104 github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
105 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
106 github.com/huandu/xstrings v1.5.0 // indirect
104 - github.com/invopop/jsonschema v0.12.0 // indirect
107 + github.com/inconshreveable/mousetrap v1.1.0 // indirect
108 github.com/jackc/chunkreader/v2 v2.0.1 // indirect
109 github.com/jackc/pgconn v1.14.3 // indirect
110 github.com/jackc/pgio v1.0.0 // indirect
src/go/go.sum
+8
@@ -83,6 +83,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
83 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
84 github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
85 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
86 +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
87 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
88 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
89 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -198,6 +199,8 @@ github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
199 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
200 github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
201 github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
202 +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
203 +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
204 github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
205 github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
206 github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
@@ -404,6 +407,9 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
407 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
408 github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
409 github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
410 +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
411 +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
412 +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
413 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
414 github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
415 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
@@ -421,6 +427,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS
427 github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
428 github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
429 github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
430 +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
431 +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
432 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
433 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
434 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
src/go/plugin/go.d/collector/snmp/charts.go
+10 -2
@@ -199,8 +199,16 @@ func (c *Collector) addSNMPChart(processedMetric processedMetric) {
199 chart := snmpChartTemplate.Copy()
200
201 chart.ID = fmt.Sprintf(chart.ID, processedMetric.name)
202 - chart.Title = fmt.Sprintf(chart.Title, processedMetric.name)
203 - chart.Units = fmt.Sprintf(chart.Units, "TBD unit")
202 + if processedMetric.description == "" {
203 + chart.Title = fmt.Sprintf(chart.Title, "TBD Description")
204 + } else {
205 + chart.Title = fmt.Sprintf(chart.Title, processedMetric.description)
206 + }
207 + if processedMetric.unit == "" {
208 + chart.Units = fmt.Sprintf(chart.Units, "TBD unit")
209 + } else {
210 + chart.Units = fmt.Sprintf(chart.Units, processedMetric.unit)
211 + }
212 chart.Ctx = fmt.Sprintf(chart.Ctx, processedMetric.name)
213 chart.Fam = fmt.Sprintf(chart.Fam, processedMetric.name)
214
src/go/plugin/go.d/collector/snmp/collect.go
+3 -7
@@ -5,7 +5,6 @@ package snmp
5 import (
6 "errors"
7 "fmt"
8 - "log"
8 "slices"
9 "strings"
10
@@ -18,6 +17,9 @@ import (
17 )
18
19 func (c *Collector) collect() (map[string]int64, error) {
20 +
21 + mx := make(map[string]int64)
22 +
23 if c.enableProfiles {
24 sysObjectID, err := c.getSysObjectID(snmpsd.OidSysObject)
25 if err != nil {
@@ -31,7 +33,6 @@ func (c *Collector) collect() (map[string]int64, error) {
33 return nil, err
34 }
35 seen := make(map[string]bool)
34 - mx := make(map[string]int64)
36 c.makeChartsFromMetricMap(mx, metricMap, seen)
37 }
38
@@ -49,8 +50,6 @@ func (c *Collector) collect() (map[string]int64, error) {
50 }
51 }
52
52 - mx := make(map[string]int64)
53 -
53 if err := c.collectSysUptime(mx); err != nil {
54 return nil, err
55 }
@@ -83,9 +82,6 @@ func (c *Collector) makeChartsFromMetricMap(mx map[string]int64, metricMap map[s
82 if metric.tableName == "" {
83 switch s := metric.value.(type) {
84 case int:
86 -
87 - log.Println(metric)
88 -
85 name := metric.name
86 if name == "" {
87 continue
src/go/plugin/go.d/collector/snmp/collect_sys_info.go deleted
-3
@@ -1,3 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package snmp
src/go/plugin/go.d/collector/snmp/command_func.go
+3 -4
@@ -22,7 +22,6 @@ func (c *Collector) walkOIDTree(baseOID string) (map[string]processedMetric, err
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)
25
26 // If the next OID does not start with the base OID, we've reached the end of the subtree.
27 if !strings.HasPrefix(nextOID, baseOID) {
@@ -33,9 +32,9 @@ func (c *Collector) walkOIDTree(baseOID string) (map[string]processedMetric, err
32 value := fmt.Sprintf("%v", pdu.Value)
33
34 tableRows[nextOID] = processedMetric{
36 - oid: nextOID,
37 - value: value,
38 - metric_type: metricType,
35 + oid: nextOID,
36 + value: value,
37 + metricType: metricType,
38 }
39
40 currentOID = nextOID
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/LICENSE.md new
+200
@@ -0,0 +1,200 @@
1 + Apache License
2 + Version 2.0, January 2004
3 + http://www.apache.org/licenses/
4 +
5 +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 +
7 +1. Definitions.
8 +
9 + "License" shall mean the terms and conditions for use, reproduction,
10 + and distribution as defined by Sections 1 through 9 of this document.
11 +
12 + "Licensor" shall mean the copyright owner or entity authorized by
13 + the copyright owner that is granting the License.
14 +
15 + "Legal Entity" shall mean the union of the acting entity and all
16 + other entities that control, are controlled by, or are under common
17 + control with that entity. For the purposes of this definition,
18 + "control" means (i) the power, direct or indirect, to cause the
19 + direction or management of such entity, whether by contract or
20 + otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 + outstanding shares, or (iii) beneficial ownership of such entity.
22 +
23 + "You" (or "Your") shall mean an individual or Legal Entity
24 + exercising permissions granted by this License.
25 +
26 + "Source" form shall mean the preferred form for making modifications,
27 + including but not limited to software source code, documentation
28 + source, and configuration files.
29 +
30 + "Object" form shall mean any form resulting from mechanical
31 + transformation or translation of a Source form, including but
32 + not limited to compiled object code, generated documentation,
33 + and conversions to other media types.
34 +
35 + "Work" shall mean the work of authorship, whether in Source or
36 + Object form, made available under the License, as indicated by a
37 + copyright notice that is included in or attached to the work
38 + (an example is provided in the Appendix below).
39 +
40 + "Derivative Works" shall mean any work, whether in Source or Object
41 + form, that is based on (or derived from) the Work and for which the
42 + editorial revisions, annotations, elaborations, or other modifications
43 + represent, as a whole, an original work of authorship. For the purposes
44 + of this License, Derivative Works shall not include works that remain
45 + separable from, or merely link (or bind by name) to the interfaces of,
46 + the Work and Derivative Works thereof.
47 +
48 + "Contribution" shall mean any work of authorship, including
49 + the original version of the Work and any modifications or additions
50 + to that Work or Derivative Works thereof, that is intentionally
51 + submitted to Licensor for inclusion in the Work by the copyright owner
52 + or by an individual or Legal Entity authorized to submit on behalf of
53 + the copyright owner. For the purposes of this definition, "submitted"
54 + means any form of electronic, verbal, or written communication sent
55 + to the Licensor or its representatives, including but not limited to
56 + communication on electronic mailing lists, source code control systems,
57 + and issue tracking systems that are managed by, or on behalf of, the
58 + Licensor for the purpose of discussing and improving the Work, but
59 + excluding communication that is conspicuously marked or otherwise
60 + designated in writing by the copyright owner as "Not a Contribution."
61 +
62 + "Contributor" shall mean Licensor and any individual or Legal Entity
63 + on behalf of whom a Contribution has been received by Licensor and
64 + subsequently incorporated within the Work.
65 +
66 +2. Grant of Copyright License. Subject to the terms and conditions of
67 + this License, each Contributor hereby grants to You a perpetual,
68 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 + copyright license to reproduce, prepare Derivative Works of,
70 + publicly display, publicly perform, sublicense, and distribute the
71 + Work and such Derivative Works in Source or Object form.
72 +
73 +3. Grant of Patent License. Subject to the terms and conditions of
74 + this License, each Contributor hereby grants to You a perpetual,
75 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 + (except as stated in this section) patent license to make, have made,
77 + use, offer to sell, sell, import, and otherwise transfer the Work,
78 + where such license applies only to those patent claims licensable
79 + by such Contributor that are necessarily infringed by their
80 + Contribution(s) alone or by combination of their Contribution(s)
81 + with the Work to which such Contribution(s) was submitted. If You
82 + institute patent litigation against any entity (including a
83 + cross-claim or counterclaim in a lawsuit) alleging that the Work
84 + or a Contribution incorporated within the Work constitutes direct
85 + or contributory patent infringement, then any patent licenses
86 + granted to You under this License for that Work shall terminate
87 + as of the date such litigation is filed.
88 +
89 +4. Redistribution. You may reproduce and distribute copies of the
90 + Work or Derivative Works thereof in any medium, with or without
91 + modifications, and in Source or Object form, provided that You
92 + meet the following conditions:
93 +
94 + (a) You must give any other recipients of the Work or
95 + Derivative Works a copy of this License; and
96 +
97 + (b) You must cause any modified files to carry prominent notices
98 + stating that You changed the files; and
99 +
100 + (c) You must retain, in the Source form of any Derivative Works
101 + that You distribute, all copyright, patent, trademark, and
102 + attribution notices from the Source form of the Work,
103 + excluding those notices that do not pertain to any part of
104 + the Derivative Works; and
105 +
106 + (d) If the Work includes a "NOTICE" text file as part of its
107 + distribution, then any Derivative Works that You distribute must
108 + include a readable copy of the attribution notices contained
109 + within such NOTICE file, excluding those notices that do not
110 + pertain to any part of the Derivative Works, in at least one
111 + of the following places: within a NOTICE text file distributed
112 + as part of the Derivative Works; within the Source form or
113 + documentation, if provided along with the Derivative Works; or,
114 + within a display generated by the Derivative Works, if and
115 + wherever such third-party notices normally appear. The contents
116 + of the NOTICE file are for informational purposes only and
117 + do not modify the License. You may add Your own attribution
118 + notices within Derivative Works that You distribute, alongside
119 + or as an addendum to the NOTICE text from the Work, provided
120 + that such additional attribution notices cannot be construed
121 + as modifying the License.
122 +
123 + You may add Your own copyright statement to Your modifications and
124 + may provide additional or different license terms and conditions
125 + for use, reproduction, or distribution of Your modifications, or
126 + for any such Derivative Works as a whole, provided Your use,
127 + reproduction, and distribution of the Work otherwise complies with
128 + the conditions stated in this License.
129 +
130 +5. Submission of Contributions. Unless You explicitly state otherwise,
131 + any Contribution intentionally submitted for inclusion in the Work
132 + by You to the Licensor shall be under the terms and conditions of
133 + this License, without any additional terms or conditions.
134 + Notwithstanding the above, nothing herein shall supersede or modify
135 + the terms of any separate license agreement you may have executed
136 + with Licensor regarding such Contributions.
137 +
138 +6. Trademarks. This License does not grant permission to use the trade
139 + names, trademarks, service marks, or product names of the Licensor,
140 + except as required for reasonable and customary use in describing the
141 + origin of the Work and reproducing the content of the NOTICE file.
142 +
143 +7. Disclaimer of Warranty. Unless required by applicable law or
144 + agreed to in writing, Licensor provides the Work (and each
145 + Contributor provides its Contributions) on an "AS IS" BASIS,
146 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 + implied, including, without limitation, any warranties or conditions
148 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 + PARTICULAR PURPOSE. You are solely responsible for determining the
150 + appropriateness of using or redistributing the Work and assume any
151 + risks associated with Your exercise of permissions under this License.
152 +
153 +8. Limitation of Liability. In no event and under no legal theory,
154 + whether in tort (including negligence), contract, or otherwise,
155 + unless required by applicable law (such as deliberate and grossly
156 + negligent acts) or agreed to in writing, shall any Contributor be
157 + liable to You for damages, including any direct, indirect, special,
158 + incidental, or consequential damages of any character arising as a
159 + result of this License or out of the use or inability to use the
160 + Work (including but not limited to damages for loss of goodwill,
161 + work stoppage, computer failure or malfunction, or any and all
162 + other commercial damages or losses), even if such Contributor
163 + has been advised of the possibility of such damages.
164 +
165 +9. Accepting Warranty or Additional Liability. While redistributing
166 + the Work or Derivative Works thereof, You may choose to offer,
167 + and charge a fee for, acceptance of support, warranty, indemnity,
168 + or other liability obligations and/or rights consistent with this
169 + License. However, in accepting such obligations, You may act only
170 + on Your own behalf and on Your sole responsibility, not on behalf
171 + of any other Contributor, and only if You agree to indemnify,
172 + defend, and hold each Contributor harmless for any liability
173 + incurred by, or claims asserted against, such Contributor by reason
174 + of your accepting any such warranty or additional liability.
175 +
176 +END OF TERMS AND CONDITIONS
177 +
178 +APPENDIX: How to apply the Apache License to your work.
179 +
180 + To apply the Apache License to your work, attach the following
181 + boilerplate notice, with the fields enclosed by brackets "{}"
182 + replaced with your own identifying information. (Don't include
183 + the brackets!) The text should be enclosed in the appropriate
184 + comment syntax for the file format. We also recommend that a
185 + file or class name and description of purpose be included on the
186 + same "printed page" as the copyright notice for easier
187 + identification within third-party archives.
188 +
189 +Copyright 2016-present Datadog, Inc.
190 +Licensed under the Apache License, Version 2.0 (the "License");
191 +you may not use this file except in compliance with the License.
192 +You may obtain a copy of the License at
193 +
194 + http://www.apache.org/licenses/LICENSE-2.0
195 +
196 +Unless required by applicable law or agreed to in writing, software
197 +distributed under the License is distributed on an "AS IS" BASIS,
198 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
199 +See the License for the specific language governing permissions and
200 +limitations under the License.
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/README.md new
+9
@@ -0,0 +1,9 @@
1 +
2 +# Generate Json Schema
3 +
4 +```
5 +cd pkg/networkdevice/profile/profiledefinition/schema_cmd
6 +go generate
7 +```
8 +
9 +The command above will generate this jsonschema file `profiledefinition/schema/profile_rc_schema.json`.
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/bundle.go new
+17
@@ -0,0 +1,17 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +// ProfileBundleProfileItem represent a profile entry with metadata.
9 +type ProfileBundleProfileItem struct {
10 + Profile ProfileDefinition `json:"profile"`
11 +}
12 +
13 +// ProfileBundle represent a list of profiles meant to be downloaded by user.
14 +type ProfileBundle struct {
15 + CreatedTimestamp int64 `json:"created_timestamp"` // Millisecond
16 + Profiles []ProfileBundleProfileItem `json:"profiles"`
17 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/clone.go new
+38
@@ -0,0 +1,38 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2024-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +// Cloneable is a generic type for objects that can duplicate themselves.
9 +// It is exclusively used in the form [T Cloneable[T]], i.e. a type that
10 +// has a .Clone() that returns a new instance of itself.
11 +type Cloneable[T any] interface {
12 + Clone() T
13 +}
14 +
15 +// CloneSlice clones all the objects in a slice into a new slice.
16 +func CloneSlice[Slice ~[]T, T Cloneable[T]](s Slice) Slice {
17 + if s == nil {
18 + return nil
19 + }
20 + result := make(Slice, 0, len(s))
21 + for _, v := range s {
22 + result = append(result, v.Clone())
23 + }
24 + return result
25 +}
26 +
27 +// CloneMap clones a map[K]T for any cloneable type T.
28 +// The map keys are shallow-copied; values are cloned.
29 +func CloneMap[Map ~map[K]T, K comparable, T Cloneable[T]](m Map) Map {
30 + if m == nil {
31 + return nil
32 + }
33 + result := make(Map, len(m))
34 + for k, v := range m {
35 + result[k] = v.Clone()
36 + }
37 + return result
38 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/clone_test.go new
+102
@@ -0,0 +1,102 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2024-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import (
9 + "slices"
10 + "testing"
11 +
12 + "github.com/stretchr/testify/assert"
13 +)
14 +
15 +type cloneMe struct {
16 + label *string
17 + ps []int
18 +}
19 +
20 +func item(label string, ps ...int) *cloneMe {
21 + return &cloneMe{
22 + label: &label,
23 + ps: ps,
24 + }
25 +}
26 +
27 +func (c *cloneMe) Clone() *cloneMe {
28 + c2 := &cloneMe{
29 + ps: slices.Clone(c.ps),
30 + }
31 + if c.label != nil {
32 + var tmp = *c.label
33 + c2.label = &tmp
34 + }
35 + return c2
36 +}
37 +
38 +func TestCloneSlice(t *testing.T) {
39 + items := []*cloneMe{
40 + item("a", 1, 2, 3, 4),
41 + item("b", 1, 2),
42 + }
43 + itemsCopy := CloneSlice(items)
44 + *itemsCopy[0].label = "aaa"
45 + itemsCopy[1] = item("bbb", 10, 20)
46 + itemsCopy = append(itemsCopy, item("ccc", 100, 200))
47 + // items is unchanged
48 + assert.Equal(t, []*cloneMe{
49 + item("a", 1, 2, 3, 4),
50 + item("b", 1, 2),
51 + }, items)
52 + assert.Equal(t, []*cloneMe{
53 + item("aaa", 1, 2, 3, 4),
54 + item("bbb", 10, 20),
55 + item("ccc", 100, 200),
56 + }, itemsCopy)
57 +}
58 +
59 +func TestCloneMap(t *testing.T) {
60 + m := map[string]*cloneMe{
61 + "Item A": item("a", 1, 2, 3, 4),
62 + "Item B": item("b", 1, 2),
63 + }
64 + mCopy := CloneMap(m)
65 + mCopy["Item A"].ps[0] = 100
66 + mCopy["Item B"] = item("bbb", 10, 20)
67 + mCopy["Item C"] = item("ccc", 100, 200)
68 + assert.Equal(t, map[string]*cloneMe{
69 + "Item A": item("a", 1, 2, 3, 4),
70 + "Item B": item("b", 1, 2),
71 + }, m)
72 + assert.Equal(t, map[string]*cloneMe{
73 + "Item A": item("a", 100, 2, 3, 4),
74 + "Item B": item("bbb", 10, 20),
75 + "Item C": item("ccc", 100, 200),
76 + }, mCopy)
77 +}
78 +
79 +func TestCustomSliceClone(t *testing.T) {
80 + type customSlice []*cloneMe
81 +
82 + items := customSlice{
83 + item("a", 1, 2, 3, 4),
84 + item("b", 1, 2),
85 + }
86 + itemsCopy := CloneSlice(items)
87 +
88 + assert.IsType(t, customSlice{}, itemsCopy)
89 + assert.Equal(t, items, itemsCopy)
90 +}
91 +
92 +func TestCustomMapClone(t *testing.T) {
93 + type customMap map[string]*cloneMe
94 +
95 + m := customMap{
96 + "Item A": item("a", 1, 2, 3, 4),
97 + "Item B": item("b", 1, 2),
98 + }
99 + mCopy := CloneMap(m)
100 + assert.IsType(t, customMap{}, mCopy)
101 + assert.Equal(t, m, mCopy)
102 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/doc.go new
+7
@@ -0,0 +1,7 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +// Package profiledefinition contain the structure used to parse SNMP Profiles
7 +package ddprofiledefinition
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/listmap.go new
+56
@@ -0,0 +1,56 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import (
9 + "encoding/json"
10 +
11 + "github.com/invopop/jsonschema"
12 +)
13 +
14 +// ListMap is used to marshall a map into a list (map[string]T to []MapItem[T]) and vice versa.
15 +type ListMap[T any] map[string]T
16 +
17 +// MapItem is used for ListMap marshalling/unmarshalling
18 +type MapItem[T any] struct {
19 + Key string `json:"key"`
20 + Value T `json:"value"`
21 +}
22 +
23 +// MarshalJSON marshalls map to list
24 +func (lm ListMap[T]) MarshalJSON() ([]byte, error) {
25 + var items []MapItem[T]
26 + for key, value := range lm {
27 + items = append(items, MapItem[T]{Key: key, Value: value})
28 + }
29 + return json.Marshal(items)
30 +}
31 +
32 +// UnmarshalJSON unmarshalls list to map
33 +func (lm *ListMap[T]) UnmarshalJSON(data []byte) error {
34 + var items []MapItem[T]
35 + if err := json.Unmarshal(data, &items); err != nil {
36 + return err
37 + }
38 + result := make(ListMap[T])
39 + for _, item := range items {
40 + result[item.Key] = item.Value
41 + }
42 + *lm = result
43 + return nil
44 +}
45 +
46 +// JSONSchema is needed to customize jsonschema to match []MapItem[T] used in json format
47 +func (lm ListMap[T]) JSONSchema() *jsonschema.Schema {
48 + reflector := jsonschema.Reflector{
49 + AllowAdditionalProperties: false,
50 + DoNotReference: true,
51 + }
52 + schema := reflector.Reflect([]MapItem[T]{})
53 + // don't need version because this is a child of a versioned schema.
54 + schema.Version = ""
55 + return schema
56 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/listmap_test.go new
+103
@@ -0,0 +1,103 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import (
9 + "encoding/json"
10 + "testing"
11 +
12 + "github.com/invopop/jsonschema"
13 + "github.com/stretchr/testify/assert"
14 + "github.com/stretchr/testify/require"
15 +)
16 +
17 +type ExampleStruct struct {
18 + Mapping ListMap[string] `json:"mapping"`
19 +}
20 +
21 +var example = ExampleStruct{
22 + Mapping: map[string]string{
23 + "1": "aaa",
24 + "2": "bbb",
25 + },
26 +}
27 +
28 +var exampleJSON = `{
29 + "mapping": [
30 + {"key": "2", "value": "bbb"},
31 + {"key": "1", "value": "aaa"}
32 + ]
33 +}`
34 +
35 +func TestListMap_MarshalJSON(t *testing.T) {
36 + bytes, err := json.Marshal(example)
37 + require.NoError(t, err)
38 +
39 + var expectedExample ExampleStruct
40 + err = json.Unmarshal(bytes, &expectedExample)
41 + require.NoError(t, err)
42 +
43 + assert.Equal(t, example.Mapping, expectedExample.Mapping)
44 +}
45 +
46 +func TestListMap_UnmarshalJSON(t *testing.T) {
47 + var expectedExample ExampleStruct
48 + err := json.Unmarshal([]byte(exampleJSON), &expectedExample)
49 + require.NoError(t, err)
50 +
51 + assert.Equal(t, example.Mapping, expectedExample.Mapping)
52 +}
53 +
54 +func TestListMap_JSONSchema(t *testing.T) {
55 + reflector := jsonschema.Reflector{
56 + AllowAdditionalProperties: false,
57 + }
58 + schema := reflector.Reflect(&ExampleStruct{})
59 + schema.Version = "" // no version, to make the test more future-proof
60 + schemaJSON, err := json.MarshalIndent(schema, "", " ")
61 + require.NoError(t, err)
62 +
63 + expectedSchema := `
64 +{
65 + "$id": "https://github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/example-struct",
66 + "$ref": "#/$defs/ExampleStruct",
67 + "$defs": {
68 + "ExampleStruct": {
69 + "properties": {
70 + "mapping": {
71 + "$ref": "#/$defs/ListMap[string]"
72 + }
73 + },
74 + "additionalProperties": false,
75 + "type": "object",
76 + "required": [
77 + "mapping"
78 + ]
79 + },
80 + "ListMap[string]": {
81 + "items": {
82 + "properties": {
83 + "key": {
84 + "type": "string"
85 + },
86 + "value": {
87 + "type": "string"
88 + }
89 + },
90 + "additionalProperties": false,
91 + "type": "object",
92 + "required": [
93 + "key",
94 + "value"
95 + ]
96 + },
97 + "type": "array"
98 + }
99 + }
100 +}
101 +`
102 + assert.JSONEq(t, expectedSchema, string(schemaJSON))
103 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/metadata.go new
+75
@@ -0,0 +1,75 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import "github.com/invopop/jsonschema"
9 +
10 +// MetadataDeviceResource is the device resource name
11 +const MetadataDeviceResource = "device"
12 +
13 +// MetadataConfig holds configs per resource type
14 +type MetadataConfig ListMap[MetadataResourceConfig]
15 +
16 +// JSONSchema defines the JSON schema for MetadataConfig
17 +func (mc MetadataConfig) JSONSchema() *jsonschema.Schema {
18 + return ListMap[MetadataResourceConfig](mc).JSONSchema()
19 +}
20 +
21 +// MarshalJSON marshals the metadata config
22 +func (mc MetadataConfig) MarshalJSON() ([]byte, error) {
23 + return ListMap[MetadataResourceConfig](mc).MarshalJSON()
24 +}
25 +
26 +// UnmarshalJSON unmarshals the metadata config
27 +func (mc *MetadataConfig) UnmarshalJSON(data []byte) error {
28 + return (*ListMap[MetadataResourceConfig])(mc).UnmarshalJSON(data)
29 +}
30 +
31 +// Clone duplicates this MetadataConfig
32 +func (mc MetadataConfig) Clone() MetadataConfig {
33 + return CloneMap(mc)
34 +}
35 +
36 +// MetadataResourceConfig holds configs for a metadata resource
37 +type MetadataResourceConfig struct {
38 + Fields ListMap[MetadataField] `yaml:"fields" json:"fields"`
39 + IDTags MetricTagConfigList `yaml:"id_tags,omitempty" json:"id_tags,omitempty"`
40 +}
41 +
42 +// Clone duplicates this MetadataResourceConfig
43 +func (c MetadataResourceConfig) Clone() MetadataResourceConfig {
44 + return MetadataResourceConfig{
45 + Fields: CloneMap(c.Fields),
46 + IDTags: CloneSlice(c.IDTags),
47 + }
48 +}
49 +
50 +// MetadataField holds configs for a metadata field
51 +type MetadataField struct {
52 + Symbol SymbolConfig `yaml:"symbol,omitempty" json:"symbol,omitempty"`
53 + Symbols []SymbolConfig `yaml:"symbols,omitempty" json:"symbols,omitempty"`
54 + Value string `yaml:"value,omitempty" json:"value,omitempty"`
55 +}
56 +
57 +// Clone duplicates this MetadataField
58 +func (c MetadataField) Clone() MetadataField {
59 + return MetadataField{
60 + Symbol: c.Symbol.Clone(),
61 + Symbols: CloneSlice(c.Symbols),
62 + Value: c.Value,
63 + }
64 +}
65 +
66 +// NewMetadataResourceConfig returns a new metadata resource config
67 +func NewMetadataResourceConfig() MetadataResourceConfig {
68 + return MetadataResourceConfig{}
69 +}
70 +
71 +// IsMetadataResourceWithScalarOids returns true if the resource is based on scalar OIDs
72 +// at the moment, we only expect "device" resource to be based on scalar OIDs
73 +func IsMetadataResourceWithScalarOids(resource string) bool {
74 + return resource == MetadataDeviceResource
75 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/metadata_test.go new
+91
@@ -0,0 +1,91 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2024-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import (
9 + "regexp"
10 + "testing"
11 +
12 + "github.com/stretchr/testify/assert"
13 +)
14 +
15 +func makeMetadata() MetadataConfig {
16 + // This is not actually a valid config, since e.g. it has ExtractValue and
17 + // MatchPattern both set; this is just to check that every field gets copied
18 + // properly.
19 + return MetadataConfig{
20 + "device": MetadataResourceConfig{
21 + Fields: map[string]MetadataField{
22 + "name": {
23 + Value: "hey",
24 + Symbol: SymbolConfig{
25 + OID: "1.2.3",
26 + Name: "someSymbol",
27 + ExtractValue: ".*",
28 + ExtractValueCompiled: regexp.MustCompile(".*"),
29 + MatchPattern: ".*",
30 + MatchPatternCompiled: regexp.MustCompile(".*"),
31 + MatchValue: "$1",
32 + ScaleFactor: 100,
33 + Format: "mac_address",
34 + ConstantValueOne: true,
35 + MetricType: "gauge",
36 + },
37 + },
38 + },
39 + IDTags: []MetricTagConfig{
40 + {
41 + Tag: "foo",
42 + Index: 1,
43 + Column: SymbolConfig{
44 + Name: "bar",
45 + OID: "1.2.3",
46 + ExtractValue: ".*",
47 + ExtractValueCompiled: regexp.MustCompile(".*"),
48 + },
49 + OID: "2.3.4",
50 + Symbol: SymbolConfigCompat{
51 + OID: "1.2.3",
52 + Name: "someSymbol",
53 + ExtractValue: ".*",
54 + ExtractValueCompiled: regexp.MustCompile(".*"),
55 + },
56 + IndexTransform: []MetricIndexTransform{
57 + {
58 + Start: 1,
59 + End: 5,
60 + },
61 + },
62 + Mapping: map[string]string{
63 + "1": "on",
64 + "2": "off",
65 + },
66 + Match: ".*",
67 + Pattern: regexp.MustCompile(".*"),
68 + Tags: map[string]string{
69 + "foo": "bar",
70 + },
71 + SymbolTag: "ok",
72 + },
73 + },
74 + },
75 + }
76 +}
77 +
78 +func TestCloneMetadata(t *testing.T) {
79 + metadata := makeMetadata()
80 + metaCopy := metadata.Clone()
81 + assert.Equal(t, metadata, metaCopy)
82 + // Modify the copy in place
83 + metaCopy["interface"] = MetadataResourceConfig{}
84 + metaCopy["device"].Fields["foo"] = MetadataField{
85 + Value: "foo",
86 + }
87 + // Original has not changed
88 + assert.Equal(t, makeMetadata(), metadata)
89 + // New one is different
90 + assert.NotEqual(t, metadata, metaCopy)
91 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/metrics.go new
+234
@@ -0,0 +1,234 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2016-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import (
9 + "maps"
10 + "regexp"
11 + "slices"
12 +)
13 +
14 +// ProfileMetricType metric type used to override default type of the metric
15 +// By default metric type is derived from the type of the SNMP value, for example Counter32/64 -> rate.
16 +type ProfileMetricType string
17 +
18 +const (
19 + // ProfileMetricTypeGauge is used to create a gauge metric
20 + ProfileMetricTypeGauge ProfileMetricType = "gauge"
21 +
22 + // ProfileMetricTypeMonotonicCount is used to create a monotonic_count metric
23 + ProfileMetricTypeMonotonicCount ProfileMetricType = "monotonic_count"
24 +
25 + // ProfileMetricTypeMonotonicCountAndRate is used to create a monotonic_count and rate metric
26 + ProfileMetricTypeMonotonicCountAndRate ProfileMetricType = "monotonic_count_and_rate"
27 +
28 + // ProfileMetricTypeRate is used to create a rate metric
29 + ProfileMetricTypeRate ProfileMetricType = "rate"
30 +
31 + // ProfileMetricTypeFlagStream is used to create metric based on a value that represent flags
32 + // See details in https://github.com/DataDog/integrations-core/pull/7072
33 + ProfileMetricTypeFlagStream ProfileMetricType = "flag_stream"
34 +
35 + // ProfileMetricTypeCounter is DEPRECATED
36 + // `counter` is deprecated in favour of `rate`
37 + ProfileMetricTypeCounter ProfileMetricType = "counter"
38 +
39 + // ProfileMetricTypePercent is DEPRECATED
40 + // `percent` is deprecated in favour of `scale_factor`
41 + ProfileMetricTypePercent ProfileMetricType = "percent"
42 +)
43 +
44 +// SymbolConfigCompat is used to deserialize string field or SymbolConfig.
45 +// For OID/Name to Symbol harmonization:
46 +// When users declare metric tag like:
47 +//
48 +// metric_tags:
49 +// - OID: 1.2.3
50 +// symbol: aSymbol
51 +//
52 +// this will lead to OID stored as MetricTagConfig.OID and name stored as MetricTagConfig.Symbol.Name
53 +// When this happens, in ValidateEnrichMetricTags we harmonize by moving MetricTagConfig.OID to MetricTagConfig.Symbol.OID.
54 +type SymbolConfigCompat SymbolConfig
55 +
56 +// Clone creates a duplicate of this SymbolConfigCompat
57 +func (s SymbolConfigCompat) Clone() SymbolConfigCompat {
58 + return SymbolConfigCompat(SymbolConfig(s).Clone())
59 +}
60 +
61 +// SymbolConfig holds info for a single symbol/oid
62 +type SymbolConfig struct {
63 + OID string `yaml:"OID,omitempty" json:"OID,omitempty"`
64 + Name string `yaml:"name,omitempty" json:"name,omitempty"`
65 +
66 + ExtractValue string `yaml:"extract_value,omitempty" json:"extract_value,omitempty"`
67 + ExtractValueCompiled *regexp.Regexp `yaml:"-" json:"-"`
68 +
69 + MatchPattern string `yaml:"match_pattern,omitempty" json:"match_pattern,omitempty"`
70 + MatchValue string `yaml:"match_value,omitempty" json:"match_value,omitempty"`
71 + MatchPatternCompiled *regexp.Regexp `yaml:"-" json:"-"`
72 +
73 + ScaleFactor float64 `yaml:"scale_factor,omitempty" json:"scale_factor,omitempty"`
74 + Format string `yaml:"format,omitempty" json:"format,omitempty"`
75 + ConstantValueOne bool `yaml:"constant_value_one,omitempty" json:"constant_value_one,omitempty"`
76 +
77 + // `metric_type` is used for force the metric type
78 + // When empty, by default, the metric type is derived from SNMP OID value type.
79 + // Valid `metric_type` types: `gauge`, `rate`, `monotonic_count`, `monotonic_count_and_rate`
80 + // 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 +}
85 +
86 +// Clone creates a duplicate of this SymbolConfig
87 +func (s SymbolConfig) Clone() SymbolConfig {
88 + // SymbolConfig has no mutable members, so simple assignment copies it.
89 + // (technically this is false - regexes in go are mutable SOLELY through the
90 + // .Longest() method. But we never use that, so we ignore it here)
91 + return s
92 +}
93 +
94 +// MetricTagConfig holds metric tag info
95 +type MetricTagConfig struct {
96 + Tag string `yaml:"tag" json:"tag"`
97 +
98 + // Table config
99 + Index uint `yaml:"index,omitempty" json:"index,omitempty"`
100 +
101 + // DEPRECATED: Use .Symbol instead
102 + Column SymbolConfig `yaml:"column,omitempty" json:"-"`
103 +
104 + // DEPRECATED: use .Symbol instead
105 + OID string `yaml:"OID,omitempty" json:"-" jsonschema:"-"`
106 + // Symbol records the OID to be parsed. Note that .Symbol.Name is ignored:
107 + // set .Tag to specify the tag name. If a serialized Symbol is a string
108 + // instead of an object, it will be treated like {name: <value>}; this use
109 + // pattern is deprecated
110 + Symbol SymbolConfigCompat `yaml:"symbol,omitempty" json:"symbol,omitempty"`
111 +
112 + IndexTransform []MetricIndexTransform `yaml:"index_transform,omitempty" json:"index_transform,omitempty"`
113 +
114 + Mapping ListMap[string] `yaml:"mapping,omitempty" json:"mapping,omitempty"`
115 +
116 + // Regex
117 + // Match/Tags are not exposed as json (UI) since ExtractValue can be used instead
118 + Match string `yaml:"match,omitempty" json:"-"`
119 + Tags map[string]string `yaml:"tags,omitempty" json:"-"`
120 + Pattern *regexp.Regexp `yaml:"-" json:"-"`
121 +
122 + SymbolTag string `yaml:"-" json:"-"`
123 +}
124 +
125 +// Clone duplicates this MetricTagConfig
126 +func (m MetricTagConfig) Clone() MetricTagConfig {
127 + m2 := m // non-pointer assignment shallow-copies members
128 + // deep copy symbols and structures
129 + m2.Column = m.Column.Clone()
130 + m2.Symbol = m.Symbol.Clone()
131 + m2.IndexTransform = slices.Clone(m.IndexTransform)
132 + m2.Mapping = maps.Clone(m.Mapping)
133 + m2.Tags = maps.Clone(m.Tags)
134 + return m2
135 +}
136 +
137 +// MetricTagConfigList holds configs for a list of metric tags
138 +type MetricTagConfigList []MetricTagConfig
139 +
140 +// MetricIndexTransform holds configs for metric index transform
141 +type MetricIndexTransform struct {
142 + Start uint `yaml:"start" json:"start"`
143 + End uint `yaml:"end" json:"end"`
144 +}
145 +
146 +// MetricsConfigOption holds config for metrics options
147 +type MetricsConfigOption struct {
148 + Placement uint `yaml:"placement,omitempty" json:"placement,omitempty"`
149 + MetricSuffix string `yaml:"metric_suffix,omitempty" json:"metric_suffix,omitempty"`
150 +}
151 +
152 +// MetricsConfig holds configs for a metric
153 +type MetricsConfig struct {
154 + // MIB the MIB used for this metric
155 + MIB string `yaml:"MIB,omitempty" json:"MIB,omitempty"`
156 +
157 + // Table the table OID
158 + Table SymbolConfig `yaml:"table,omitempty" json:"table,omitempty"`
159 +
160 + // Symbol configs
161 + Symbol SymbolConfig `yaml:"symbol,omitempty" json:"symbol,omitempty"`
162 +
163 + // DEPRECATED: Use .Symbol instead
164 + OID string `yaml:"OID,omitempty" json:"OID,omitempty" jsonschema:"-"`
165 + // DEPRECATED: Use .Symbol instead
166 + Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"-"`
167 +
168 + // Table configs
169 + Symbols []SymbolConfig `yaml:"symbols,omitempty" json:"symbols,omitempty"`
170 +
171 + // `static_tags` is not exposed as json at the moment since we need to evaluate if we want to expose it via UI
172 + StaticTags []string `yaml:"static_tags,omitempty" json:"-"`
173 + MetricTags MetricTagConfigList `yaml:"metric_tags,omitempty" json:"metric_tags,omitempty"`
174 +
175 + // DEPRECATED: use Symbol.MetricType instead.
176 + ForcedType ProfileMetricType `yaml:"forced_type,omitempty" json:"forced_type,omitempty" jsonschema:"-"`
177 + // DEPRECATED: use Symbol.MetricType instead.
178 + MetricType ProfileMetricType `yaml:"metric_type,omitempty" json:"metric_type,omitempty" jsonschema:"-"`
179 +
180 + Options MetricsConfigOption `yaml:"options,omitempty" json:"options,omitempty"`
181 +}
182 +
183 +// Clone duplicates this MetricsConfig
184 +func (m MetricsConfig) Clone() MetricsConfig {
185 + return MetricsConfig{
186 + MIB: m.MIB,
187 + Table: m.Table.Clone(),
188 + Symbol: m.Symbol.Clone(),
189 + OID: m.OID,
190 + Name: m.Name,
191 + Symbols: CloneSlice(m.Symbols),
192 + StaticTags: slices.Clone(m.StaticTags),
193 + MetricTags: CloneSlice(m.MetricTags),
194 + ForcedType: m.ForcedType,
195 + MetricType: m.MetricType,
196 + Options: m.Options,
197 + }
198 +}
199 +
200 +// GetSymbolTags returns symbol tags
201 +func (m *MetricsConfig) GetSymbolTags() []string {
202 + var symbolTags []string
203 + for _, metricTag := range m.MetricTags {
204 + symbolTags = append(symbolTags, metricTag.SymbolTag)
205 + }
206 + return symbolTags
207 +}
208 +
209 +// IsColumn returns true if the metrics config define columns metrics
210 +func (m *MetricsConfig) IsColumn() bool {
211 + return len(m.Symbols) > 0
212 +}
213 +
214 +// IsScalar returns true if the metrics config define scalar metrics
215 +func (m *MetricsConfig) IsScalar() bool {
216 + return m.Symbol.OID != "" && m.Symbol.Name != ""
217 +}
218 +
219 +// NormalizeMetrics converts legacy syntax to new syntax
220 +// 1/ converts old symbol syntax to new symbol syntax
221 +// metric.Name and metric.OID info are moved to metric.Symbol.Name and metric.Symbol.OID
222 +func NormalizeMetrics(metrics []MetricsConfig) {
223 + for i := range metrics {
224 + metric := &metrics[i]
225 +
226 + // converts old symbol syntax to new symbol syntax
227 + if metric.Symbol.Name == "" && metric.Symbol.OID == "" && metric.Name != "" && metric.OID != "" {
228 + metric.Symbol.Name = metric.Name
229 + metric.Symbol.OID = metric.OID
230 + metric.Name = ""
231 + metric.OID = ""
232 + }
233 + }
234 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/metrics_test.go new
+180
@@ -0,0 +1,180 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2024-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import (
9 + "regexp"
10 + "testing"
11 +
12 + "github.com/stretchr/testify/assert"
13 +)
14 +
15 +func TestCloneSymbolConfig(t *testing.T) {
16 + s := SymbolConfig{
17 + OID: "1.2.3.4",
18 + Name: "foo",
19 + ExtractValue: ".*",
20 + ExtractValueCompiled: regexp.MustCompile(".*"),
21 + MatchPattern: ".*",
22 + MatchPatternCompiled: regexp.MustCompile(".*"),
23 + MatchValue: "$1",
24 + ScaleFactor: 100,
25 + Format: "mac_address",
26 + ConstantValueOne: true,
27 + MetricType: ProfileMetricTypeCounter,
28 + }
29 + s2 := s.Clone()
30 + assert.Equal(t, s, s2)
31 + // An issue with our previous deepcopy was that regexes were duplicated but
32 + // didn't keep their private internals, causing them to fail when used, so
33 + // double-check that the regex works fine.
34 + assert.True(t, s2.ExtractValueCompiled.MatchString("foo"))
35 +}
36 +
37 +func TestCloneSymbolConfigCompat(t *testing.T) {
38 + s := SymbolConfigCompat{
39 + OID: "1.2.3.4",
40 + Name: "foo",
41 + ExtractValue: ".*",
42 + ExtractValueCompiled: regexp.MustCompile(".*"),
43 + MatchPattern: ".*",
44 + MatchPatternCompiled: regexp.MustCompile(".*"),
45 + MatchValue: "$1",
46 + ScaleFactor: 100,
47 + Format: "mac_address",
48 + ConstantValueOne: true,
49 + MetricType: ProfileMetricTypeCounter,
50 + }
51 + s2 := s.Clone()
52 + assert.Equal(t, s, s2)
53 +}
54 +
55 +func TestCloneMetricTagConfig(t *testing.T) {
56 + c := MetricTagConfig{
57 + Tag: "foo",
58 + Index: 10,
59 + Column: SymbolConfig{
60 + OID: "1.2.3.4",
61 + ExtractValue: ".*",
62 + ExtractValueCompiled: regexp.MustCompile(".*"),
63 + },
64 + OID: "2.4",
65 + Symbol: SymbolConfigCompat{},
66 + IndexTransform: []MetricIndexTransform{
67 + {
68 + Start: 0,
69 + End: 1,
70 + },
71 + },
72 + Mapping: map[string]string{
73 + "1": "bar",
74 + "2": "baz",
75 + },
76 + Match: ".*",
77 + Pattern: regexp.MustCompile(".*"),
78 + Tags: map[string]string{
79 + "foo": "$1",
80 + },
81 + SymbolTag: "baz",
82 + }
83 + c2 := c.Clone()
84 + assert.Equal(t, c, c2)
85 + c2.Tags["bar"] = "$2"
86 + c2.IndexTransform = append(c2.IndexTransform, MetricIndexTransform{1, 3})
87 + c2.Mapping["3"] = "foo"
88 + c2.Tag = "bar"
89 + assert.NotEqual(t, c, c2)
90 + // Validate that c has not changed
91 + assert.Equal(t, c, MetricTagConfig{
92 + Tag: "foo",
93 + Index: 10,
94 + Column: SymbolConfig{
95 + OID: "1.2.3.4",
96 + ExtractValue: ".*",
97 + ExtractValueCompiled: regexp.MustCompile(".*"),
98 + },
99 + OID: "2.4",
100 + Symbol: SymbolConfigCompat{},
101 + IndexTransform: []MetricIndexTransform{
102 + {
103 + Start: 0,
104 + End: 1,
105 + },
106 + },
107 + Mapping: map[string]string{
108 + "1": "bar",
109 + "2": "baz",
110 + },
111 + Match: ".*",
112 + Pattern: regexp.MustCompile(".*"),
113 + Tags: map[string]string{
114 + "foo": "$1",
115 + },
116 + SymbolTag: "baz",
117 + })
118 +}
119 +
120 +func TestCloneMetricsConfig(t *testing.T) {
121 + buildConf := func() MetricsConfig {
122 + return MetricsConfig{
123 + MIB: "FOO-MIB",
124 + Table: SymbolConfig{
125 + OID: "1.2.3.4",
126 + ExtractValue: ".*",
127 + ExtractValueCompiled: regexp.MustCompile(".*"),
128 + },
129 + Symbol: SymbolConfig{
130 + OID: "1.2.3.4",
131 + ExtractValue: ".*",
132 + ExtractValueCompiled: regexp.MustCompile(".*"),
133 + },
134 + OID: "1.2.3.4",
135 + Name: "foo",
136 + Symbols: []SymbolConfig{
137 + {
138 + OID: "1.2.3.4",
139 + ExtractValue: ".*",
140 + ExtractValueCompiled: regexp.MustCompile(".*"),
141 + },
142 + },
143 + StaticTags: []string{
144 + "foo",
145 + "bar",
146 + },
147 + MetricTags: []MetricTagConfig{
148 + {
149 + IndexTransform: make([]MetricIndexTransform, 0),
150 + },
151 + },
152 + ForcedType: ProfileMetricTypeCounter,
153 + MetricType: ProfileMetricTypeGauge,
154 + Options: MetricsConfigOption{
155 + Placement: 1,
156 + MetricSuffix: ".foo",
157 + },
158 + }
159 + }
160 + conf := buildConf()
161 + unchanged := buildConf()
162 +
163 + conf2 := conf.Clone()
164 + assert.Equal(t, conf, conf2)
165 + conf2.StaticTags[0] = "baz"
166 + conf2.MetricTags[0].IndexTransform = []MetricIndexTransform{{5, 7}}
167 + conf2.Options.Placement = 2
168 + conf2.Options.MetricSuffix = ".bar"
169 + assert.Equal(t, unchanged, conf)
170 + assert.NotEqual(t, conf, conf2)
171 +}
172 +
173 +func TestCloneEmpty(t *testing.T) {
174 + mc := MetricsConfig{}
175 + assert.Equal(t, mc, mc.Clone())
176 + sym := SymbolConfig{}
177 + assert.Equal(t, sym, sym.Clone())
178 + tag := MetricTagConfig{}
179 + assert.Equal(t, tag, tag.Clone())
180 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/normalize_cmd/cmd/root.go new
+125
@@ -0,0 +1,125 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2016-present Datadog, Inc.
5 +
6 +// Package cmd implements a cobra command for validating and normalizing profiles.
7 +package cmd
8 +
9 +import (
10 + "encoding/json"
11 + "fmt"
12 + "os"
13 + "path/filepath"
14 +
15 + "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
16 + "github.com/spf13/cobra"
17 + "gopkg.in/yaml.v2"
18 +)
19 +
20 +// rootCmd represents the base command when called without any subcommands
21 +var rootCmd = &cobra.Command{
22 + Use: "normalize_cmd FILE [FILE...]",
23 + Short: "Validate and normalize profiles.",
24 + Long: `normalize_cmd is a tool for validating and normalizing profiles.
25 +
26 + Each profile file passed in will be parsed, and any errors in them will be
27 + reported. If an output directory is specified with -o, then the profiles will
28 + also be normalized, migrating legacy and deprecated structures to their
29 + modern counterparts, and written to the output directory.`,
30 +
31 + Run: func(cmd *cobra.Command, args []string) {
32 + outdir, err := cmd.Flags().GetString("outdir")
33 + if err != nil {
34 + fmt.Printf("parse failure %v, unable to generate output\n", err)
35 + }
36 + useJSON, err := cmd.Flags().GetBool("json")
37 + if err != nil {
38 + fmt.Printf("parse failure %v, unable to generate output\n", err)
39 + }
40 + for _, filePath := range args {
41 + filename := filepath.Base(filePath)
42 + name := filename[:len(filename)-len(filepath.Ext(filename))] // remove extension
43 + def, errors := GetProfile(filePath)
44 + if len(errors) > 0 {
45 + fmt.Printf("*** %d error(s) in profile %q ***\n", len(errors), filePath)
46 + for _, e := range errors {
47 + fmt.Println(" ", e)
48 + }
49 + fmt.Println()
50 + continue
51 + }
52 + if err := WriteProfile(def, name, outdir, useJSON); err != nil {
53 + fmt.Println(err)
54 + }
55 + }
56 + },
57 +}
58 +
59 +// GetProfile parses a profile from a file path and validates it.
60 +func GetProfile(filePath string) (*profiledefinition.ProfileDefinition, []string) {
61 + buf, err := os.ReadFile(filePath)
62 + if err != nil {
63 + return nil, []string{fmt.Sprintf("unable to read file: %v", err)}
64 + }
65 + def := profiledefinition.NewProfileDefinition()
66 + err = yaml.Unmarshal(buf, def)
67 + if err != nil {
68 + return nil, []string{fmt.Sprintf("unable to parse profile: %v", err)}
69 + }
70 + errors := profiledefinition.ValidateEnrichProfile(def)
71 + if len(errors) > 0 {
72 + return nil, errors
73 + }
74 + return def, nil
75 +}
76 +
77 +// WriteProfile writes a profile to disk.
78 +func WriteProfile(def *profiledefinition.ProfileDefinition, name string, outdir string, useJSON bool) error {
79 + if outdir == "" {
80 + return nil
81 + }
82 + var filename string
83 + var data []byte
84 + if useJSON {
85 + var err error
86 + filename = name + ".json"
87 + data, err = json.Marshal(def)
88 + if err != nil {
89 + return fmt.Errorf("unable to marshal profile %s: %w", name, err)
90 + }
91 + } else {
92 + var err error
93 + data, err = yaml.Marshal(def)
94 + filename = name + ".yaml"
95 + if err != nil {
96 + return fmt.Errorf("unable to marshal profile %s: %w", name, err)
97 + }
98 + }
99 + outfile := filepath.Join(outdir, filename)
100 + f, err := os.Create(outfile)
101 + if err != nil {
102 + return fmt.Errorf("unable to create file %s: %w", outfile, err)
103 + }
104 + defer func() {
105 + _ = f.Close()
106 + }()
107 + _, err = f.Write(data)
108 + if err != nil {
109 + return fmt.Errorf("unable to write to file %s: %w", outfile, err)
110 + }
111 + return nil
112 +}
113 +
114 +// Execute runs the command.
115 +func Execute() {
116 + err := rootCmd.Execute()
117 + if err != nil {
118 + os.Exit(1)
119 + }
120 +}
121 +
122 +func init() {
123 + rootCmd.Flags().StringP("outdir", "o", "", "Output path for normalized files. If blank, inputs will be validated but not output.")
124 + rootCmd.Flags().BoolP("json", "j", false, "Output as JSON")
125 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/normalize_cmd/main.go new
+11
@@ -0,0 +1,11 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2016-present Datadog, Inc.
5 +package main
6 +
7 +import "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition/normalize_cmd/cmd"
8 +
9 +func main() {
10 + cmd.Execute()
11 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/profile_definition.go new
+79
@@ -0,0 +1,79 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import "slices"
9 +
10 +// DeviceMeta holds device related static metadata
11 +// DEPRECATED in favour of profile metadata syntax
12 +type DeviceMeta struct {
13 + // deprecated in favour of new `ProfileDefinition.Metadata` syntax
14 + Vendor string `yaml:"vendor,omitempty" json:"vendor,omitempty"`
15 +}
16 +
17 +// ProfileDefinition is the root profile structure. The ProfileDefinition is currently used in:
18 +// 1/ SNMP Integration: the profiles are in yaml profiles. Yaml profiles include default datadog profiles and user custom profiles.
19 +// The serialisation of yaml profiles are defined by the yaml annotation and few custom unmarshaller (see yaml_utils.go).
20 +// 2/ Datadog backend: the profiles are in json format, they are used to store profiles created via UI.
21 +// The serialisation of json profiles are defined by the json annotation.
22 +type ProfileDefinition struct {
23 + Name string `yaml:"name,omitempty" json:"name,omitempty"`
24 + Description string `yaml:"description,omitempty" json:"description,omitempty"`
25 + SysObjectIDs StringArray `yaml:"sysobjectid,omitempty" json:"sysobjectid,omitempty"`
26 + Extends []string `yaml:"extends,omitempty" json:"extends,omitempty"`
27 + Metadata MetadataConfig `yaml:"metadata,omitempty" json:"metadata,omitempty"`
28 + MetricTags []MetricTagConfig `yaml:"metric_tags,omitempty" json:"metric_tags,omitempty"`
29 + StaticTags []string `yaml:"static_tags,omitempty" json:"static_tags,omitempty"`
30 + Metrics []MetricsConfig `yaml:"metrics,omitempty" json:"metrics,omitempty"`
31 +
32 + // DEPRECATED: Use metadata directly
33 + Device DeviceMeta `yaml:"device,omitempty" json:"device,omitempty" jsonschema:"device,omitempty"`
34 +
35 + // Version is the profile version.
36 + // It is currently used only with downloaded/RC profiles.
37 + Version uint64 `yaml:"version,omitempty" json:"version,omitempty"`
38 +}
39 +
40 +// DeviceProfileRcConfig represent the profile stored in remote config.
41 +type DeviceProfileRcConfig struct {
42 + Profile ProfileDefinition `json:"profile_definition"`
43 +}
44 +
45 +// NewProfileDefinition creates a new ProfileDefinition
46 +func NewProfileDefinition() *ProfileDefinition {
47 + p := &ProfileDefinition{}
48 + p.Metadata = make(MetadataConfig)
49 + return p
50 +}
51 +
52 +// SplitOIDs returns two slices (scalars, columns) of all scalar and column OIDs requested by this profile.
53 +func (p *ProfileDefinition) SplitOIDs(includeMetadata bool) ([]string, []string) {
54 + if includeMetadata {
55 + return splitOIDs(p.Metrics, p.MetricTags, p.Metadata)
56 + }
57 + return splitOIDs(p.Metrics, p.MetricTags, nil)
58 +}
59 +
60 +// Clone duplicates this ProfileDefinition
61 +func (p *ProfileDefinition) Clone() *ProfileDefinition {
62 + if p == nil {
63 + return nil
64 + }
65 + return &ProfileDefinition{
66 + Name: p.Name,
67 + Description: p.Description,
68 + SysObjectIDs: slices.Clone(p.SysObjectIDs),
69 + Extends: slices.Clone(p.Extends),
70 + Metadata: CloneMap(p.Metadata),
71 + MetricTags: CloneSlice(p.MetricTags),
72 + StaticTags: slices.Clone(p.StaticTags),
73 + Metrics: CloneSlice(p.Metrics),
74 + Device: DeviceMeta{
75 + Vendor: p.Device.Vendor,
76 + },
77 + Version: p.Version,
78 + }
79 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/doc.go new
+7
@@ -0,0 +1,7 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +// Package schema contain json schema related code
7 +package schema
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/generate.go new
+26
@@ -0,0 +1,26 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package schema
7 +
8 +import (
9 + "encoding/json"
10 + "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
11 + "github.com/invopop/jsonschema"
12 +)
13 +
14 +// GenerateJSONSchema generate jsonschema from profiledefinition.DeviceProfileRcConfig
15 +func GenerateJSONSchema() ([]byte, error) {
16 + reflector := jsonschema.Reflector{
17 + AllowAdditionalProperties: false,
18 + }
19 + schema := reflector.Reflect(&profiledefinition.DeviceProfileRcConfig{})
20 + schemaJSON, err := json.MarshalIndent(schema, "", " ")
21 + if err != nil {
22 + return nil, err
23 + }
24 + schemaJSON = append(schemaJSON, byte('\n'))
25 + return schemaJSON, nil
26 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/generate_test.go new
+20
@@ -0,0 +1,20 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package schema
7 +
8 +import (
9 + "testing"
10 +
11 + "github.com/stretchr/testify/assert"
12 + "github.com/stretchr/testify/require"
13 +)
14 +
15 +func TestGenerateJSONSchemaIsInSync(t *testing.T) {
16 + schemaJSON, err := GenerateJSONSchema()
17 + require.NoError(t, err)
18 +
19 + assert.JSONEq(t, string(GetDeviceProfileRcConfigJsonschema()), string(schemaJSON))
20 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/profile_rc_schema.json new
+463
@@ -0,0 +1,463 @@
1 +{
2 + "$schema": "https://json-schema.org/draft/2020-12/schema",
3 + "$id": "https://github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition/device-profile-rc-config",
4 + "$ref": "#/$defs/DeviceProfileRcConfig",
5 + "$defs": {
6 + "DeviceMeta": {
7 + "properties": {
8 + "vendor": {
9 + "type": "string"
10 + }
11 + },
12 + "additionalProperties": false,
13 + "type": "object"
14 + },
15 + "DeviceProfileRcConfig": {
16 + "properties": {
17 + "profile_definition": {
18 + "$ref": "#/$defs/ProfileDefinition"
19 + }
20 + },
21 + "additionalProperties": false,
22 + "type": "object",
23 + "required": [
24 + "profile_definition"
25 + ]
26 + },
27 + "ListMap[string]": {
28 + "items": {
29 + "properties": {
30 + "key": {
31 + "type": "string"
32 + },
33 + "value": {
34 + "type": "string"
35 + }
36 + },
37 + "additionalProperties": false,
38 + "type": "object",
39 + "required": [
40 + "key",
41 + "value"
42 + ]
43 + },
44 + "type": "array"
45 + },
46 + "MetadataConfig": {
47 + "items": {
48 + "properties": {
49 + "key": {
50 + "type": "string"
51 + },
52 + "value": {
53 + "properties": {
54 + "fields": {
55 + "items": {
56 + "properties": {
57 + "key": {
58 + "type": "string"
59 + },
60 + "value": {
61 + "properties": {
62 + "symbol": {
63 + "properties": {
64 + "OID": {
65 + "type": "string"
66 + },
67 + "name": {
68 + "type": "string"
69 + },
70 + "extract_value": {
71 + "type": "string"
72 + },
73 + "match_pattern": {
74 + "type": "string"
75 + },
76 + "match_value": {
77 + "type": "string"
78 + },
79 + "scale_factor": {
80 + "type": "number"
81 + },
82 + "format": {
83 + "type": "string"
84 + },
85 + "constant_value_one": {
86 + "type": "boolean"
87 + },
88 + "metric_type": {
89 + "type": "string"
90 + }
91 + },
92 + "additionalProperties": false,
93 + "type": "object"
94 + },
95 + "symbols": {
96 + "items": {
97 + "properties": {
98 + "OID": {
99 + "type": "string"
100 + },
101 + "name": {
102 + "type": "string"
103 + },
104 + "extract_value": {
105 + "type": "string"
106 + },
107 + "match_pattern": {
108 + "type": "string"
109 + },
110 + "match_value": {
111 + "type": "string"
112 + },
113 + "scale_factor": {
114 + "type": "number"
115 + },
116 + "format": {
117 + "type": "string"
118 + },
119 + "constant_value_one": {
120 + "type": "boolean"
121 + },
122 + "metric_type": {
123 + "type": "string"
124 + }
125 + },
126 + "additionalProperties": false,
127 + "type": "object"
128 + },
129 + "type": "array"
130 + },
131 + "value": {
132 + "type": "string"
133 + }
134 + },
135 + "additionalProperties": false,
136 + "type": "object"
137 + }
138 + },
139 + "additionalProperties": false,
140 + "type": "object",
141 + "required": [
142 + "key",
143 + "value"
144 + ]
145 + },
146 + "type": "array"
147 + },
148 + "id_tags": {
149 + "items": {
150 + "properties": {
151 + "tag": {
152 + "type": "string"
153 + },
154 + "index": {
155 + "type": "integer"
156 + },
157 + "symbol": {
158 + "properties": {
159 + "OID": {
160 + "type": "string"
161 + },
162 + "name": {
163 + "type": "string"
164 + },
165 + "extract_value": {
166 + "type": "string"
167 + },
168 + "match_pattern": {
169 + "type": "string"
170 + },
171 + "match_value": {
172 + "type": "string"
173 + },
174 + "scale_factor": {
175 + "type": "number"
176 + },
177 + "format": {
178 + "type": "string"
179 + },
180 + "constant_value_one": {
181 + "type": "boolean"
182 + },
183 + "metric_type": {
184 + "type": "string"
185 + }
186 + },
187 + "additionalProperties": false,
188 + "type": "object"
189 + },
190 + "index_transform": {
191 + "items": {
192 + "properties": {
193 + "start": {
194 + "type": "integer"
195 + },
196 + "end": {
197 + "type": "integer"
198 + }
199 + },
200 + "additionalProperties": false,
201 + "type": "object",
202 + "required": [
203 + "start",
204 + "end"
205 + ]
206 + },
207 + "type": "array"
208 + },
209 + "mapping": {
210 + "items": {
211 + "properties": {
212 + "key": {
213 + "type": "string"
214 + },
215 + "value": {
216 + "type": "string"
217 + }
218 + },
219 + "additionalProperties": false,
220 + "type": "object",
221 + "required": [
222 + "key",
223 + "value"
224 + ]
225 + },
226 + "type": "array"
227 + }
228 + },
229 + "additionalProperties": false,
230 + "type": "object",
231 + "required": [
232 + "tag"
233 + ]
234 + },
235 + "type": "array"
236 + }
237 + },
238 + "additionalProperties": false,
239 + "type": "object",
240 + "required": [
241 + "fields"
242 + ]
243 + }
244 + },
245 + "additionalProperties": false,
246 + "type": "object",
247 + "required": [
248 + "key",
249 + "value"
250 + ]
251 + },
252 + "type": "array"
253 + },
254 + "MetricIndexTransform": {
255 + "properties": {
256 + "start": {
257 + "type": "integer"
258 + },
259 + "end": {
260 + "type": "integer"
261 + }
262 + },
263 + "additionalProperties": false,
264 + "type": "object",
265 + "required": [
266 + "start",
267 + "end"
268 + ]
269 + },
270 + "MetricTagConfig": {
271 + "properties": {
272 + "tag": {
273 + "type": "string"
274 + },
275 + "index": {
276 + "type": "integer"
277 + },
278 + "symbol": {
279 + "$ref": "#/$defs/SymbolConfigCompat"
280 + },
281 + "index_transform": {
282 + "items": {
283 + "$ref": "#/$defs/MetricIndexTransform"
284 + },
285 + "type": "array"
286 + },
287 + "mapping": {
288 + "$ref": "#/$defs/ListMap[string]"
289 + }
290 + },
291 + "additionalProperties": false,
292 + "type": "object",
293 + "required": [
294 + "tag"
295 + ]
296 + },
297 + "MetricTagConfigList": {
298 + "items": {
299 + "$ref": "#/$defs/MetricTagConfig"
300 + },
301 + "type": "array"
302 + },
303 + "MetricsConfig": {
304 + "properties": {
305 + "MIB": {
306 + "type": "string"
307 + },
308 + "table": {
309 + "$ref": "#/$defs/SymbolConfig"
310 + },
311 + "symbol": {
312 + "$ref": "#/$defs/SymbolConfig"
313 + },
314 + "symbols": {
315 + "items": {
316 + "$ref": "#/$defs/SymbolConfig"
317 + },
318 + "type": "array"
319 + },
320 + "metric_tags": {
321 + "$ref": "#/$defs/MetricTagConfigList"
322 + },
323 + "options": {
324 + "$ref": "#/$defs/MetricsConfigOption"
325 + }
326 + },
327 + "additionalProperties": false,
328 + "type": "object"
329 + },
330 + "MetricsConfigOption": {
331 + "properties": {
332 + "placement": {
333 + "type": "integer"
334 + },
335 + "metric_suffix": {
336 + "type": "string"
337 + }
338 + },
339 + "additionalProperties": false,
340 + "type": "object"
341 + },
342 + "ProfileDefinition": {
343 + "properties": {
344 + "name": {
345 + "type": "string"
346 + },
347 + "description": {
348 + "type": "string"
349 + },
350 + "sysobjectid": {
351 + "$ref": "#/$defs/StringArray"
352 + },
353 + "extends": {
354 + "items": {
355 + "type": "string"
356 + },
357 + "type": "array"
358 + },
359 + "metadata": {
360 + "$ref": "#/$defs/MetadataConfig"
361 + },
362 + "metric_tags": {
363 + "items": {
364 + "$ref": "#/$defs/MetricTagConfig"
365 + },
366 + "type": "array"
367 + },
368 + "static_tags": {
369 + "items": {
370 + "type": "string"
371 + },
372 + "type": "array"
373 + },
374 + "metrics": {
375 + "items": {
376 + "$ref": "#/$defs/MetricsConfig"
377 + },
378 + "type": "array"
379 + },
380 + "device": {
381 + "$ref": "#/$defs/DeviceMeta"
382 + },
383 + "version": {
384 + "type": "integer"
385 + }
386 + },
387 + "additionalProperties": false,
388 + "type": "object"
389 + },
390 + "StringArray": {
391 + "items": {
392 + "type": "string"
393 + },
394 + "type": "array"
395 + },
396 + "SymbolConfig": {
397 + "properties": {
398 + "OID": {
399 + "type": "string"
400 + },
401 + "name": {
402 + "type": "string"
403 + },
404 + "extract_value": {
405 + "type": "string"
406 + },
407 + "match_pattern": {
408 + "type": "string"
409 + },
410 + "match_value": {
411 + "type": "string"
412 + },
413 + "scale_factor": {
414 + "type": "number"
415 + },
416 + "format": {
417 + "type": "string"
418 + },
419 + "constant_value_one": {
420 + "type": "boolean"
421 + },
422 + "metric_type": {
423 + "type": "string"
424 + }
425 + },
426 + "additionalProperties": false,
427 + "type": "object"
428 + },
429 + "SymbolConfigCompat": {
430 + "properties": {
431 + "OID": {
432 + "type": "string"
433 + },
434 + "name": {
435 + "type": "string"
436 + },
437 + "extract_value": {
438 + "type": "string"
439 + },
440 + "match_pattern": {
441 + "type": "string"
442 + },
443 + "match_value": {
444 + "type": "string"
445 + },
446 + "scale_factor": {
447 + "type": "number"
448 + },
449 + "format": {
450 + "type": "string"
451 + },
452 + "constant_value_one": {
453 + "type": "boolean"
454 + },
455 + "metric_type": {
456 + "type": "string"
457 + }
458 + },
459 + "additionalProperties": false,
460 + "type": "object"
461 + }
462 + }
463 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/profile_rc_schema_test.go new
+100
@@ -0,0 +1,100 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package schema
7 +
8 +import (
9 + "encoding/json"
10 + "fmt"
11 + "io/fs"
12 + "os"
13 + "path/filepath"
14 + "strings"
15 + "testing"
16 +
17 + "github.com/santhosh-tekuri/jsonschema/v5"
18 + "github.com/stretchr/testify/assert"
19 + "github.com/stretchr/testify/require"
20 +)
21 +
22 +type testcaseExpected struct {
23 + ErrorPatterns []string `json:"error_patterns"`
24 +}
25 +
26 +func Test_DeviceProfileRcConfigJsonSchema(t *testing.T) {
27 + // language=json
28 + instanceJSON := `{
29 + "profile_definition": {
30 + "name": "my-profile",
31 + "version": 10
32 + }
33 +}`
34 +
35 + err := assertAgainstSchema(t, instanceJSON)
36 +
37 + fmt.Printf("%#v\n", err) // using %#v prints errors hierarchy
38 + require.NoError(t, err)
39 +}
40 +
41 +func Test_Schema_TextCases(t *testing.T) {
42 + var testcases []string
43 + err := filepath.WalkDir("./testcases", func(s string, d fs.DirEntry, e error) error {
44 + if e != nil {
45 + return e
46 + }
47 + if strings.HasSuffix(d.Name(), "_exp.json") {
48 + return nil
49 + }
50 + if filepath.Ext(d.Name()) == ".json" {
51 + testcases = append(testcases, s)
52 + }
53 + return nil
54 + })
55 + require.NoError(t, err)
56 +
57 + for _, testcaseJSONPath := range testcases {
58 + t.Run(testcaseJSONPath, func(t *testing.T) {
59 + content, err := os.ReadFile(testcaseJSONPath)
60 + require.NoError(t, err)
61 +
62 + validationErr := assertAgainstSchema(t, string(content))
63 + validationErrStr := fmt.Sprintf("%#v\n", validationErr) // using %#v prints errors hierarchy
64 +
65 + if validationErr != nil {
66 + // Print validation error to ease debugging tests
67 + fmt.Print("--- ACTUAL VALIDATION ERRORS --------------------------------------------\n")
68 + fmt.Print(validationErrStr)
69 + fmt.Print("-------------------------------------------------------------------------\n")
70 + }
71 +
72 + testcaseExpectedErrPath := strings.ReplaceAll(testcaseJSONPath, ".json", "_exp.json")
73 + testcaseExpectedErr, err := os.ReadFile(testcaseExpectedErrPath)
74 + require.NoError(t, err)
75 +
76 + var expected testcaseExpected
77 + err = json.Unmarshal(testcaseExpectedErr, &expected)
78 + require.NoError(t, err)
79 +
80 + for _, expectedErrorPattern := range expected.ErrorPatterns {
81 + assert.Regexp(t, expectedErrorPattern, validationErrStr)
82 + }
83 + if len(expected.ErrorPatterns) == 0 {
84 + assert.NoError(t, validationErr)
85 + }
86 + })
87 + }
88 +}
89 +
90 +func assertAgainstSchema(t *testing.T, instanceJSON string) error {
91 + sch, err := jsonschema.CompileString("schema.json", string(GetDeviceProfileRcConfigJsonschema()))
92 + require.NoError(t, err)
93 +
94 + var v interface{}
95 + err = json.Unmarshal([]byte(instanceJSON), &v)
96 + require.NoError(t, err)
97 +
98 + err = sch.Validate(v)
99 + return err
100 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/schema.go new
+18
@@ -0,0 +1,18 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package schema
7 +
8 +import _ "embed"
9 +
10 +var (
11 + //go:embed profile_rc_schema.json
12 + deviceProfileRcConfigJsonschema []byte
13 +)
14 +
15 +// GetDeviceProfileRcConfigJsonschema returns deviceProfileRcConfigJsonschema
16 +func GetDeviceProfileRcConfigJsonschema() []byte {
17 + return deviceProfileRcConfigJsonschema
18 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_deprtd_forced_t.json new
+14
@@ -0,0 +1,14 @@
1 +{
2 + "profile_definition": {
3 + "name": "some-profile",
4 + "metrics": [
5 + {
6 + "symbol": {
7 + "OID": "1.3.6.1.4.1.318.1.1.1.2.2.5.0",
8 + "name": "upsAdvBatteryNumOfBattPacks"
9 + },
10 + "forced_type": "gauge"
11 + }
12 + ]
13 + }
14 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_deprtd_forced_t_exp.json new
+5
@@ -0,0 +1,5 @@
1 +{
2 + "error_patterns": [
3 + "defs/MetricsConfig/additionalProperties] additionalProperties 'forced_type' not allowed"
4 + ]
5 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_deprtd_oid_name.json new
+11
@@ -0,0 +1,11 @@
1 +{
2 + "profile_definition": {
3 + "name": "some-profile",
4 + "metrics": [
5 + {
6 + "OID": "1.3.6.1.4.1.318.1.1.1.2.2.5.0",
7 + "name": "upsAdvBatteryNumOfBattPacks"
8 + }
9 + ]
10 + }
11 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_deprtd_oid_name_exp.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "error_patterns": [
3 + "MetricsConfig/additionalProperties] additionalProperties .*'OID'.* not allowed",
4 + "MetricsConfig/additionalProperties] additionalProperties .*'name'.* not allowed"
5 + ]
6 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_empty_profile.json new
+1
@@ -0,0 +1 @@
1 +{}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_empty_profile_exp.json new
+5
@@ -0,0 +1,5 @@
1 +{
2 + "error_patterns": [
3 + "missing properties: 'profile_definition'"
4 + ]
5 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_mult_errors.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "profile_definition": {
3 + "name": 123,
4 + "unknown_field": "abc"
5 + }
6 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_mult_errors_exp.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "error_patterns": [
3 + "ProfileDefinition/properties/name/type\\] expected string, but got number",
4 + "ProfileDefinition/additionalProperties] additionalProperties 'unknown_field' not allowed"
5 + ]
6 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_symbol_field.json new
+49
@@ -0,0 +1,49 @@
1 +{
2 + "profile_definition": {
3 + "name": "apc-ups",
4 + "extends": [
5 + "apc.yaml"
6 + ],
7 + "sysobjectid": [
8 + "1.3.6.1.4.1.318.1.*"
9 + ],
10 + "device": {
11 + "vendor": "apc"
12 + },
13 + "metadata": {
14 + "device": {
15 + "fields": {
16 + "version": {
17 + "symbol": {
18 + "OID": "1.3.6.1.4.1.318.1.1.1.1.2.1.0",
19 + "name": "upsAdvIdentFirmwareRevision"
20 + }
21 + },
22 + "product_name": {
23 + "symbol": {
24 + "OID": "1.3.6.1.4.1.318.1.1.1.1.1.1.0",
25 + "name": "upsBasicIdentModel"
26 + }
27 + }
28 + }
29 + }
30 + },
31 + "metrics": [
32 + {
33 + "MIB": "PowerNet-MIB",
34 + "symbol": {
35 + "OID": "1.3.6.1.4.1.318.1.1.1.2.2.5.0",
36 + "name": "upsAdvBatteryNumOfBattPacks",
37 + "unknown_field": "value"
38 + }
39 + }
40 + ],
41 + "metric_tags": [
42 + {
43 + "tag": "model",
44 + "OID": "1.3.6.1.4.1.318.1.1.1.1.1.1.0",
45 + "symbol": "upsBasicIdentModel"
46 + }
47 + ]
48 + }
49 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_symbol_field_exp.json new
+5
@@ -0,0 +1,5 @@
1 +{
2 + "error_patterns": [
3 + "defs/SymbolConfig/additionalProperties] additionalProperties 'unknown_field' not allowed"
4 + ]
5 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_unknown_field.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "profile_definition": {
3 + "name": "some-profile",
4 + "unknown_field": "abc"
5 + }
6 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_unknown_field_exp.json new
+5
@@ -0,0 +1,5 @@
1 +{
2 + "error_patterns": [
3 + "additionalProperties 'unknown_field' not allowed"
4 + ]
5 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_device_vendor.json new
+9
@@ -0,0 +1,9 @@
1 +{
2 + "profile_definition": {
3 + "name": "some-profile",
4 + "version": 10,
5 + "device": {
6 + "vendor": "abc"
7 + }
8 + }
9 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_device_vendor_exp.json new
+4
@@ -0,0 +1,4 @@
1 +{
2 + "error_patterns": [
3 + ]
4 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_empty_profile.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "profile_definition": {
3 + "name": "some-profile",
4 + "version": 10
5 + }
6 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_empty_profile_exp.json new
+4
@@ -0,0 +1,4 @@
1 +{
2 + "error_patterns": [
3 + ]
4 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_metric_tag_mapping.json new
+45
@@ -0,0 +1,45 @@
1 +{
2 + "profile_definition": {
3 + "name": "apc-ups",
4 + "version": 10,
5 + "extends": [
6 + "apc.yaml"
7 + ],
8 + "sysobjectid": [
9 + "1.3.6.1.4.1.318.1.*"
10 + ],
11 + "metrics": [
12 + {
13 + "MIB": "PowerNet-MIB",
14 + "table": {
15 + "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2",
16 + "name": "upsOutletGroupStatusTable"
17 + },
18 + "symbols": [
19 + {
20 + "name": "upsOutletGroupStatus",
21 + "constant_value_one": true
22 + },
23 + {
24 + "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2.1.3",
25 + "name": "upsOutletGroupStatusGroupState"
26 + }
27 + ],
28 + "metric_tags": [
29 + {
30 + "tag": "ups_outlet_group_status_group_state",
31 + "symbol": {
32 + "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2.1.3",
33 + "name": "upsOutletGroupStatusGroupState"
34 + },
35 + "mapping": [
36 + {"key": "1", "value": "ups_outlet_group_status_on"},
37 + {"key": "2", "value": "ups_outlet_group_status_off"},
38 + {"key": "3", "value": "ups_outlet_group_status_unknown"}
39 + ]
40 + }
41 + ]
42 + }
43 + ]
44 + }
45 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_metric_tag_mapping_exp.json new
+4
@@ -0,0 +1,4 @@
1 +{
2 + "error_patterns": [
3 + ]
4 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_profile_apc_ups.json new
+246
@@ -0,0 +1,246 @@
1 +{
2 + "profile_definition": {
3 + "name": "apc-ups",
4 + "version": 10,
5 + "extends": [
6 + "apc.yaml"
7 + ],
8 + "sysobjectid": [
9 + "1.3.6.1.4.1.318.1.*"
10 + ],
11 + "device": {
12 + "vendor": "abc"
13 + },
14 + "metrics": [
15 + {
16 + "MIB": "PowerNet-MIB",
17 + "symbol": {
18 + "OID": "1.3.6.1.4.1.318.1.1.1.2.2.5.0",
19 + "name": "upsAdvBatteryNumOfBattPacks"
20 + }
21 + },
22 + {
23 + "MIB": "POWERNET-MIB",
24 + "symbol": {
25 + "OID": "1.3.6.1.4.1.318.1.1.1.2.2.6.0",
26 + "name": "upsAdvBatteryNumOfBadBattPacks"
27 + }
28 + },
29 + {
30 + "MIB": "PowerNet-MIB",
31 + "symbol": {
32 + "OID": "1.3.6.1.4.1.318.1.1.1.2.2.4.0",
33 + "name": "upsAdvBatteryReplaceIndicator"
34 + }
35 + },
36 + {
37 + "MIB": "PowerNet-MIB",
38 + "symbol": {
39 + "OID": "1.3.6.1.4.1.318.1.1.1.2.2.3.0",
40 + "name": "upsAdvBatteryRunTimeRemaining"
41 + }
42 + },
43 + {
44 + "MIB": "PowerNet-MIB",
45 + "symbol": {
46 + "OID": "1.3.6.1.4.1.318.1.1.1.2.2.2.0",
47 + "name": "upsAdvBatteryTemperature"
48 + }
49 + },
50 + {
51 + "MIB": "PowerNet-MIB",
52 + "symbol": {
53 + "OID": "1.3.6.1.4.1.318.1.1.1.2.2.1.0",
54 + "name": "upsAdvBatteryCapacity"
55 + }
56 + },
57 + {
58 + "MIB": "PowerNet-MIB",
59 + "symbol": {
60 + "OID": "1.3.6.1.4.1.318.1.1.1.2.3.2.0",
61 + "name": "upsHighPrecExtdBatteryTemperature"
62 + }
63 + },
64 + {
65 + "MIB": "PowerNet-MIB",
66 + "symbol": {
67 + "OID": "1.3.6.1.4.1.318.1.1.1.3.2.1.0",
68 + "name": "upsAdvInputLineVoltage"
69 + }
70 + },
71 + {
72 + "MIB": "PowerNet-MIB",
73 + "symbol": {
74 + "OID": "1.3.6.1.4.1.318.1.1.1.3.2.4.0",
75 + "name": "upsAdvInputFrequency"
76 + }
77 + },
78 + {
79 + "MIB": "PowerNet-MIB",
80 + "symbol": {
81 + "OID": "1.3.6.1.4.1.318.1.1.1.3.3.4.0",
82 + "name": "upsHighPrecInputFrequency"
83 + }
84 + },
85 + {
86 + "MIB": "PowerNet-MIB",
87 + "symbol": {
88 + "OID": "1.3.6.1.4.1.318.1.1.1.3.3.1.0",
89 + "name": "upsHighPrecInputLineVoltage"
90 + }
91 + },
92 + {
93 + "MIB": "PowerNet-MIB",
94 + "symbol": {
95 + "OID": "1.3.6.1.4.1.318.1.1.1.4.3.4.0",
96 + "name": "upsHighPrecOutputCurrent"
97 + }
98 + },
99 + {
100 + "MIB": "PowerNet-MIB",
101 + "symbol": {
102 + "OID": "1.3.6.1.4.1.318.1.1.1.3.2.5.0",
103 + "name": "upsAdvInputLineFailCause"
104 + }
105 + },
106 + {
107 + "MIB": "PowerNet-MIB",
108 + "symbol": {
109 + "OID": "1.3.6.1.4.1.318.1.1.1.4.2.1.0",
110 + "name": "upsAdvOutputVoltage"
111 + }
112 + },
113 + {
114 + "MIB": "PowerNet-MIB",
115 + "symbol": {
116 + "OID": "1.3.6.1.4.1.318.1.1.1.4.2.2.0",
117 + "name": "upsAdvOutputFrequency"
118 + }
119 + },
120 + {
121 + "MIB": "PowerNet-MIB",
122 + "symbol": {
123 + "OID": "1.3.6.1.4.1.318.1.1.1.4.2.3.0",
124 + "name": "upsAdvOutputLoad"
125 + }
126 + },
127 + {
128 + "MIB": "PowerNet-MIB",
129 + "symbol": {
130 + "OID": "1.3.6.1.4.1.318.1.1.1.4.2.4.0",
131 + "name": "upsAdvOutputCurrent"
132 + }
133 + },
134 + {
135 + "MIB": "PowerNet-MIB",
136 + "symbol": {
137 + "OID": "1.3.6.1.4.1.318.1.1.1.4.2.8.0",
138 + "name": "upsAdvOutputActivePower"
139 + }
140 + },
141 + {
142 + "MIB": "PowerNet-MIB",
143 + "symbol": {
144 + "OID": "1.3.6.1.4.1.318.1.1.1.4.2.9.0",
145 + "name": "upsAdvOutputApparentPower"
146 + }
147 + },
148 + {
149 + "MIB": "PowerNet-MIB",
150 + "symbol": {
151 + "OID": "1.3.6.1.4.1.318.1.1.1.4.2.12.0",
152 + "name": "upsAdvOutputEnergyUsage"
153 + }
154 + },
155 + {
156 + "MIB": "PowerNet-MIB",
157 + "symbol": {
158 + "OID": "1.3.6.1.4.1.318.1.1.1.2.1.2.0",
159 + "name": "upsBasicBatteryTimeOnBattery"
160 + }
161 + },
162 + {
163 + "MIB": "PowerNet-MIB",
164 + "symbol": {
165 + "OID": "1.3.6.1.4.1.318.1.1.1.7.2.3.0",
166 + "name": "upsAdvTestDiagnosticsResults"
167 + }
168 + },
169 + {
170 + "MIB": "PowerNet-MIB",
171 + "table": {
172 + "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2",
173 + "name": "upsOutletGroupStatusTable"
174 + },
175 + "symbols": [
176 + {
177 + "name": "upsOutletGroupStatus",
178 + "constant_value_one": true
179 + },
180 + {
181 + "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2.1.3",
182 + "name": "upsOutletGroupStatusGroupState"
183 + }
184 + ],
185 + "metric_tags": [
186 + {
187 + "tag": "outlet_group_name",
188 + "symbol": {
189 + "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2.1.2",
190 + "name": "upsOutletGroupStatusName"
191 + }
192 + },
193 + {
194 + "tag": "ups_outlet_group_status_group_state",
195 + "symbol": {
196 + "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2.1.3",
197 + "name": "upsOutletGroupStatusGroupState"
198 + },
199 + "mapping": [
200 + {"key": "1", "value": "ups_outlet_group_status_on"},
201 + {"key": "2", "value": "ups_outlet_group_status_off"},
202 + {"key": "3", "value": "ups_outlet_group_status_unknown"}
203 + ]
204 + }
205 + ]
206 + },
207 + {
208 + "MIB": "PowerNet-MIB",
209 + "symbol": {
210 + "OID": "1.3.6.1.4.1.318.1.1.25.1.2.1.6.0",
211 + "name": "uioSensorStatusTemperatureDegC"
212 + }
213 + }
214 + ],
215 + "metric_tags": [
216 + {
217 + "tag": "model",
218 + "symbol": {
219 + "OID": "1.3.6.1.4.1.318.1.1.1.1.1.1.0",
220 + "name": "upsBasicIdentModel"
221 + }
222 + },
223 + {
224 + "tag": "serial_num",
225 + "symbol": {
226 + "OID": "1.3.6.1.4.1.318.1.1.1.1.2.3.0",
227 + "name": "upsAdvIdentSerialNumber"
228 + }
229 + },
230 + {
231 + "tag": "firmware_version",
232 + "symbol": {
233 + "OID": "1.3.6.1.4.1.318.1.1.1.1.2.1.0",
234 + "name": "upsAdvIdentFirmwareRevision"
235 + }
236 + },
237 + {
238 + "tag": "ups_name",
239 + "symbol": {
240 + "OID": "1.3.6.1.4.1.318.1.1.1.1.1.2.0",
241 + "name": "upsBasicIdentName"
242 + }
243 + }
244 + ]
245 + }
246 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_profile_apc_ups_exp.json new
+4
@@ -0,0 +1,4 @@
1 +{
2 + "error_patterns": [
3 + ]
4 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema_cmd/main.go new
+35
@@ -0,0 +1,35 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2016-present Datadog, Inc.
5 +
6 +// Package main holds main related files
7 +package main
8 +
9 +import (
10 + "flag"
11 + "fmt"
12 + "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition/schema"
13 + "os"
14 +)
15 +
16 +//go:generate go run ./main.go ../schema/profile_rc_schema.json
17 +
18 +func main() {
19 + var output string
20 +
21 + flag.StringVar(&output, "output", "../schema/profile_rc_schema.json", "Generate JSON schema generated file")
22 + flag.Parse()
23 +
24 + schemaJSON, err := schema.GenerateJSONSchema()
25 + if err != nil {
26 + panic(err)
27 + }
28 +
29 + err = os.WriteFile(output, schemaJSON, 0664)
30 + if err != nil {
31 + panic(err)
32 + }
33 +
34 + fmt.Printf("File generated at %s\n", output)
35 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/splitoids.go new
+63
@@ -0,0 +1,63 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import "sort"
9 +
10 +// splitOIDs returns all scalar and column (i.e. table) OIDs from metrics, tags, and metadata
11 +func splitOIDs(metrics []MetricsConfig, globalTags []MetricTagConfig, metadata MetadataConfig) ([]string, []string) {
12 + scalars := make(map[string]bool)
13 + columns := make(map[string]bool)
14 + // Singular metric values are scalars; metrics with .Symbols are tables,
15 + // and their symbols and tags are both expected to be columns.
16 + for _, metric := range metrics {
17 + scalars[metric.Symbol.OID] = true
18 + for _, symbolConfig := range metric.Symbols {
19 + columns[symbolConfig.OID] = true
20 + }
21 + for _, metricTag := range metric.MetricTags {
22 + columns[metricTag.Symbol.OID] = true
23 + }
24 + }
25 + // Global tags are scalar by definition
26 + for _, tag := range globalTags {
27 + scalars[tag.Symbol.OID] = true
28 + }
29 + // Metadata fields are all columns except when IsMetadataResourceWithScalarOids is true
30 + for resource, metadataConfig := range metadata {
31 + target := columns
32 + if IsMetadataResourceWithScalarOids(resource) {
33 + target = scalars
34 + }
35 + for _, field := range metadataConfig.Fields {
36 + target[field.Symbol.OID] = true
37 + for _, symbol := range field.Symbols {
38 + target[symbol.OID] = true
39 + }
40 + }
41 + for _, tagConfig := range metadataConfig.IDTags {
42 + target[tagConfig.Symbol.OID] = true
43 + }
44 + }
45 + scalarValues := make([]string, 0, len(scalars))
46 + for key := range scalars {
47 + if key == "" {
48 + continue
49 + }
50 + scalarValues = append(scalarValues, key)
51 + }
52 + columnValues := make([]string, 0, len(columns))
53 + for key := range columns {
54 + if key == "" {
55 + continue
56 + }
57 + columnValues = append(columnValues, key)
58 + }
59 + // Sort them for deterministic testing
60 + sort.Strings(scalarValues)
61 + sort.Strings(columnValues)
62 + return scalarValues, columnValues
63 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/splitoids_test.go new
+228
@@ -0,0 +1,228 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2023-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import (
9 + "testing"
10 +
11 + "github.com/stretchr/testify/assert"
12 +)
13 +
14 +func TestSplitOIDs(t *testing.T) {
15 + type testCase struct {
16 + name string
17 + metrics []MetricsConfig
18 + tags []MetricTagConfig
19 + metadata MetadataConfig
20 + expectedScalars []string
21 + expectedColumns []string
22 + }
23 + testCases := []testCase{
24 + {
25 + name: "scalar metric",
26 + metrics: []MetricsConfig{{
27 + Symbol: SymbolConfig{
28 + OID: "1.2.3.4",
29 + },
30 + }},
31 + expectedScalars: []string{"1.2.3.4"},
32 + }, {
33 + name: "tabular metric",
34 + metrics: []MetricsConfig{{
35 + Symbols: []SymbolConfig{{OID: "1.2.3.4"}},
36 + MetricTags: []MetricTagConfig{
37 + {Symbol: SymbolConfigCompat{
38 + OID: "2.3.4.5",
39 + }},
40 + },
41 + }},
42 + expectedColumns: []string{"1.2.3.4", "2.3.4.5"},
43 + }, {
44 + name: "tags",
45 + tags: []MetricTagConfig{
46 + {Symbol: SymbolConfigCompat{
47 + OID: "2.3.4.5",
48 + },
49 + },
50 + },
51 + expectedScalars: []string{"2.3.4.5"},
52 + }, {
53 + name: "metadata",
54 + metadata: map[string]MetadataResourceConfig{
55 + "device": {
56 + Fields: map[string]MetadataField{
57 + "vendor": {Value: "static"},
58 + "name": {Symbol: SymbolConfig{
59 + OID: "1.1",
60 + }},
61 + "os_name": {Symbols: []SymbolConfig{
62 + {
63 + OID: "1.2",
64 + }, {
65 + OID: "1.3",
66 + },
67 + }},
68 + },
69 + IDTags: []MetricTagConfig{
70 + {Symbol: SymbolConfigCompat{
71 + OID: "1.4",
72 + }},
73 + },
74 + },
75 + "not_device": {
76 + Fields: map[string]MetadataField{
77 + "vendor": {Value: "static"},
78 + "name": {Symbol: SymbolConfig{
79 + OID: "2.1",
80 + }},
81 + "os_name": {Symbols: []SymbolConfig{
82 + {
83 + OID: "2.2",
84 + }, {
85 + OID: "2.3",
86 + },
87 + }},
88 + },
89 + IDTags: []MetricTagConfig{
90 + {Symbol: SymbolConfigCompat{
91 + OID: "2.4",
92 + }},
93 + },
94 + },
95 + },
96 + expectedScalars: []string{"1.1", "1.2", "1.3", "1.4"},
97 + expectedColumns: []string{"2.1", "2.2", "2.3", "2.4"},
98 + }, {
99 + name: "duplicates",
100 + metrics: []MetricsConfig{
101 + {
102 + Symbol: SymbolConfig{OID: "1.1"},
103 + }, {
104 + Symbols: []SymbolConfig{
105 + {OID: "1.1"},
106 + },
107 + MetricTags: []MetricTagConfig{
108 + {Symbol: SymbolConfigCompat{OID: "1.1"}},
109 + },
110 + }},
111 + metadata: map[string]MetadataResourceConfig{
112 + "device": {
113 + Fields: map[string]MetadataField{
114 + "name": {Symbol: SymbolConfig{
115 + OID: "1.1",
116 + }},
117 + },
118 + },
119 + },
120 + expectedScalars: []string{"1.1"},
121 + expectedColumns: []string{"1.1"},
122 + }, {
123 + name: "sorting",
124 + metrics: []MetricsConfig{
125 + {Symbol: SymbolConfig{OID: "1.2"}},
126 + {Symbol: SymbolConfig{OID: "1.1"}},
127 + {
128 + Symbols: []SymbolConfig{
129 + {OID: "2.4"},
130 + {OID: "2.3"},
131 + },
132 + MetricTags: []MetricTagConfig{
133 + {Symbol: SymbolConfigCompat{OID: "2.2"}},
134 + {Symbol: SymbolConfigCompat{OID: "2.1"}},
135 + },
136 + }},
137 + expectedScalars: []string{"1.1", "1.2"},
138 + expectedColumns: []string{"2.1", "2.2", "2.3", "2.4"},
139 + },
140 + }
141 + for _, tc := range testCases {
142 + t.Run(tc.name, func(t *testing.T) {
143 + scalars, columns := splitOIDs(tc.metrics, tc.tags, tc.metadata)
144 + expectedScalars := tc.expectedScalars
145 + if expectedScalars == nil {
146 + expectedScalars = []string{}
147 + }
148 + assert.Equal(t, expectedScalars, scalars)
149 + expectedColumns := tc.expectedColumns
150 + if expectedColumns == nil {
151 + expectedColumns = []string{}
152 + }
153 + assert.Equal(t, expectedColumns, columns)
154 + })
155 + }
156 +}
157 +
158 +func TestProfileSplitOIDs(t *testing.T) {
159 + p := ProfileDefinition{
160 + Metrics: []MetricsConfig{
161 + {Symbol: SymbolConfig{OID: "1.2"}},
162 + {Symbol: SymbolConfig{OID: "1.1"}},
163 + {
164 + Symbols: []SymbolConfig{
165 + {OID: "2.4"},
166 + {OID: "2.3"},
167 + },
168 + MetricTags: []MetricTagConfig{
169 + {Symbol: SymbolConfigCompat{OID: "2.2"}},
170 + {Symbol: SymbolConfigCompat{OID: "2.1"}},
171 + },
172 + },
173 + },
174 + MetricTags: []MetricTagConfig{
175 + {Symbol: SymbolConfigCompat{OID: "1.4"}},
176 + {Symbol: SymbolConfigCompat{OID: "1.3"}},
177 + },
178 + Metadata: map[string]MetadataResourceConfig{
179 + "device": {
180 + Fields: map[string]MetadataField{
181 + "vendor": {Value: "static"},
182 + "name": {Symbol: SymbolConfig{
183 + OID: "3.4",
184 + }},
185 + "os_name": {Symbols: []SymbolConfig{
186 + {
187 + OID: "3.3",
188 + }, {
189 + OID: "3.2",
190 + },
191 + }},
192 + },
193 + IDTags: []MetricTagConfig{
194 + {Symbol: SymbolConfigCompat{
195 + OID: "3.1",
196 + }},
197 + },
198 + },
199 + "not_device": {
200 + Fields: map[string]MetadataField{
201 + "vendor": {Value: "static"},
202 + "name": {Symbol: SymbolConfig{
203 + OID: "4.4",
204 + }},
205 + "os_name": {Symbols: []SymbolConfig{
206 + {
207 + OID: "4.3",
208 + }, {
209 + OID: "4.2",
210 + },
211 + }},
212 + },
213 + IDTags: []MetricTagConfig{
214 + {Symbol: SymbolConfigCompat{
215 + OID: "4.1",
216 + }},
217 + },
218 + },
219 + },
220 + }
221 + scalars, columns := p.SplitOIDs(true)
222 + assert.Equal(t, []string{"1.1", "1.2", "1.3", "1.4", "3.1", "3.2", "3.3", "3.4"}, scalars)
223 + assert.Equal(t, []string{"2.1", "2.2", "2.3", "2.4", "4.1", "4.2", "4.3", "4.4"}, columns)
224 +
225 + scalars, columns = p.SplitOIDs(false)
226 + assert.Equal(t, []string{"1.1", "1.2", "1.3", "1.4"}, scalars)
227 + assert.Equal(t, []string{"2.1", "2.2", "2.3", "2.4"}, columns)
228 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/validation.go new
+250
@@ -0,0 +1,250 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2016-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import (
9 + "fmt"
10 + "regexp"
11 +)
12 +
13 +var validMetadataResources = map[string]map[string]bool{
14 + "device": {
15 + "name": true,
16 + "description": true,
17 + "sys_object_id": true,
18 + "location": true,
19 + "serial_number": true,
20 + "vendor": true,
21 + "version": true,
22 + "product_name": true,
23 + "model": true,
24 + "os_name": true,
25 + "os_version": true,
26 + "os_hostname": true,
27 + "type": true,
28 + },
29 + "interface": {
30 + "name": true,
31 + "alias": true,
32 + "description": true,
33 + "mac_address": true,
34 + "admin_status": true,
35 + "oper_status": true,
36 + },
37 +}
38 +
39 +// SymbolContext represent the context in which the symbol is used
40 +type SymbolContext int64
41 +
42 +// ScalarSymbol enums
43 +const (
44 + ScalarSymbol SymbolContext = iota
45 + ColumnSymbol
46 + MetricTagSymbol
47 + MetadataSymbol
48 +)
49 +
50 +// ValidateEnrichProfile validates a profile and normalizes it.
51 +func ValidateEnrichProfile(profile *ProfileDefinition) []string {
52 + NormalizeMetrics(profile.Metrics)
53 + // migrate legacy profile.Device.Vendor to metadata field.
54 + // TODO this is commented out because it changes existing behavior; uncomment when we want to support this properly.
55 + // if profile.Device.Vendor != "" {
56 + // dev, ok := profile.Metadata["device"]
57 + // if !ok {
58 + // profile.Metadata["device"] = MetadataResourceConfig{
59 + // Fields: make(map[string]MetadataField),
60 + // }
61 + // dev = profile.Metadata["device"]
62 + // }
63 + // _, ok = dev.Fields["vendor"]
64 + // if !ok {
65 + // dev.Fields["vendor"] = MetadataField{
66 + // Value: profile.Device.Vendor,
67 + // }
68 + // }
69 + // profile.Device.Vendor = ""
70 + // }
71 + errors := ValidateEnrichMetadata(profile.Metadata)
72 + errors = append(errors, ValidateEnrichMetrics(profile.Metrics)...)
73 + errors = append(errors, ValidateEnrichMetricTags(profile.MetricTags)...)
74 + return errors
75 +}
76 +
77 +// ValidateEnrichMetricTags validates and normalizes metric tags
78 +func ValidateEnrichMetricTags(metricTags []MetricTagConfig) []string {
79 + var errors []string
80 + for i := range metricTags {
81 + errors = append(errors, validateEnrichMetricTag(&metricTags[i])...)
82 + }
83 + return errors
84 +}
85 +
86 +// ValidateEnrichMetrics will validate MetricsConfig and enrich it.
87 +// Example of enrichment:
88 +// - storage of compiled regex pattern
89 +func ValidateEnrichMetrics(metrics []MetricsConfig) []string {
90 + var errors []string
91 + for i := range metrics {
92 + metricConfig := &metrics[i]
93 + if !metricConfig.IsScalar() && !metricConfig.IsColumn() {
94 + errors = append(errors, fmt.Sprintf("either a table symbol or a scalar symbol must be provided: %#v", metricConfig))
95 + }
96 + if metricConfig.IsScalar() && metricConfig.IsColumn() {
97 + errors = append(errors, fmt.Sprintf("table symbol and scalar symbol cannot be both provided: %#v", metricConfig))
98 + }
99 + if metricConfig.IsScalar() {
100 + errors = append(errors, validateEnrichSymbol(&metricConfig.Symbol, ScalarSymbol)...)
101 + }
102 + if metricConfig.IsColumn() {
103 + for j := range metricConfig.Symbols {
104 + errors = append(errors, validateEnrichSymbol(&metricConfig.Symbols[j], ColumnSymbol)...)
105 + }
106 + if len(metricConfig.MetricTags) == 0 {
107 + errors = append(errors, fmt.Sprintf("column symbols doesn't have a 'metric_tags' section (%+v), all its metrics will use the same tags; "+
108 + "if the table has multiple rows, only one row will be submitted; "+
109 + "please add at least one discriminating metric tag (such as a row index) "+
110 + "to ensure metrics of all rows are submitted", metricConfig.Symbols))
111 + }
112 + for i := range metricConfig.MetricTags {
113 + metricTag := &metricConfig.MetricTags[i]
114 + errors = append(errors, validateEnrichMetricTag(metricTag)...)
115 + }
116 + }
117 + // Setting forced_type value to metric_type value for backward compatibility
118 + if metricConfig.MetricType == "" && metricConfig.ForcedType != "" {
119 + metricConfig.MetricType = metricConfig.ForcedType
120 + }
121 + metricConfig.ForcedType = ""
122 + }
123 + return errors
124 +}
125 +
126 +// ValidateEnrichMetadata will validate MetadataConfig and enrich it.
127 +func ValidateEnrichMetadata(metadata MetadataConfig) []string {
128 + var errors []string
129 + for resName := range metadata {
130 + _, isValidRes := validMetadataResources[resName]
131 + if !isValidRes {
132 + errors = append(errors, fmt.Sprintf("invalid resource: %s", resName))
133 + } else {
134 + res := metadata[resName]
135 + for fieldName := range res.Fields {
136 + _, isValidField := validMetadataResources[resName][fieldName]
137 + if !isValidField {
138 + errors = append(errors, fmt.Sprintf("invalid resource (%s) field: %s", resName, fieldName))
139 + continue
140 + }
141 + field := res.Fields[fieldName]
142 + for i := range field.Symbols {
143 + errors = append(errors, validateEnrichSymbol(&field.Symbols[i], MetadataSymbol)...)
144 + }
145 + if field.Symbol.OID != "" {
146 + errors = append(errors, validateEnrichSymbol(&field.Symbol, MetadataSymbol)...)
147 + }
148 + res.Fields[fieldName] = field
149 + }
150 + metadata[resName] = res
151 + }
152 + if resName == "device" && len(metadata[resName].IDTags) > 0 {
153 + errors = append(errors, "device resource does not support custom id_tags")
154 + }
155 + for i := range metadata[resName].IDTags {
156 + metricTag := &metadata[resName].IDTags[i]
157 + errors = append(errors, validateEnrichMetricTag(metricTag)...)
158 + }
159 + }
160 + return errors
161 +}
162 +
163 +func validateEnrichSymbol(symbol *SymbolConfig, symbolContext SymbolContext) []string {
164 + var errors []string
165 + if symbol.Name == "" {
166 + errors = append(errors, fmt.Sprintf("symbol name missing: name=`%s` oid=`%s`", symbol.Name, symbol.OID))
167 + }
168 + if symbol.OID == "" {
169 + if symbolContext == ColumnSymbol && !symbol.ConstantValueOne {
170 + errors = append(errors, fmt.Sprintf("symbol oid or send_as_one missing: name=`%s` oid=`%s`", symbol.Name, symbol.OID))
171 + } else if symbolContext != ColumnSymbol {
172 + errors = append(errors, fmt.Sprintf("symbol oid missing: name=`%s` oid=`%s`", symbol.Name, symbol.OID))
173 + }
174 + }
175 + if symbol.ExtractValue != "" {
176 + pattern, err := regexp.Compile(symbol.ExtractValue)
177 + if err != nil {
178 + errors = append(errors, fmt.Sprintf("cannot compile `extract_value` (%s): %s", symbol.ExtractValue, err.Error()))
179 + } else {
180 + symbol.ExtractValueCompiled = pattern
181 + }
182 + }
183 + if symbol.MatchPattern != "" {
184 + pattern, err := regexp.Compile(symbol.MatchPattern)
185 + if err != nil {
186 + errors = append(errors, fmt.Sprintf("cannot compile `extract_value` (%s): %s", symbol.ExtractValue, err.Error()))
187 + } else {
188 + symbol.MatchPatternCompiled = pattern
189 + }
190 + }
191 + if symbolContext != ColumnSymbol && symbol.ConstantValueOne {
192 + errors = append(errors, "`constant_value_one` cannot be used outside of tables")
193 + }
194 + if (symbolContext != ColumnSymbol && symbolContext != ScalarSymbol) && symbol.MetricType != "" {
195 + errors = append(errors, "`metric_type` cannot be used outside scalar/table metric symbols and metrics root")
196 + }
197 + return errors
198 +}
199 +func validateEnrichMetricTag(metricTag *MetricTagConfig) []string {
200 + var errors []string
201 + if (metricTag.Column.OID != "" || metricTag.Column.Name != "") && (metricTag.Symbol.OID != "" || metricTag.Symbol.Name != "") {
202 + errors = append(errors, fmt.Sprintf("metric tag symbol and column cannot be both declared: symbol=%v, column=%v", metricTag.Symbol, metricTag.Column))
203 + }
204 +
205 + // Move deprecated metricTag.Column to metricTag.Symbol
206 + if metricTag.Column.OID != "" || metricTag.Column.Name != "" {
207 + metricTag.Symbol = SymbolConfigCompat(metricTag.Column)
208 + metricTag.Column = SymbolConfig{}
209 + }
210 +
211 + // OID/Name to Symbol harmonization:
212 + // When users declare metric tag like:
213 + // metric_tags:
214 + // - OID: 1.2.3
215 + // symbol: aSymbol
216 + // this will lead to OID stored as MetricTagConfig.OID and name stored as MetricTagConfig.Symbol.Name
217 + // When this happens, we harmonize by moving MetricTagConfig.OID to MetricTagConfig.Symbol.OID.
218 + if metricTag.OID != "" && metricTag.Symbol.OID != "" {
219 + errors = append(errors, fmt.Sprintf("metric tag OID and symbol.OID cannot be both declared: OID=%s, symbol.OID=%s", metricTag.OID, metricTag.Symbol.OID))
220 + }
221 + if metricTag.OID != "" && metricTag.Symbol.OID == "" {
222 + metricTag.Symbol.OID = metricTag.OID
223 + metricTag.OID = ""
224 + }
225 + if metricTag.Symbol.OID != "" || metricTag.Symbol.Name != "" {
226 + symbol := SymbolConfig(metricTag.Symbol)
227 + errors = append(errors, validateEnrichSymbol(&symbol, MetricTagSymbol)...)
228 + metricTag.Symbol = SymbolConfigCompat(symbol)
229 + }
230 + if metricTag.Match != "" {
231 + pattern, err := regexp.Compile(metricTag.Match)
232 + if err != nil {
233 + errors = append(errors, fmt.Sprintf("cannot compile `match` (`%s`): %s", metricTag.Match, err.Error()))
234 + } else {
235 + metricTag.Pattern = pattern
236 + }
237 + if len(metricTag.Tags) == 0 {
238 + errors = append(errors, fmt.Sprintf("`tags` mapping must be provided if `match` (`%s`) is defined", metricTag.Match))
239 + }
240 + }
241 + if len(metricTag.Mapping) > 0 && metricTag.Tag == "" {
242 + errors = append(errors, fmt.Sprintf("``tag` must be provided if `mapping` (`%s`) is defined", metricTag.Mapping))
243 + }
244 + for _, transform := range metricTag.IndexTransform {
245 + if transform.Start > transform.End {
246 + errors = append(errors, fmt.Sprintf("transform rule end should be greater than start. Invalid rule: %#v", transform))
247 + }
248 + }
249 + return errors
250 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/validation_test.go new
+871
@@ -0,0 +1,871 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2016-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import (
9 + "fmt"
10 + "regexp"
11 + "testing"
12 +
13 + "github.com/stretchr/testify/assert"
14 +)
15 +
16 +func Test_ValidateEnrichMetrics(t *testing.T) {
17 + tests := []struct {
18 + name string
19 + metrics []MetricsConfig
20 + expectedErrors []string
21 + expectedMetrics []MetricsConfig
22 + }{
23 + {
24 + name: "either table symbol or scalar symbol must be provided",
25 + metrics: []MetricsConfig{
26 + {},
27 + },
28 + expectedErrors: []string{
29 + "either a table symbol or a scalar symbol must be provided",
30 + },
31 + expectedMetrics: []MetricsConfig{
32 + {},
33 + },
34 + },
35 + {
36 + name: "table column symbols and scalar symbol cannot be both provided",
37 + metrics: []MetricsConfig{
38 + {
39 + Symbol: SymbolConfig{
40 + OID: "1.2",
41 + Name: "abc",
42 + },
43 + Symbols: []SymbolConfig{
44 + {
45 + OID: "1.2",
46 + Name: "abc",
47 + },
48 + },
49 + MetricTags: MetricTagConfigList{
50 + MetricTagConfig{},
51 + },
52 + },
53 + },
54 + expectedErrors: []string{
55 + "table symbol and scalar symbol cannot be both provided",
56 + },
57 + },
58 + {
59 + name: "multiple errors",
60 + metrics: []MetricsConfig{
61 + {},
62 + {
63 + Symbol: SymbolConfig{
64 + OID: "1.2",
65 + Name: "abc",
66 + },
67 + Symbols: []SymbolConfig{
68 + {
69 + OID: "1.2",
70 + Name: "abc",
71 + },
72 + },
73 + MetricTags: MetricTagConfigList{
74 + MetricTagConfig{},
75 + },
76 + },
77 + },
78 + expectedErrors: []string{
79 + "either a table symbol or a scalar symbol must be provided",
80 + "table symbol and scalar symbol cannot be both provided",
81 + },
82 + },
83 + {
84 + name: "missing symbol name",
85 + metrics: []MetricsConfig{
86 + {
87 + Symbol: SymbolConfig{
88 + OID: "1.2.3",
89 + },
90 + },
91 + },
92 + expectedErrors: []string{
93 + "either a table symbol or a scalar symbol must be provided",
94 + },
95 + },
96 + {
97 + name: "table column symbol name missing",
98 + metrics: []MetricsConfig{
99 + {
100 + Symbols: []SymbolConfig{
101 + {
102 + OID: "1.2",
103 + },
104 + {
105 + Name: "abc",
106 + },
107 + },
108 + MetricTags: MetricTagConfigList{
109 + MetricTagConfig{},
110 + },
111 + },
112 + },
113 + expectedErrors: []string{
114 + "symbol name missing: name=`` oid=`1.2`",
115 + "symbol oid or send_as_one missing: name=`abc` oid=``",
116 + },
117 + },
118 + {
119 + name: "table external metric column tag symbol error",
120 + metrics: []MetricsConfig{
121 + {
122 + Symbols: []SymbolConfig{
123 + {
124 + OID: "1.2",
125 + Name: "abc",
126 + },
127 + },
128 + MetricTags: MetricTagConfigList{
129 + MetricTagConfig{
130 + Symbol: SymbolConfigCompat{
131 + OID: "1.2.3",
132 + },
133 + },
134 + MetricTagConfig{
135 + Symbol: SymbolConfigCompat{
136 + Name: "abc",
137 + },
138 + },
139 + },
140 + },
141 + },
142 + expectedErrors: []string{
143 + "symbol name missing: name=`` oid=`1.2.3`",
144 + "symbol oid missing: name=`abc` oid=``",
145 + },
146 + },
147 + {
148 + name: "missing MetricTags",
149 + metrics: []MetricsConfig{
150 + {
151 + Symbols: []SymbolConfig{
152 + {
153 + OID: "1.2",
154 + Name: "abc",
155 + },
156 + },
157 + MetricTags: MetricTagConfigList{},
158 + },
159 + },
160 + expectedErrors: []string{
161 + "column symbols doesn't have a 'metric_tags' section",
162 + },
163 + },
164 + {
165 + name: "table external metric column tag MIB error",
166 + metrics: []MetricsConfig{
167 + {
168 + Symbols: []SymbolConfig{
169 + {
170 + OID: "1.2",
171 + Name: "abc",
172 + },
173 + },
174 + MetricTags: MetricTagConfigList{
175 + MetricTagConfig{
176 + Symbol: SymbolConfigCompat{
177 + OID: "1.2.3",
178 + },
179 + },
180 + MetricTagConfig{
181 + Symbol: SymbolConfigCompat{
182 + Name: "abc",
183 + },
184 + },
185 + },
186 + },
187 + },
188 + expectedErrors: []string{
189 + "symbol name missing: name=`` oid=`1.2.3`",
190 + "symbol oid missing: name=`abc` oid=``",
191 + },
192 + },
193 + {
194 + name: "missing match tags",
195 + metrics: []MetricsConfig{
196 + {
197 + Symbols: []SymbolConfig{
198 + {
199 + OID: "1.2",
200 + Name: "abc",
201 + },
202 + },
203 + MetricTags: MetricTagConfigList{
204 + MetricTagConfig{
205 + Symbol: SymbolConfigCompat{
206 + OID: "1.2.3",
207 + Name: "abc",
208 + },
209 + Match: "([a-z])",
210 + },
211 + },
212 + },
213 + },
214 + expectedErrors: []string{
215 + "`tags` mapping must be provided if `match` (`([a-z])`) is defined",
216 + },
217 + },
218 + {
219 + name: "match cannot compile regex",
220 + metrics: []MetricsConfig{
221 + {
222 + Symbols: []SymbolConfig{
223 + {
224 + OID: "1.2",
225 + Name: "abc",
226 + },
227 + },
228 + MetricTags: MetricTagConfigList{
229 + MetricTagConfig{
230 + Symbol: SymbolConfigCompat{
231 + OID: "1.2.3",
232 + Name: "abc",
233 + },
234 + Match: "([a-z)",
235 + Tags: map[string]string{
236 + "foo": "bar",
237 + },
238 + },
239 + },
240 + },
241 + },
242 + expectedErrors: []string{
243 + "cannot compile `match` (`([a-z)`)",
244 + },
245 + },
246 + {
247 + name: "match cannot compile regex",
248 + metrics: []MetricsConfig{
249 + {
250 + Symbols: []SymbolConfig{
251 + {
252 + OID: "1.2",
253 + Name: "abc",
254 + },
255 + },
256 + MetricTags: MetricTagConfigList{
257 + MetricTagConfig{
258 + Symbol: SymbolConfigCompat{
259 + OID: "1.2.3",
260 + Name: "abc",
261 + },
262 + Tag: "hello",
263 + IndexTransform: []MetricIndexTransform{
264 + {
265 + Start: 2,
266 + End: 1,
267 + },
268 + },
269 + },
270 + },
271 + },
272 + },
273 + expectedErrors: []string{
274 + "transform rule end should be greater than start. Invalid rule",
275 + },
276 + },
277 + {
278 + name: "compiling extract_value",
279 + metrics: []MetricsConfig{
280 + {
281 + Symbol: SymbolConfig{
282 + OID: "1.2.3",
283 + Name: "myMetric",
284 + ExtractValue: `(\d+)C`,
285 + },
286 + },
287 + {
288 + Symbols: []SymbolConfig{
289 + {
290 + OID: "1.2",
291 + Name: "hey",
292 + ExtractValue: `(\d+)C`,
293 + },
294 + },
295 + MetricTags: MetricTagConfigList{
296 + MetricTagConfig{
297 + Symbol: SymbolConfigCompat{
298 + OID: "1.2.3",
299 + Name: "abc",
300 + ExtractValue: `(\d+)C`,
301 + },
302 + Tag: "hello",
303 + },
304 + },
305 + },
306 + },
307 + expectedMetrics: []MetricsConfig{
308 + {
309 + Symbol: SymbolConfig{
310 + OID: "1.2.3",
311 + Name: "myMetric",
312 + ExtractValue: `(\d+)C`,
313 + ExtractValueCompiled: regexp.MustCompile(`(\d+)C`),
314 + },
315 + },
316 + {
317 + Symbols: []SymbolConfig{
318 + {
319 + OID: "1.2",
320 + Name: "hey",
321 + ExtractValue: `(\d+)C`,
322 + ExtractValueCompiled: regexp.MustCompile(`(\d+)C`),
323 + },
324 + },
325 + MetricTags: MetricTagConfigList{
326 + MetricTagConfig{
327 + Symbol: SymbolConfigCompat{
328 + OID: "1.2.3",
329 + Name: "abc",
330 + ExtractValue: `(\d+)C`,
331 + ExtractValueCompiled: regexp.MustCompile(`(\d+)C`),
332 + },
333 + Tag: "hello",
334 + },
335 + },
336 + },
337 + },
338 + expectedErrors: []string{},
339 + },
340 + {
341 + name: "error compiling extract_value",
342 + metrics: []MetricsConfig{
343 + {
344 + Symbol: SymbolConfig{
345 + OID: "1.2.3",
346 + Name: "myMetric",
347 + ExtractValue: "[{",
348 + },
349 + },
350 + },
351 + expectedErrors: []string{
352 + "cannot compile `extract_value`",
353 + },
354 + },
355 + {
356 + name: "constant_value_one usage in column symbol",
357 + metrics: []MetricsConfig{
358 + {
359 + Symbols: []SymbolConfig{
360 + {
361 + Name: "abc",
362 + ConstantValueOne: true,
363 + },
364 + },
365 + MetricTags: MetricTagConfigList{
366 + MetricTagConfig{
367 + Symbol: SymbolConfigCompat{
368 + Name: "abc",
369 + OID: "1.2.3",
370 + },
371 + Tag: "hello",
372 + },
373 + },
374 + },
375 + },
376 + expectedErrors: []string{},
377 + },
378 + {
379 + name: "constant_value_one usage in scalar symbol",
380 + metrics: []MetricsConfig{
381 + {
382 + Symbol: SymbolConfig{
383 + Name: "myMetric",
384 + ConstantValueOne: true,
385 + },
386 + },
387 + },
388 + expectedErrors: []string{
389 + "either a table symbol or a scalar symbol must be provided",
390 + },
391 + },
392 + {
393 + name: "constant_value_one usage in scalar symbol with OID",
394 + metrics: []MetricsConfig{
395 + {
396 + Symbol: SymbolConfig{
397 + OID: "1.2.3",
398 + Name: "myMetric",
399 + ConstantValueOne: true,
400 + },
401 + },
402 + },
403 + expectedErrors: []string{
404 + "`constant_value_one` cannot be used outside of tables",
405 + },
406 + },
407 + {
408 + name: "constant_value_one usage in metric tags",
409 + metrics: []MetricsConfig{
410 + {
411 + Symbols: []SymbolConfig{
412 + {
413 + OID: "1.2",
414 + Name: "abc",
415 + },
416 + },
417 + MetricTags: MetricTagConfigList{
418 + MetricTagConfig{
419 + Symbol: SymbolConfigCompat{
420 + Name: "abc",
421 + ConstantValueOne: true,
422 + },
423 + Tag: "hello",
424 + },
425 + },
426 + },
427 + },
428 + expectedErrors: []string{
429 + "symbol oid missing",
430 + "`constant_value_one` cannot be used outside of tables",
431 + },
432 + },
433 + {
434 + name: "metric_type usage in column symbol",
435 + metrics: []MetricsConfig{
436 + {
437 + Symbols: []SymbolConfig{
438 + {
439 + Name: "abc",
440 + OID: "1.2.3",
441 + MetricType: ProfileMetricTypeCounter,
442 + },
443 + },
444 + MetricTags: MetricTagConfigList{
445 + MetricTagConfig{
446 + Symbol: SymbolConfigCompat{
447 + Name: "abc",
448 + OID: "1.2.3",
449 + },
450 + Tag: "hello",
451 + },
452 + },
453 + },
454 + },
455 + expectedErrors: []string{},
456 + },
457 + {
458 + name: "metric_type usage in scalar symbol",
459 + metrics: []MetricsConfig{
460 + {
461 + Symbol: SymbolConfig{
462 + Name: "abc",
463 + OID: "1.2.3",
464 + MetricType: ProfileMetricTypeCounter,
465 + },
466 + },
467 + },
468 + expectedErrors: []string{},
469 + },
470 + {
471 + name: "ERROR metric_type usage in metric_tags",
472 + metrics: []MetricsConfig{
473 + {
474 + Symbols: []SymbolConfig{
475 + {
476 + Name: "abc",
477 + OID: "1.2.3",
478 + },
479 + },
480 + MetricTags: MetricTagConfigList{
481 + MetricTagConfig{
482 + Symbol: SymbolConfigCompat{
483 + Name: "abc",
484 + OID: "1.2.3",
485 + MetricType: ProfileMetricTypeCounter,
486 + },
487 + Tag: "hello",
488 + },
489 + },
490 + },
491 + },
492 + expectedErrors: []string{
493 + "`metric_type` cannot be used outside scalar/table metric symbols and metrics root",
494 + },
495 + },
496 + {
497 + name: "metric root forced_type converted to metric_type",
498 + metrics: []MetricsConfig{
499 + {
500 + ForcedType: ProfileMetricTypeCounter,
501 + Symbols: []SymbolConfig{
502 + {
503 + Name: "abc",
504 + OID: "1.2.3",
505 + },
506 + },
507 + MetricTags: MetricTagConfigList{
508 + MetricTagConfig{
509 + Symbol: SymbolConfigCompat{
510 + Name: "abc",
511 + OID: "1.2.3",
512 + },
513 + Tag: "hello",
514 + },
515 + },
516 + },
517 + },
518 + expectedMetrics: []MetricsConfig{
519 + {
520 + MetricType: ProfileMetricTypeCounter,
521 + Symbols: []SymbolConfig{
522 + {
523 + Name: "abc",
524 + OID: "1.2.3",
525 + },
526 + },
527 + MetricTags: MetricTagConfigList{
528 + MetricTagConfig{
529 + Symbol: SymbolConfigCompat{
530 + Name: "abc",
531 + OID: "1.2.3",
532 + },
533 + Tag: "hello",
534 + },
535 + },
536 + },
537 + },
538 + },
539 + {
540 + name: "mapping used without tag",
541 + metrics: []MetricsConfig{
542 + {
543 + Symbols: []SymbolConfig{
544 + {
545 + OID: "1.2",
546 + Name: "abc",
547 + },
548 + },
549 + MetricTags: MetricTagConfigList{
550 + MetricTagConfig{
551 + Symbol: SymbolConfigCompat{
552 + OID: "1.2",
553 + Name: "abc",
554 + },
555 + Mapping: map[string]string{
556 + "1": "abc",
557 + "2": "def",
558 + },
559 + },
560 + },
561 + },
562 + },
563 + expectedErrors: []string{"`tag` must be provided if `mapping` (`map[1:abc 2:def]`) is defined"},
564 + },
565 + }
566 + for _, tt := range tests {
567 + t.Run(tt.name, func(t *testing.T) {
568 + errors := ValidateEnrichMetrics(tt.metrics)
569 + assert.Equal(t, len(tt.expectedErrors), len(errors), fmt.Sprintf("ERRORS: %v", errors))
570 + for i := range errors {
571 + assert.Contains(t, errors[i], tt.expectedErrors[i])
572 + }
573 + if tt.expectedMetrics != nil {
574 + assert.Equal(t, tt.expectedMetrics, tt.metrics)
575 + }
576 + })
577 + }
578 +}
579 +
580 +func Test_ValidateEnrichMetricTags(t *testing.T) {
581 + tests := []struct {
582 + name string
583 + metrics []MetricTagConfig
584 + expectedErrors []string
585 + expectedMetrics []MetricTagConfig
586 + }{
587 + {
588 + name: "Move OID to Symbol",
589 + metrics: []MetricTagConfig{
590 + {
591 + OID: "1.2.3.4",
592 + Symbol: SymbolConfigCompat{
593 + Name: "mySymbol",
594 + },
595 + },
596 + },
597 + expectedMetrics: []MetricTagConfig{
598 + {
599 + Symbol: SymbolConfigCompat{
600 + OID: "1.2.3.4",
601 + Name: "mySymbol",
602 + },
603 + },
604 + },
605 + },
606 + {
607 + name: "Metric tag OID and symbol.OID cannot be both declared",
608 + metrics: []MetricTagConfig{
609 + {
610 + OID: "1.2.3.4",
611 + Symbol: SymbolConfigCompat{
612 + OID: "1.2.3.5",
613 + Name: "mySymbol",
614 + },
615 + },
616 + },
617 + expectedErrors: []string{
618 + "metric tag OID and symbol.OID cannot be both declared",
619 + },
620 + },
621 + {
622 + name: "metric tag symbol and column cannot be both declared",
623 + metrics: []MetricTagConfig{
624 + {
625 + Symbol: SymbolConfigCompat{
626 + OID: "1.2.3.5",
627 + Name: "mySymbol",
628 + },
629 + Column: SymbolConfig{
630 + OID: "1.2.3.5",
631 + Name: "mySymbol",
632 + },
633 + },
634 + },
635 + expectedErrors: []string{
636 + "metric tag symbol and column cannot be both declared",
637 + },
638 + },
639 + {
640 + name: "Missing OID",
641 + metrics: []MetricTagConfig{
642 + {
643 + Symbol: SymbolConfigCompat{
644 + Name: "mySymbol",
645 + },
646 + },
647 + },
648 + expectedErrors: []string{
649 + "symbol oid missing",
650 + },
651 + },
652 + }
653 + for _, tt := range tests {
654 + t.Run(tt.name, func(t *testing.T) {
655 + errors := ValidateEnrichMetricTags(tt.metrics)
656 + assert.Equal(t, len(tt.expectedErrors), len(errors), fmt.Sprintf("ERRORS: %v", errors))
657 + for i := range errors {
658 + assert.Contains(t, errors[i], tt.expectedErrors[i])
659 + }
660 + if tt.expectedMetrics != nil {
661 + assert.Equal(t, tt.expectedMetrics, tt.metrics)
662 + }
663 + })
664 + }
665 +}
666 +
667 +func Test_validateEnrichMetadata(t *testing.T) {
668 + tests := []struct {
669 + name string
670 + metadata MetadataConfig
671 + expectedErrors []string
672 + expectedMetadata MetadataConfig
673 + }{
674 + {
675 + name: "both field symbol and value can be provided",
676 + metadata: MetadataConfig{
677 + "device": MetadataResourceConfig{
678 + Fields: map[string]MetadataField{
679 + "name": {
680 + Value: "hey",
681 + Symbol: SymbolConfig{
682 + OID: "1.2.3",
683 + Name: "someSymbol",
684 + },
685 + },
686 + },
687 + },
688 + },
689 + expectedMetadata: MetadataConfig{
690 + "device": MetadataResourceConfig{
691 + Fields: map[string]MetadataField{
692 + "name": {
693 + Value: "hey",
694 + Symbol: SymbolConfig{
695 + OID: "1.2.3",
696 + Name: "someSymbol",
697 + },
698 + },
699 + },
700 + },
701 + },
702 + },
703 + {
704 + name: "invalid regex pattern for symbol",
705 + metadata: MetadataConfig{
706 + "device": MetadataResourceConfig{
707 + Fields: map[string]MetadataField{
708 + "name": {
709 + Symbol: SymbolConfig{
710 + OID: "1.2.3",
711 + Name: "someSymbol",
712 + ExtractValue: "(\\w[)",
713 + },
714 + },
715 + },
716 + },
717 + },
718 + expectedErrors: []string{
719 + "cannot compile `extract_value`",
720 + },
721 + },
722 + {
723 + name: "invalid regex pattern for multiple symbols",
724 + metadata: MetadataConfig{
725 + "device": MetadataResourceConfig{
726 + Fields: map[string]MetadataField{
727 + "name": {
728 + Symbols: []SymbolConfig{
729 + {
730 + OID: "1.2.3",
731 + Name: "someSymbol",
732 + ExtractValue: "(\\w[)",
733 + },
734 + },
735 + },
736 + },
737 + },
738 + },
739 + expectedErrors: []string{
740 + "cannot compile `extract_value`",
741 + },
742 + },
743 + {
744 + name: "field regex pattern is compiled",
745 + metadata: MetadataConfig{
746 + "device": MetadataResourceConfig{
747 + Fields: map[string]MetadataField{
748 + "name": {
749 + Symbol: SymbolConfig{
750 + OID: "1.2.3",
751 + Name: "someSymbol",
752 + ExtractValue: "(\\w)",
753 + },
754 + },
755 + },
756 + },
757 + },
758 + expectedErrors: []string{},
759 + expectedMetadata: MetadataConfig{
760 + "device": MetadataResourceConfig{
761 + Fields: map[string]MetadataField{
762 + "name": {
763 + Symbol: SymbolConfig{
764 + OID: "1.2.3",
765 + Name: "someSymbol",
766 + ExtractValue: "(\\w)",
767 + ExtractValueCompiled: regexp.MustCompile(`(\w)`),
768 + },
769 + },
770 + },
771 + },
772 + },
773 + },
774 + {
775 + name: "invalid resource",
776 + metadata: MetadataConfig{
777 + "invalid-res": MetadataResourceConfig{
778 + Fields: map[string]MetadataField{
779 + "name": {
780 + Value: "hey",
781 + },
782 + },
783 + },
784 + },
785 + expectedErrors: []string{
786 + "invalid resource: invalid-res",
787 + },
788 + },
789 + {
790 + name: "invalid field",
791 + metadata: MetadataConfig{
792 + "device": MetadataResourceConfig{
793 + Fields: map[string]MetadataField{
794 + "invalid-field": {
795 + Value: "hey",
796 + },
797 + },
798 + },
799 + },
800 + expectedErrors: []string{
801 + "invalid resource (device) field: invalid-field",
802 + },
803 + },
804 + {
805 + name: "invalid idtags",
806 + metadata: MetadataConfig{
807 + "interface": MetadataResourceConfig{
808 + Fields: map[string]MetadataField{
809 + "invalid-field": {
810 + Value: "hey",
811 + },
812 + },
813 + IDTags: MetricTagConfigList{
814 + MetricTagConfig{
815 + Symbol: SymbolConfigCompat{
816 + OID: "1.2.3",
817 + Name: "abc",
818 + },
819 + Match: "([a-z)",
820 + Tags: map[string]string{
821 + "foo": "bar",
822 + },
823 + },
824 + },
825 + },
826 + },
827 + expectedErrors: []string{
828 + "invalid resource (interface) field: invalid-field",
829 + "cannot compile `match` (`([a-z)`)",
830 + },
831 + },
832 + {
833 + name: "device resource does not support id_tags",
834 + metadata: MetadataConfig{
835 + "device": MetadataResourceConfig{
836 + Fields: map[string]MetadataField{
837 + "name": {
838 + Value: "hey",
839 + },
840 + },
841 + IDTags: MetricTagConfigList{
842 + MetricTagConfig{
843 + Symbol: SymbolConfigCompat{
844 + OID: "1.2.3",
845 + Name: "abc",
846 + },
847 + Tag: "abc",
848 + },
849 + },
850 + },
851 + },
852 + expectedErrors: []string{
853 + "device resource does not support custom id_tags",
854 + },
855 + },
856 + }
857 + for _, tt := range tests {
858 + t.Run(tt.name, func(t *testing.T) {
859 + errors := ValidateEnrichMetadata(tt.metadata)
860 + assert.Equal(t, len(tt.expectedErrors), len(errors), fmt.Sprintf("ERRORS: %v", errors))
861 + for i := range errors {
862 + assert.Contains(t, errors[i], tt.expectedErrors[i])
863 + }
864 + if tt.expectedMetadata != nil {
865 + assert.Equal(t, tt.expectedMetadata, tt.metadata)
866 + }
867 + })
868 + }
869 +}
870 +
871 +// TODO: Add test for ValidateEnrichMetricTags
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/yaml_utils.go new
+64
@@ -0,0 +1,64 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2016-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +// StringArray is list of string with a yaml un-marshaller that support both array and string.
9 +// See test file for example usage.
10 +// Credit: https://github.com/go-yaml/yaml/issues/100#issuecomment-324964723
11 +type StringArray []string
12 +
13 +// UnmarshalYAML unmarshalls StringArray
14 +func (a *StringArray) UnmarshalYAML(unmarshal func(interface{}) error) error {
15 + var multi []string
16 + err := unmarshal(&multi)
17 + if err != nil {
18 + var single string
19 + err := unmarshal(&single)
20 + if err != nil {
21 + return err
22 + }
23 + *a = []string{single}
24 + } else {
25 + *a = multi
26 + }
27 + return nil
28 +}
29 +
30 +// UnmarshalYAML unmarshalls SymbolConfig
31 +func (a *SymbolConfigCompat) UnmarshalYAML(unmarshal func(interface{}) error) error {
32 + var symbol SymbolConfig
33 + err := unmarshal(&symbol)
34 + if err != nil {
35 + var str string
36 + err := unmarshal(&str)
37 + if err != nil {
38 + return err
39 + }
40 + *a = SymbolConfigCompat(SymbolConfig{Name: str})
41 + } else {
42 + *a = SymbolConfigCompat(symbol)
43 + }
44 + return nil
45 +}
46 +
47 +// UnmarshalYAML unmarshalls MetricTagConfigList
48 +func (mtcl *MetricTagConfigList) UnmarshalYAML(unmarshal func(interface{}) error) error {
49 + var multi []MetricTagConfig
50 + err := unmarshal(&multi)
51 + if err != nil {
52 + var tags []string
53 + err := unmarshal(&tags)
54 + if err != nil {
55 + return err
56 + }
57 + multi = []MetricTagConfig{}
58 + for _, tag := range tags {
59 + multi = append(multi, MetricTagConfig{SymbolTag: tag})
60 + }
61 + }
62 + *mtcl = multi
63 + return nil
64 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/yaml_utils_test.go new
+94
@@ -0,0 +1,94 @@
1 +// Unless explicitly stated otherwise all files in this repository are licensed
2 +// under the Apache License Version 2.0.
3 +// This product includes software developed at Datadog (https://www.datadoghq.com/).
4 +// Copyright 2016-present Datadog, Inc.
5 +
6 +package ddprofiledefinition
7 +
8 +import (
9 + "testing"
10 +
11 + "gopkg.in/yaml.v2"
12 +
13 + "github.com/stretchr/testify/assert"
14 +)
15 +
16 +type MyStringArray struct {
17 + SomeIDs StringArray `yaml:"my_field"`
18 +}
19 +
20 +type MySymbolStruct struct {
21 + SymbolField SymbolConfigCompat `yaml:"my_symbol_field"`
22 +}
23 +
24 +func Test_metricTagConfig_UnmarshalYAML(t *testing.T) {
25 + myStruct := MetricsConfig{}
26 + expected := MetricsConfig{MetricTags: []MetricTagConfig{{Index: 3}}}
27 +
28 + yaml.Unmarshal([]byte(`
29 +metric_tags:
30 +- index: 3
31 +`), &myStruct)
32 +
33 + assert.Equal(t, expected, myStruct)
34 +}
35 +
36 +func Test_metricTagConfig_onlyTags(t *testing.T) {
37 + myStruct := MetricsConfig{}
38 + expected := MetricsConfig{MetricTags: []MetricTagConfig{{SymbolTag: "aaa"}}}
39 +
40 + yaml.Unmarshal([]byte(`
41 +metric_tags:
42 +- aaa
43 +`), &myStruct)
44 +
45 + assert.Equal(t, expected, myStruct)
46 +}
47 +
48 +func TestStringArray_UnmarshalYAML_array(t *testing.T) {
49 + myStruct := MyStringArray{}
50 + expected := MyStringArray{SomeIDs: StringArray{"aaa", "bbb"}}
51 +
52 + yaml.Unmarshal([]byte(`
53 +my_field:
54 + - aaa
55 + - bbb
56 +`), &myStruct)
57 +
58 + assert.Equal(t, expected, myStruct)
59 +}
60 +
61 +func TestStringArray_UnmarshalYAML_string(t *testing.T) {
62 + myStruct := MyStringArray{}
63 + expected := MyStringArray{SomeIDs: StringArray{"aaa"}}
64 +
65 + yaml.Unmarshal([]byte(`
66 +my_field: aaa
67 +`), &myStruct)
68 +
69 + assert.Equal(t, expected, myStruct)
70 +}
71 +
72 +func TestSymbolConfig_UnmarshalYAML_symbolObject(t *testing.T) {
73 + myStruct := MySymbolStruct{}
74 + expected := MySymbolStruct{SymbolField: SymbolConfigCompat{OID: "1.2.3", Name: "aSymbol"}}
75 +
76 + yaml.Unmarshal([]byte(`
77 +my_symbol_field:
78 + name: aSymbol
79 + OID: 1.2.3
80 +`), &myStruct)
81 +
82 + assert.Equal(t, expected, myStruct)
83 +}
84 +
85 +func TestSymbolConfig_UnmarshalYAML_symbolString(t *testing.T) {
86 + myStruct := MySymbolStruct{}
87 + expected := MySymbolStruct{SymbolField: SymbolConfigCompat{Name: "aSymbol"}}
88 +
89 + yaml.Unmarshal([]byte(`
90 +my_symbol_field: aSymbol
91 +`), &myStruct)
92 +
93 + assert.Equal(t, expected, myStruct)
94 +}
src/go/plugin/go.d/collector/snmp/ddsnmp/profile.go
+10 -11
@@ -5,9 +5,8 @@ package ddsnmp
5 import (
6 "errors"
7
8 - "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
9 -
8 "github.com/netdata/netdata/go/plugins/pkg/matcher"
9 + "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
10 )
11
12 func Find(sysObjId string) []*Profile {
@@ -30,8 +29,8 @@ func Find(sysObjId string) []*Profile {
29 }
30
31 type Profile struct {
33 - SourceFile string `yaml:"-"`
34 - Definition *profiledefinition.ProfileDefinition `yaml:",inline"`
32 + SourceFile string `yaml:"-"`
33 + Definition *ddprofiledefinition.ProfileDefinition `yaml:",inline"`
34 }
35
36 func (p *Profile) clone() *Profile {
@@ -47,19 +46,19 @@ func (p *Profile) merge(base *Profile) {
46 p.Definition.StaticTags = append(p.Definition.StaticTags, base.Definition.StaticTags...)
47
48 if p.Definition.Metadata == nil {
50 - p.Definition.Metadata = make(profiledefinition.MetadataConfig)
49 + p.Definition.Metadata = make(ddprofiledefinition.MetadataConfig)
50 }
51
52 for resName, baseRes := range base.Definition.Metadata {
53 targetRes, exists := p.Definition.Metadata[resName]
54 if !exists {
56 - targetRes = profiledefinition.NewMetadataResourceConfig()
55 + targetRes = ddprofiledefinition.NewMetadataResourceConfig()
56 }
57
58 targetRes.IDTags = append(targetRes.IDTags, baseRes.IDTags...)
59
60 if targetRes.Fields == nil && len(baseRes.Fields) > 0 {
62 - targetRes.Fields = make(map[string]profiledefinition.MetadataField, len(baseRes.Fields))
61 + targetRes.Fields = make(map[string]ddprofiledefinition.MetadataField, len(baseRes.Fields))
62 }
63
64 for field, symbol := range baseRes.Fields {
@@ -73,11 +72,11 @@ func (p *Profile) merge(base *Profile) {
72 }
73
74 func (p *Profile) validate() error {
76 - profiledefinition.NormalizeMetrics(p.Definition.Metrics)
75 + ddprofiledefinition.NormalizeMetrics(p.Definition.Metrics)
76
78 - errs := profiledefinition.ValidateEnrichMetadata(p.Definition.Metadata)
79 - errs = append(errs, profiledefinition.ValidateEnrichMetrics(p.Definition.Metrics)...)
80 - errs = append(errs, profiledefinition.ValidateEnrichMetricTags(p.Definition.MetricTags)...)
77 + errs := ddprofiledefinition.ValidateEnrichMetadata(p.Definition.Metadata)
78 + errs = append(errs, ddprofiledefinition.ValidateEnrichMetrics(p.Definition.Metrics)...)
79 + errs = append(errs, ddprofiledefinition.ValidateEnrichMetricTags(p.Definition.MetricTags)...)
80 if len(errs) > 0 {
81 errList := make([]error, 0, len(errs))
82 for _, s := range errs {
src/go/plugin/go.d/collector/snmp/parsing.go
+37 -31
@@ -5,17 +5,18 @@ import (
5 "fmt"
6 "regexp"
7
8 - "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
8 + "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
9 )
10
11 -func parseMetrics(metrics []profiledefinition.MetricsConfig) (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
11 +func parseMetrics(metrics []ddprofiledefinition.MetricsConfig) (parsedResult, error) {
12 + var (
13 + OIDs, nextOIDs, bulkOIDs []string
14 + OIDsToResolve []map[string]string
15 + parsedMetrics []parsedMetric
16 + indexesToResolve []indexMapping
17 + )
18 +
19 + bulkThreshold := 0
20 for _, metric := range metrics {
21 result, err := parseMetric(metric)
22
@@ -23,33 +24,36 @@ func parseMetrics(metrics []profiledefinition.MetricsConfig) (parsedResult, erro
24 return parsedResult{}, err
25 }
26
26 - oids = append(oids, result.oidsToFetch...)
27 + OIDs = append(OIDs, result.oidsToFetch...)
28
29 for name, oid := range result.oidsToResolve {
30 // 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 + OIDsToResolve = append(OIDsToResolve, map[string]string{name: oid})
32 }
33
34 // 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 + indexesToResolve = append(indexesToResolve, result.indexMappings...)
36
37 for _, batch := range result.tableBatches {
37 - should_query_in_bulk := bulk_threshold > 0 && len(batch.oids) > bulk_threshold
38 + should_query_in_bulk := bulkThreshold > 0 && len(batch.oids) > bulkThreshold
39 if should_query_in_bulk {
39 - bulk_oids = append(bulk_oids, batch.tableOID)
40 + bulkOIDs = append(bulkOIDs, batch.tableOID)
41 } else {
41 - next_oids = append(next_oids, batch.oids...)
42 + nextOIDs = append(nextOIDs, batch.oids...)
43 }
44 }
45
45 - parsed_metrics = append(parsed_metrics, result.parsedMetrics...)
46 + parsedMetrics = append(parsedMetrics, result.parsedMetrics...)
47
48 }
48 - return parsedResult{oids: oids,
49 - next_oids: next_oids, bulk_oids: bulk_oids, parsed_metrics: parsed_metrics}, nil
49 + return parsedResult{
50 + OIDs: OIDs,
51 + nextOIDs: nextOIDs,
52 + bulkOIDs: bulkOIDs,
53 + parsedMetrics: parsedMetrics}, nil
54 }
55
52 -func parseMetric(metric profiledefinition.MetricsConfig) (metricParseResult, error) {
56 +func parseMetric(metric ddprofiledefinition.MetricsConfig) (metricParseResult, error) {
57 /*Can either be:
58
59 * An OID metric:
@@ -92,7 +96,7 @@ func parseMetric(metric profiledefinition.MetricsConfig) (metricParseResult, err
96
97 } else if len(metric.MIB) == 0 {
98 return metricParseResult{}, fmt.Errorf("unsupported metric {%v}", metric)
95 - } else if metric.Symbol != (profiledefinition.SymbolConfig{}) {
99 + } else if metric.Symbol != (ddprofiledefinition.SymbolConfig{}) {
100 // Single Metric
101 return (parseSymbolMetric(metric.Symbol, metric.MIB)) // TODO metric tags might be needed here.
102 //Can't support tables at the moment
@@ -102,7 +106,7 @@ func parseMetric(metric profiledefinition.MetricsConfig) (metricParseResult, err
106 }
107
108 // TODO error outs on functions
105 -func parseSymbolMetric(symbol profiledefinition.SymbolConfig, mib string) (metricParseResult, error) {
109 +func parseSymbolMetric(symbol ddprofiledefinition.SymbolConfig, mib string) (metricParseResult, error) {
110 /* Parse a symbol metric (= an OID in a MIB).
111 Example:
112
@@ -118,31 +122,33 @@ func parseSymbolMetric(symbol profiledefinition.SymbolConfig, mib string) (metri
122 symbol: tcpActiveOpens # require MIB syntax
123 ```*/
124
121 - parsed_symbol, err := parseSymbol(mib, symbol)
125 + parsedSymbol, err := parseSymbol(symbol)
126 if err != nil {
127 return metricParseResult{}, err
128 }
129
126 - parsed_symbol_metric := parsedSymbolMetric{
127 - name: parsed_symbol.name,
130 + parsedSymbolMetric := parsedSymbolMetric{
131 + name: parsedSymbol.name,
132 tags: nil,
133 forcedType: string(symbol.MetricType),
134 enforceScalar: false,
135 options: nil,
132 - extractValuePattern: parsed_symbol.extractValuePattern,
133 - baseoid: parsed_symbol.oid,
136 + extractValuePattern: parsedSymbol.extractValuePattern,
137 + baseoid: parsedSymbol.oid,
138 + unit: symbol.Unit,
139 + description: symbol.Description,
140 }
141
142 return metricParseResult{
137 - oidsToFetch: []string{parsed_symbol.oid},
138 - oidsToResolve: parsed_symbol.oidsToResolve,
139 - parsedMetrics: []parsedMetric{parsed_symbol_metric},
143 + oidsToFetch: []string{parsedSymbol.oid},
144 + oidsToResolve: parsedSymbol.oidsToResolve,
145 + parsedMetrics: []parsedMetric{parsedSymbolMetric},
146 tableBatches: nil,
147 indexMappings: nil,
148 }, nil
149 }
150
145 -func parseSymbol(mib string, symbol interface{}) (parsedSymbol, error) {
151 +func parseSymbol(symbol interface{}) (parsedSymbol, error) {
152 /*
153 Parse an OID symbol.
154
@@ -162,7 +168,7 @@ func parseSymbol(mib string, symbol interface{}) (parsedSymbol, error) {
168 */
169
170 switch s := symbol.(type) {
165 - case profiledefinition.SymbolConfig:
171 + case ddprofiledefinition.SymbolConfig:
172 oid := s.OID
173 name := s.Name
174 if s.ExtractValue != "" {
src/go/plugin/go.d/collector/snmp/profile.go
+8 -7
@@ -2,7 +2,6 @@ package snmp
2
3 import (
4 "fmt"
5 - "log"
5
6 "github.com/gosnmp/gosnmp"
7
@@ -34,13 +33,13 @@ func (c *Collector) parseMetricsFromProfiles(matchingProfiles []*ddsnmp.Profile)
33 return nil, err
34 }
35
37 - for _, oid := range results.oids {
36 + for _, oid := range results.OIDs {
37 response, err := c.snmpClient.Get([]string{oid})
38 if err != nil {
39 return nil, err
40 }
41 if (response != &gosnmp.SnmpPacket{}) {
43 - for _, metric := range results.parsed_metrics {
42 + for _, metric := range results.parsedMetrics {
43 switch s := metric.(type) {
44 case parsedSymbolMetric:
45 // find a matching metric
@@ -48,8 +47,10 @@ func (c *Collector) parseMetricsFromProfiles(matchingProfiles []*ddsnmp.Profile)
47 metricName := s.name
48 metricType := response.Variables[0].Type
49 metricValue := response.Variables[0].Value
50 + metricUnit := s.unit
51 + metricDescription := s.description
52
52 - metricMap[oid] = processedMetric{oid: oid, name: metricName, value: metricValue, metric_type: metricType}
53 + metricMap[oid] = processedMetric{oid: oid, name: metricName, value: metricValue, metricType: metricType, unit: metricUnit, description: metricDescription}
54 }
55 }
56 }
@@ -57,14 +58,14 @@ func (c *Collector) parseMetricsFromProfiles(matchingProfiles []*ddsnmp.Profile)
58 }
59 }
60
60 - for _, oid := range results.next_oids {
61 + for _, oid := range results.nextOIDs {
62 if len(oid) < 1 {
63 continue
64 }
65 if tableRows, err := c.walkOIDTree(oid); err != nil {
65 - log.Fatalf("Error walking OID tree: %v, oid %s", err, oid)
66 + return nil, fmt.Errorf("error walking OID tree: %v, oid %s", err, oid)
67 } else {
67 - for _, metric := range results.parsed_metrics {
68 + for _, metric := range results.parsedMetrics {
69 switch s := metric.(type) {
70 case parsedTableMetric:
71 // find a matching metric
src/go/plugin/go.d/collector/snmp/types.go
+22 -18
@@ -7,18 +7,18 @@ import (
7 )
8
9 type snmpPDU struct {
10 - value interface{}
11 - oid string
12 - metric_type gosnmp.Asn1BER
10 + value interface{}
11 + oid string
12 + metricType gosnmp.Asn1BER
13 }
14
15 type SysObjectIDs []string
16
17 type parsedResult struct {
18 - oids []string
19 - next_oids []string
20 - bulk_oids []string
21 - parsed_metrics []parsedMetric
18 + OIDs []string
19 + nextOIDs []string
20 + bulkOIDs []string
21 + parsedMetrics []parsedMetric
22 }
23
24 type tableBatchKey struct {
@@ -83,6 +83,8 @@ type parsedSymbolMetric struct {
83 options map[string]string
84 extractValuePattern *regexp.Regexp
85 baseoid string //TODO consider changing this to OID, it will not have nested OIDs as it is a symbol
86 + unit string
87 + description string
88 }
89
90 type parsedTableMetric struct {
@@ -102,8 +104,8 @@ type parsedMetric any
104
105 // Not supported yet
106 /*type parsedSimpleMetricTag struct {
105 - name string
106 -}
107 + name string
108 + }
109
110 type parsedMatchMetricTag struct {
111 tags []string
@@ -111,15 +113,15 @@ symbol Symbol
113 pattern *regexp.Regexp
114 }
115
114 -type symbolTag struct {
115 - parsedMetricTag parsedMetricTag
116 - symbol string
117 -}
116 + type symbolTag struct {
117 + parsedMetricTag parsedMetricTag
118 + symbol string
119 + }
120
119 -type parsedSymbolTagsResult struct {
120 - oids []string
121 - parsedSymbolTags []symbolTag
122 -}
121 + type parsedSymbolTagsResult struct {
122 + oids []string
123 + parsedSymbolTags []symbolTag
124 + }
125 */
126 type parsedMetricTag struct {
127 name string
@@ -146,6 +148,8 @@ type processedMetric struct {
148 oid string
149 name string
150 value interface{}
149 - metric_type gosnmp.Asn1BER
151 + metricType gosnmp.Asn1BER
152 tableName string
153 + unit string
154 + description string
155 }