Files
logpile/internal/parser/vendors/inspur/diagnose.go
T
Mikhail ChusavitinandClaude Sonnet 5 2fa0f78f94 fix(inspur): read RESTful FRU info and float fans_power from component.log
Diffing an NF5280M6 BMC dump against its BEE-SP live-CD bundle found two
blind spots in the combined-component.log onekeylog layout (no
devicefrusdr.log / asset.json):

- board manufacturer/product/part/uuid empty and stats.fru 0: the
  "RESTful FRU info:" JSON block was never parsed. New component_fru.go
  (ParseComponentLogFRU) flattens it to []models.FRUInfo, prefers the
  product-area system serial over the board PCB serial, and sets
  BoardInfo.UUID from system_uuid. Wired as a fallback only when
  result.FRU is still empty.
- zero fan sensors: FanRESTInfo.FansPower was int but this firmware
  writes "fans_power": 12.000000, so json.Unmarshal of the whole fan
  block failed. Changed to float64.

Also included: SOL smartd SCSI/SAS device-line parsing and diagnose.go
gofmt from concurrent work on the same live-CD-diff task. See ADL-064.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LffAvostt3uMkiUbVUiyM
2026-09-01 11:58:55 +03:00

122 lines
4.1 KiB
Go

// Parses log/bmc/diagnose/OtrdDiagnoseComponent.json, an HGX BMC structural
// snapshot of PCIe topology (present GPUs/NICs/NVMe, negotiated link state).
// This is a direct point-in-time source for GPU presence and PCIe link
// degradation, independent of SEL/IDL alarm history.
package inspur
import (
"encoding/json"
"fmt"
"strings"
"git.mchus.pro/mchus/logpile/internal/models"
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
)
// otrdDiagnosePCIeEntry mirrors the relevant fields of "Pcie Device Info"
// entries in OtrdDiagnoseComponent.json.
type otrdDiagnosePCIeEntry struct {
LocString string `json:"LocString"`
PcieSlot int `json:"PcieSlot"`
PresentStatus int `json:"PresentStatus"`
VendorId int `json:"VendorId"`
DeviceId int `json:"DeviceId"`
BusNumber int `json:"BusNumber"`
DeviceNumber int `json:"DeviceNumber"`
FunctionNumber int `json:"FunctionNumber"`
CurrentLinkSpeed int `json:"CurrentLinkSpeed"`
MaxLinkSpeed int `json:"MaxLinkSpeed"`
NegotiatedLinkWidth int `json:"NegotiatedLinkWidth"`
MaxLinkWidth int `json:"MaxLinkWidth"`
SerialNumber *string `json:"SerialNumber"`
PartNumber *string `json:"PartNumber"`
}
type otrdDiagnoseComponent struct {
PcieDeviceInfo []otrdDiagnosePCIeEntry `json:"Pcie Device Info"`
}
// ParseOtrdDiagnosePCIe parses the "Pcie Device Info" array from
// OtrdDiagnoseComponent.json into PCIe device inventory entries.
func ParseOtrdDiagnosePCIe(content []byte) []models.PCIeDevice {
var doc otrdDiagnoseComponent
if err := json.Unmarshal(content, &doc); err != nil {
return nil
}
devices := make([]models.PCIeDevice, 0, len(doc.PcieDeviceInfo))
for _, e := range doc.PcieDeviceInfo {
present := e.PresentStatus == 1
_, deviceName := pciids.DeviceInfo(e.VendorId, e.DeviceId)
deviceClass := normalizeModelLabel(deviceName)
if strings.Contains(strings.ToUpper(deviceName), "NVIDIA") {
deviceClass = "GPU (" + deviceName + ")"
}
serial := ""
if e.SerialNumber != nil {
serial = strings.TrimSpace(*e.SerialNumber)
}
partNum := ""
if e.PartNumber != nil {
partNum = strings.TrimSpace(*e.PartNumber)
}
status := ""
if present && isLinkDegraded(e) {
status = "Link Degraded"
}
devices = append(devices, models.PCIeDevice{
Slot: e.LocString,
VendorID: e.VendorId,
DeviceID: e.DeviceId,
BDF: formatBDF(e.BusNumber, e.DeviceNumber, e.FunctionNumber),
DeviceClass: deviceClass,
Manufacturer: normalizeModelLabel(pciids.VendorName(e.VendorId)),
LinkWidth: e.NegotiatedLinkWidth,
LinkSpeed: fmt.Sprintf("GEN%d", e.CurrentLinkSpeed),
MaxLinkWidth: e.MaxLinkWidth,
MaxLinkSpeed: fmt.Sprintf("GEN%d", e.MaxLinkSpeed),
PartNumber: partNum,
SerialNumber: serial,
Present: &present,
Status: status,
})
}
return devices
}
func isLinkDegraded(e otrdDiagnosePCIeEntry) bool {
if e.MaxLinkSpeed > 0 && e.CurrentLinkSpeed < e.MaxLinkSpeed {
return true
}
if e.MaxLinkWidth > 0 && e.NegotiatedLinkWidth < e.MaxLinkWidth {
return true
}
return false
}
// BuildPCIeLinkDegradationEvents emits a Warning event for every PCIe device
// present in OtrdDiagnoseComponent.json but running below its negotiated max
// link speed/width (e.g. a GPU baseboard degrading from Gen5 x16 to a lower
// state), so the log viewer surfaces the same signal the IDL alarms would.
func BuildPCIeLinkDegradationEvents(devices []models.PCIeDevice) []models.Event {
var events []models.Event
for _, d := range devices {
if d.Present == nil || !*d.Present || d.Status != "Link Degraded" {
continue
}
events = append(events, models.Event{
ID: fmt.Sprintf("pcie_link_degraded_%s", strings.TrimPrefix(d.Slot, "#")),
Source: "PCIe Diagnose",
SensorType: "pcie_link",
EventType: "Link Degraded",
Severity: models.SeverityWarning,
Description: fmt.Sprintf("%s: link degraded to %s x%d (max %s x%d)", d.Slot, d.LinkSpeed, d.LinkWidth, d.MaxLinkSpeed, d.MaxLinkWidth),
})
}
return events
}