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 {