fix(exporter): key PCIe canonical dedup by BDF before serial

canonicalKey used serial_number as the primary merge key for pcie-class
devices (PCIe/GPU/Network), falling back to BDF only when serial was empty.
Multi-port NICs commonly report one serial for the whole physical card
across all of its ports/BDFs (e.g. a dual-port ConnectX-5: same serial,
distinct BDFs 0000:65:00.0/.1, distinct MACs). Keying on serial first
collapsed every port sharing that serial into a single canonical device,
silently dropping all but one port from the export.

BDF now takes priority for pcie-class devices, since it uniquely identifies
one physical PCI function; other device kinds (storage, memory, PSU, etc.)
keep the existing serial-first behavior, which is correct there since a
serial genuinely maps 1:1 to a physical unit for those.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-08-17 09:42:34 +03:00
co-authored by Claude Sonnet 5
parent 8665f79fd6
commit 48baae41f4
+10 -1
View File
@@ -718,10 +718,19 @@ func mergeDetailMaps(primary, secondary map[string]any) map[string]any {
func canonicalKey(item models.HardwareDevice) string { func canonicalKey(item models.HardwareDevice) string {
kind := canonicalMergeKind(item.Kind) kind := canonicalMergeKind(item.Kind)
bdf := strings.ToLower(strings.TrimSpace(item.BDF))
if kind == "pcie-class" && bdf != "" {
// BDF identifies one physical PCI function; prefer it over serial for
// pcie-class devices. Multi-port NICs commonly report the same serial
// (the physical card's serial) across several BDFs/ports — keying on
// serial first would collapse distinct ports into a single record and
// silently drop the others.
return kind + "|bdf:" + bdf
}
if sn := normalizedSerial(item.SerialNumber); sn != "" { if sn := normalizedSerial(item.SerialNumber); sn != "" {
return kind + "|sn:" + strings.ToLower(sn) return kind + "|sn:" + strings.ToLower(sn)
} }
if bdf := strings.ToLower(strings.TrimSpace(item.BDF)); bdf != "" { if bdf != "" {
return kind + "|bdf:" + bdf return kind + "|bdf:" + bdf
} }
return "" return ""