@cryptotaxi247 / netdata-1 / commits / d1c4af60e

improve(go.d/snmp): add `manual_profiles` option (#21023)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Ilya Mashchenko committed Sep 21, 2025 at 22:16 UTC d1c4af60ec9bf3c076e8367b751cafe744b30ece
7 files changed +81 -25
src/go/plugin/go.d/collector/snmp/collect.go
+2 -2
@@ -168,7 +168,7 @@ func (c *Collector) setupVnode(si *snmputils.SysInfo, deviceMeta map[string]ddsn
168 }
169
170 func (c *Collector) setupProfiles(sysObjectID string) []*ddsnmp.Profile {
171 - snmpProfiles := ddsnmp.FindProfiles(sysObjectID)
171 + snmpProfiles := ddsnmp.FindProfiles(sysObjectID, c.ManualProfiles)
172 var profInfo []string
173
174 for _, prof := range snmpProfiles {
@@ -180,7 +180,7 @@ func (c *Collector) setupProfiles(sysObjectID string) []*ddsnmp.Profile {
180 }
181 }
182
183 - c.Infof("device matched %d profile(s): %s (sysObjectID: %s)", len(snmpProfiles), strings.Join(profInfo, ", "), sysObjectID)
183 + c.Infof("device matched %d profile(s): %s (sysObjectID: '%s')", len(snmpProfiles), strings.Join(profInfo, ", "), sysObjectID)
184
185 return snmpProfiles
186 }
src/go/plugin/go.d/collector/snmp/config.go
+10 -8
@@ -6,19 +6,21 @@ import "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/vnodes"
6
7 type (
8 Config struct {
9 - UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
10 - Hostname string `yaml:"hostname" json:"hostname"`
11 - CreateVnode bool `yaml:"create_vnode,omitempty" json:"create_vnode"`
12 - VnodeDeviceDownThreshold int `yaml:"vnode_device_down_threshold,omitempty" json:"vnode_device_down_threshold"`
13 - Vnode vnodes.VirtualNode `yaml:"vnode,omitempty" json:"vnode"`
14 - Community string `yaml:"community,omitempty" json:"community"`
15 - User User `yaml:"user,omitempty" json:"user"`
16 - Options Options `yaml:"options,omitempty" json:"options"`
9 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
10 + Hostname string `yaml:"hostname" json:"hostname"`
11 + CreateVnode bool `yaml:"create_vnode,omitempty" json:"create_vnode"`
12 + VnodeDeviceDownThreshold int `yaml:"vnode_device_down_threshold,omitempty" json:"vnode_device_down_threshold"`
13 + Vnode vnodes.VirtualNode `yaml:"vnode,omitempty" json:"vnode"`
14 + Community string `yaml:"community,omitempty" json:"community"`
15 + User User `yaml:"user,omitempty" json:"user"`
16 + Options Options `yaml:"options,omitempty" json:"options"`
17 +
18 ChartsInput []ChartConfig `yaml:"charts,omitempty" json:"charts"`
19 NetworkInterfaceFilter NetworkInterfaceFilter `yaml:"network_interface_filter,omitempty" json:"network_interface_filter"`
20 EnableProfiles bool `yaml:"enable_profiles,omitempty" json:"enable_profiles"`
21 EnableProfilesTableMetrics bool `yaml:"enable_profiles_table_metrics,omitempty" json:"enable_profiles_table_metrics"`
22 DisableLegacyCollection bool `yaml:"disable_legacy_collection,omitempty" json:"disable_legacy_collection"`
23 + ManualProfiles []string `yaml:"manual_profiles,omitempty" json:"manual_profiles"`
24 }
25 NetworkInterfaceFilter struct {
26 ByName string `yaml:"by_name,omitempty" json:"by_name"`
src/go/plugin/go.d/collector/snmp/config_schema.json
+19 -1
@@ -367,6 +367,19 @@
367 "description": "Disable the legacy SNMP collection method, forcing the collector to use only SNMP profiles (YAML-based configuration). When enabled, the collector will ignore any non-profile based collection logic.",
368 "type": "boolean",
369 "default": false
370 + },
371 + "manual_profiles": {
372 + "title": "Manual SNMP Profiles",
373 + "description": "Profiles to apply if automatic detection cannot be used.",
374 + "type": [
375 + "array",
376 + "null"
377 + ],
378 + "items": {
379 + "title": "Profile",
380 + "type": "string"
381 + },
382 + "uniqueItems": true
383 }
384 },
385 "required": [
@@ -385,6 +398,10 @@
398 "enable_profiles_table_metrics": {
399 "ui:help": "Table metrics include interface statistics, routing tables, and other tabular data."
400 },
401 + "manual_profiles": {
402 + "ui:listFlavour": "list",
403 + "ui:help": "**Profiles are always applied automatically** based on the device sysObjectID. **If no sysObjectID is provided** (rare case, e.g. some printers), the profiles listed here will be applied instead. In most cases, **leave this empty**."
404 + },
405 "network_interface_filter": {
406 "ui:collapsible": true
407 },
@@ -499,7 +516,8 @@
516 "fields": [
517 "enable_profiles",
518 "enable_profiles_table_metrics",
502 - "disable_legacy_collection"
519 + "disable_legacy_collection",
520 + "manual_profiles"
521 ]
522 }
523 ]
src/go/plugin/go.d/collector/snmp/ddsnmp/profile.go
+34 -9
@@ -30,29 +30,54 @@ func OidMatches(sysObjId, id string) bool {
30
31 // FindProfiles returns profiles matching the given sysObjectID.
32 // Profiles are sorted by match specificity: most specific first.
33 -func FindProfiles(sysObjId string) []*Profile {
33 +func FindProfiles(sysObjID string, manualProfiles []string) []*Profile {
34 loadProfiles()
35
36 + finalize := func(profiles []*Profile) []*Profile {
37 + if len(profiles) == 0 {
38 + return nil
39 + }
40 + enrichProfiles(profiles)
41 + deduplicateMetricsAcrossProfiles(profiles)
42 + return profiles
43 + }
44 +
45 + // Fallback/manual path (no sysObjectID)
46 + if sysObjID == "" {
47 + if len(manualProfiles) == 0 {
48 + log.Warning("No sysObjectID found and no manual_profiles configured. Either ensure the device provides sysObjectID or configure manual_profiles option.")
49 + return nil
50 + }
51 +
52 + var selected []*Profile
53 + for _, prof := range ddProfiles {
54 + name := stripFileNameExt(prof.SourceFile)
55 + if slices.ContainsFunc(manualProfiles, func(p string) bool { return stripFileNameExt(p) == name }) {
56 + selected = append(selected, prof.clone())
57 + }
58 + }
59 +
60 + return finalize(selected)
61 + }
62 +
63 + // Auto-detect path
64 matchedOIDs := make(map[*Profile]string)
37 - var profiles []*Profile
65 + var selected []*Profile
66
67 for _, prof := range ddProfiles {
68 // Use first matching OID (profile author's responsibility to order them)
69 for _, id := range prof.Definition.SysObjectIDs {
42 - if OidMatches(sysObjId, id) {
70 + if OidMatches(sysObjID, id) {
71 cloned := prof.clone()
44 - profiles = append(profiles, cloned)
72 + selected = append(selected, cloned)
73 matchedOIDs[cloned] = id
74 break
75 }
76 }
77 }
78
51 - sortProfilesBySpecificity(profiles, matchedOIDs)
52 -
53 - enrichProfiles(profiles)
54 - deduplicateMetricsAcrossProfiles(profiles)
55 - return profiles
79 + sortProfilesBySpecificity(selected, matchedOIDs)
80 + return finalize(selected)
81 }
82
83 type (
src/go/plugin/go.d/collector/snmp/ddsnmp/profile_test.go
+10 -4
@@ -44,8 +44,9 @@ func Test_loadDDSnmpProfiles(t *testing.T) {
44
45 func Test_FindProfiles(t *testing.T) {
46 test := map[string]struct {
47 - sysObjOId string
48 - wanProfiles int
47 + sysObjOId string
48 + manualProfiles []string
49 + wanProfiles int
50 }{
51 "mikrotik": {
52 sysObjOId: "1.3.6.1.4.1.14988.1",
@@ -63,11 +64,16 @@ func Test_FindProfiles(t *testing.T) {
64 sysObjOId: "1.3.6.1.4.1.9.1.2170",
65 wanProfiles: 3,
66 },
67 + "no sysObjectID, manual profile applied": {
68 + sysObjOId: "",
69 + manualProfiles: []string{"generic-device"},
70 + wanProfiles: 1,
71 + },
72 }
73
74 for name, test := range test {
75 t.Run(name, func(t *testing.T) {
70 - profiles := FindProfiles(test.sysObjOId)
76 + profiles := FindProfiles(test.sysObjOId, test.manualProfiles)
77
78 require.Len(t, profiles, test.wanProfiles)
79 })
@@ -75,7 +81,7 @@ func Test_FindProfiles(t *testing.T) {
81 }
82
83 func Test_Profile_merge(t *testing.T) {
78 - profiles := FindProfiles("1.3.6.1.4.1.9.1.1216") // cisco-nexus
84 + profiles := FindProfiles("1.3.6.1.4.1.9.1.1216", nil) // cisco-nexus
85
86 i := slices.IndexFunc(profiles, func(p *Profile) bool {
87 return strings.HasSuffix(p.SourceFile, "cisco-nexus.yaml")
src/go/plugin/go.d/collector/snmp/testdata/config.json
+4 -1
@@ -56,5 +56,8 @@
56 ],
57 "enable_profiles": true,
58 "enable_profiles_table_metrics": true,
59 - "disable_legacy_collection": true
59 + "disable_legacy_collection": true,
60 + "manual_profiles": [
61 + "ok"
62 + ]
63 }
src/go/plugin/go.d/collector/snmp/testdata/config.yaml
+2
@@ -5,6 +5,8 @@ vnode_device_down_threshold: 123
5 enable_profiles: yes
6 enable_profiles_table_metrics: yes
7 disable_legacy_collection: yes
8 +manual_profiles:
9 + - "ok"
10 vnode:
11 name: "ok"
12 guid: "ok"