fix(collector): dedup GPUs and resolve model text on xFusion boards without PCI identity

A re-run against the G5500 V7 (previous fix commit a3567dd) surfaced two
follow-on bugs once the GPUs started being classified correctly:

- gpuDocDedupKey fell straight through to the full @odata.id path when a
  GPU had neither SerialNumber nor BDF, so the same physical H100 exposed
  under both Systems/1/PCIeDevices and Chassis/1/PCIeDevices (identical
  resource Id, different collection root) was kept twice, doubling the
  reported GPU count. Added an Id + resolved VendorID/DeviceID fallback
  tier that collapses same-Id duplicates without collapsing genuinely
  distinct GPUs that happen to share a vendor/device pair.

- isMissingOrRawPCIModel/isGenericRedfishInventoryName didn't recognize
  xFusion's generic "PCIeCardN"/"OCPCardN" slot labels, so when the doc's
  actual Model field was empty and GPU.Model fell back to the slot-like
  Name field, the pci.ids VendorId/DeviceId resolution never fired and the
  GPU surfaced with a meaningless model like "PCIeCard1" instead of the
  real chip name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-08-18 14:24:49 +03:00
co-authored by Claude Sonnet 5
parent a3567dd5f6
commit 1e4ec513e1
2 changed files with 83 additions and 0 deletions
+13
View File
@@ -4591,6 +4591,10 @@ func isGenericRedfishInventoryName(value string) bool {
return true
case value == "pciedevice", strings.HasPrefix(value, "pciedevice_"), strings.HasPrefix(value, "pciedevice "):
return true
// xFusion (and similar OEM BMCs) label generic PCIe/OCP slots "PCIeCardN"/
// "OCPCardN" instead of a real model — these are slot labels, not model text.
case strings.HasPrefix(value, "pciecard"), strings.HasPrefix(value, "ocpcard"):
return true
case value == "pciefunction", strings.HasPrefix(value, "pciefunction_"), strings.HasPrefix(value, "pciefunction "):
return true
case value == "ethernetinterface", strings.HasPrefix(value, "ethernetinterface_"), strings.HasPrefix(value, "ethernetinterface "):
@@ -4818,6 +4822,15 @@ func gpuDocDedupKey(doc map[string]interface{}, gpu models.GPU) string {
if bdf := strings.TrimSpace(gpu.BDF); bdf != "" {
return bdf
}
// Some BMCs (e.g. xFusion) expose the same physical GPU under both
// Systems/{id}/PCIeDevices and Chassis/{id}/PCIeDevices with neither
// serial nor BDF populated in either copy. The resource Id is identical
// across both trees for the same device, so combine it with the resolved
// VendorID/DeviceID (stable, collection-independent) to still collapse
// them without risking collisions between distinct GPUs.
if id := strings.ToLower(strings.TrimSpace(asString(doc["Id"]))); id != "" && (gpu.VendorID != 0 || gpu.DeviceID != 0) {
return fmt.Sprintf("id:%s|vd:%04x:%04x", id, gpu.VendorID, gpu.DeviceID)
}
if path := normalizeRedfishPath(asString(doc["@odata.id"])); path != "" {
return "path:" + path
}