@cryptotaxi247 / netdata / commits / 6591c20b5

chore(go.d/ddsnmp): Improve profile sorting by match specificity (#21042)

Ilya Mashchenko committed Sep 23, 2025 at 22:21 UTC 6591c20b5518b77d82dc0315e24e3f2a8ce0d7cf
2 files changed +66 -3
src/go/plugin/go.d/collector/snmp/ddsnmp/profile.go
+18 -3
@@ -259,12 +259,27 @@ func sortProfilesBySpecificity(profiles []*Profile, matchedOIDs map[*Profile]str
259 aOID := matchedOIDs[a]
260 bOID := matchedOIDs[b]
261
262 - // 1. Longer OIDs first (more specific)
262 + // 0) Profiles with an OID match (non-empty) come before descr-only matches (empty)
263 + aHasOID := aOID != ""
264 + bHasOID := bOID != ""
265 + if aHasOID != bHasOID {
266 + if aHasOID {
267 + return -1
268 + }
269 + return 1
270 + }
271 +
272 + // If both are descr-only (both empty), keep stable order.
273 + if !aHasOID && !bHasOID {
274 + return 0
275 + }
276 +
277 + // 1) Longer OIDs first (more specific)
278 if diff := len(bOID) - len(aOID); diff != 0 {
279 return diff
280 }
281
267 - // 2. Same length: exact OIDs before patterns
282 + // 2) Same length: exact OIDs before patterns
283 aIsExact := ddprofiledefinition.IsPlainOid(aOID)
284 bIsExact := ddprofiledefinition.IsPlainOid(bOID)
285 if aIsExact != bIsExact {
@@ -274,7 +289,7 @@ func sortProfilesBySpecificity(profiles []*Profile, matchedOIDs map[*Profile]str
289 return 1
290 }
291
277 - // 3. Same type: lexicographic order for stability
292 + // 3) Same type: lexicographic order for stability
293 return strings.Compare(aOID, bOID)
294 })
295 }
src/go/plugin/go.d/collector/snmp/ddsnmp/profile_test.go
+48
@@ -937,6 +937,54 @@ func TestSortProfilesBySpecificity(t *testing.T) {
937 "generic.yaml",
938 },
939 },
940 + "mix: longer > shorter; OID > descr-only": {
941 + profiles: []string{"a.yaml", "b.yaml", "c.yaml", "d.yaml"},
942 + matchedOIDs: []string{"1.3.6.1", "1.3.*", "", "1.3.6.1.4"},
943 + // d: longest "1.3.6.1.4" > a: "1.3.6.1" > b: "1.3.*" > c: descr-only
944 + expected: []string{"d.yaml", "a.yaml", "b.yaml", "c.yaml"},
945 + },
946 + "oid match outranks descr-only": {
947 + profiles: []string{"descr-only.yaml", "exact.yaml"},
948 + matchedOIDs: []string{"", "1.2.3"},
949 + expected: []string{"exact.yaml", "descr-only.yaml"},
950 + },
951 + "same length: exact before pattern": {
952 + profiles: []string{"exact.yaml", "pattern.yaml"},
953 + matchedOIDs: []string{"1.3.4", "1.3.*"}, // both len == 5
954 + expected: []string{"exact.yaml", "pattern.yaml"},
955 + },
956 + "longer pattern beats shorter exact (by design)": {
957 + profiles: []string{"exact-short.yaml", "pattern-long.yaml"},
958 + matchedOIDs: []string{"1.3.4", "1.3.4.5.*"}, // len(pattern) > len(exact)
959 + expected: []string{"pattern-long.yaml", "exact-short.yaml"},
960 + },
961 + "lexicographic tiebreaker (same type & same length, both exact)": {
962 + profiles: []string{"a.yaml", "b.yaml"},
963 + matchedOIDs: []string{"1.10", "1.20"}, // same len, both exact
964 + expected: []string{"a.yaml", "b.yaml"}, // "1.10" < "1.20"
965 + },
966 + "stability when both descr-only (both empty)": {
967 + profiles: []string{"x.yaml", "y.yaml", "z.yaml"},
968 + matchedOIDs: []string{"", "", ""},
969 + // comparator returns 0 → SortStable preserves original order
970 + expected: []string{"x.yaml", "y.yaml", "z.yaml"},
971 + },
972 + "patterns by length (no exacts)": {
973 + profiles: []string{"p1.yaml", "p2.yaml", "p3.yaml"},
974 + matchedOIDs: []string{"1.*", "1.2.*", "1.2.3.*"},
975 + expected: []string{"p3.yaml", "p2.yaml", "p1.yaml"},
976 + },
977 + "tie: same length patterns → lexicographic": {
978 + profiles: []string{"pA.yaml", "pB.yaml"},
979 + matchedOIDs: []string{"1.9.*", "1.10.*"}, // same length strings
980 + expected: []string{"pB.yaml", "pA.yaml"}, // "1.10.*" < "1.9.*" lexicographically
981 + },
982 + "mixed: two OID matches + one descr-only; ensure empty last": {
983 + profiles: []string{"o1.yaml", "desc.yaml", "o2.yaml"},
984 + matchedOIDs: []string{"1.2.*", "", "1.2.3"},
985 + // longer first among OID matches, then empty
986 + expected: []string{"o2.yaml", "o1.yaml", "desc.yaml"},
987 + },
988 }
989
990 for name, tc := range tests {