fix(exporter): drop dead bmc_event_summary field, fix status loss for inspur CPU/GPU

bmc_event_summary was a derived Assert/Deassert summary added in 4409594
alongside real fixes for GPU fault handling. It's not part of the Reanimator
hardware-ingest contract (event_logs is the only accepted log channel) and
was silently dropped on import — pure dead weight, removed.

Three related status bugs surfaced while auditing converted exports against
the contract, all specific to Inspur/onekeylog dumps:
- CPU status from RESTful CPU info was parsed but never assigned to
  models.CPU, and was skipped entirely whenever asset.json already supplied
  a CPU list (its own inventory has no status field) — CPUs always exported
  as Unknown even when the source reported OK.
- PCIe device status (RESTful "status": 1) was parsed but never mapped onto
  models.PCIeDevice, so RESTful-only devices always lost status.
- For GPUs specifically, asset.go emits two device records per physical GPU
  (a generic pcie_devices entry enriched with real status, and a separate
  gpus entry with a resolved model name but no status). dedupePCIe picks a
  single winner by quality score, and a better model name outweighed having
  a real status — the winner kept "Unknown" even when a losing duplicate had
  the real value. dedupePCIe now backfills status onto the winner from a
  losing duplicate when the winner's is Unknown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-08-15 12:11:16 +03:00
co-authored by Claude Sonnet 5
parent 0867123a91
commit 42cfa3aa94
4 changed files with 90 additions and 124 deletions
+32 -17
View File
@@ -19,10 +19,9 @@ func ParseComponentLog(content []byte, hw *models.HardwareConfig) {
text := string(content)
// Parse RESTful CPU info fallback when asset.json is absent
if len(hw.CPUs) == 0 {
parseCPUInfo(text, hw)
}
// Parse RESTful CPU info: fills in hw.CPUs when asset.json didn't provide any,
// and enriches asset.json-derived entries (e.g. status) when it did.
parseCPUInfo(text, hw)
// Parse RESTful Memory info (detailed memory data)
parseMemoryInfo(text, hw)
@@ -148,23 +147,39 @@ func parseCPUInfo(text string, hw *models.HardwareConfig) {
return
}
existingBySocket := make(map[int]int, len(hw.CPUs))
for i, existing := range hw.CPUs {
existingBySocket[existing.Socket] = i
}
for _, proc := range cpuInfo.Processors {
if proc.ProcStatus != 1 && proc.ConfigStatus != 1 {
continue
}
hw.CPUs = append(hw.CPUs, models.CPU{
Socket: proc.ProcID,
Model: strings.TrimSpace(proc.ProcName),
Cores: proc.CoreCount,
Threads: proc.ThreadCount,
FrequencyMHz: proc.ProcSpeed,
MaxFreqMHz: proc.MaxSpeedMHz,
L1CacheKB: proc.L1Cache,
L2CacheKB: proc.L2Cache,
L3CacheKB: proc.L3Cache,
TDP: proc.TDP,
PPIN: proc.PPIN,
})
status := strings.TrimSpace(proc.Status)
if idx, ok := existingBySocket[proc.ProcID]; ok {
// asset.json already provided this CPU (no status field there) —
// enrich it with status from the RESTful CPU info instead of
// appending a duplicate entry.
if strings.TrimSpace(hw.CPUs[idx].Status) == "" {
hw.CPUs[idx].Status = status
}
} else {
hw.CPUs = append(hw.CPUs, models.CPU{
Socket: proc.ProcID,
Model: strings.TrimSpace(proc.ProcName),
Cores: proc.CoreCount,
Threads: proc.ThreadCount,
FrequencyMHz: proc.ProcSpeed,
MaxFreqMHz: proc.MaxSpeedMHz,
L1CacheKB: proc.L1Cache,
L2CacheKB: proc.L2Cache,
L3CacheKB: proc.L3Cache,
TDP: proc.TDP,
PPIN: proc.PPIN,
Status: status,
})
}
if proc.MicroCode != "" {
hw.Firmware = append(hw.Firmware, models.FirmwareInfo{
DeviceName: fmt.Sprintf("CPU%d Microcode", proc.ProcID),
+12
View File
@@ -189,6 +189,7 @@ func ParsePCIeDevices(content []byte) []models.PCIeDevice {
MaxLinkSpeed: maxSpeed,
PartNumber: partNumber,
SerialNumber: strings.TrimSpace(pcie.SerialNum),
Status: pcieRESTStatus(pcie.Status),
}
devices = append(devices, device)
@@ -197,6 +198,17 @@ func ParsePCIeDevices(content []byte) []models.PCIeDevice {
return devices
}
// pcieRESTStatus maps the RESTful PCIE Device info "status" flag (1 = OK) to
// the shared status vocabulary. Only the observed OK case is mapped — the
// meaning of other values isn't confirmed in the source, so it's left
// unknown rather than guessed.
func pcieRESTStatus(status int) string {
if status == 1 {
return "OK"
}
return ""
}
var rawHexDeviceNameRegex = regexp.MustCompile(`(?i)^0x[0-9a-f]+$`)
func sanitizePCIeDeviceName(name string) string {