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
122 lines
4.1 KiB
Go
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
|
|
}
|