export: align reanimator and enrich redfish metrics

This commit is contained in:
Mikhail Chusavitin
2026-03-15 21:38:28 +03:00
parent 0acdc2b202
commit 9007f1b360
17 changed files with 3756 additions and 650 deletions

View File

@@ -16,6 +16,7 @@ import (
"git.mchus.pro/mchus/logpile/internal/models"
"git.mchus.pro/mchus/logpile/internal/parser"
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
)
const parserVersion = "3.0"
@@ -199,7 +200,7 @@ func parseDCIMViewXML(content []byte, result *models.AnalysisResult) {
parsePowerSupplyView(props, result)
case "DCIM_PCIDeviceView":
parsePCIeDeviceView(props, result)
case "DCIM_NICView":
case "DCIM_NICView", "DCIM_InfiniBandView":
parseNICView(props, result)
case "DCIM_VideoView":
parseVideoView(props, result)
@@ -374,6 +375,10 @@ func parsePhysicalDiskView(props map[string]string, result *models.AnalysisResul
Location: strings.TrimSpace(props["devicedescription"]),
Status: normalizeStatus(firstNonEmpty(props["raidstatus"], props["primarystatus"])),
}
if v := strings.TrimSpace(props["remainingratedwriteendurance"]); v != "" {
n := parseIntLoose(v)
st.RemainingEndurancePct = &n
}
result.Hardware.Storage = append(result.Hardware.Storage, st)
}
@@ -437,11 +442,18 @@ var pcieFQDDNoisePrefix = []string{
"SMBus.Embedded.",
"AHCI.Embedded.",
"Video.Embedded.",
"NIC.Embedded.",
// All NIC FQDD classes are parsed from DCIM_NICView / DCIM_InfiniBandView into
// NetworkAdapters with model, MAC, firmware, and VendorID/DeviceID. The
// DCIM_PCIDeviceView duplicate carries only DataBusWidth ("Unknown", "16x or x16")
// and no useful extra data, so suppress it here.
"NIC.",
"InfiniBand.",
}
func parsePCIeDeviceView(props map[string]string, result *models.AnalysisResult) {
desc := strings.TrimSpace(firstNonEmpty(props["devicedescription"], props["description"]))
// "description" is the chip/device model (e.g. "MT28908 Family [ConnectX-6]"); prefer
// it over "devicedescription" which is the location string ("InfiniBand in Slot 1 Port 1").
desc := strings.TrimSpace(firstNonEmpty(props["description"], props["devicedescription"]))
fqdd := strings.TrimSpace(firstNonEmpty(props["fqdd"], props["instanceid"]))
if desc == "" && fqdd == "" {
return
@@ -451,14 +463,26 @@ func parsePCIeDeviceView(props map[string]string, result *models.AnalysisResult)
return
}
}
vendorID := parseHexOrDec(firstNonEmpty(props["pcivendorid"], props["vendorid"]))
deviceID := parseHexOrDec(firstNonEmpty(props["pcideviceid"], props["deviceid"]))
manufacturer := strings.TrimSpace(props["manufacturer"])
// General rule: if chip model not found in logs but PCI IDs are known, resolve from pci.ids
if desc == "" && vendorID != 0 && deviceID != 0 {
desc = pciids.DeviceName(vendorID, deviceID)
}
if manufacturer == "" && vendorID != 0 {
manufacturer = pciids.VendorName(vendorID)
}
p := models.PCIeDevice{
Slot: fqdd,
Description: desc,
VendorID: parseHexOrDec(firstNonEmpty(props["pcivendorid"], props["vendorid"])),
DeviceID: parseHexOrDec(firstNonEmpty(props["pcideviceid"], props["deviceid"])),
VendorID: vendorID,
DeviceID: deviceID,
BDF: formatBDF(props["busnumber"], props["devicenumber"], props["functionnumber"]),
DeviceClass: strings.TrimSpace(props["databuswidth"]),
Manufacturer: strings.TrimSpace(props["manufacturer"]),
Manufacturer: manufacturer,
NUMANode: parseIntLoose(props["cpuaffinity"]),
Status: normalizeStatus(props["primarystatus"]),
}
result.Hardware.PCIeDevices = append(result.Hardware.PCIeDevices, p)
@@ -471,15 +495,31 @@ func parseNICView(props map[string]string, result *models.AnalysisResult) {
return
}
mac := strings.TrimSpace(firstNonEmpty(props["currentmacaddress"], props["permanentmacaddress"]))
vendorID := parseHexOrDec(firstNonEmpty(props["pcivendorid"], props["vendorid"]))
deviceID := parseHexOrDec(firstNonEmpty(props["pcideviceid"], props["deviceid"]))
vendor := strings.TrimSpace(firstNonEmpty(props["vendorname"], props["manufacturer"]))
// Prefer pci.ids chip model over generic ProductName when PCI IDs are available.
// Dell TSR often reports a marketing name (e.g. "Mellanox Network Adapter") while
// pci.ids has the precise chip identifier (e.g. "MT28908 Family [ConnectX-6]").
if vendorID != 0 && deviceID != 0 {
if chipModel := pciids.DeviceName(vendorID, deviceID); chipModel != "" {
model = chipModel
}
if vendor == "" {
vendor = pciids.VendorName(vendorID)
}
}
n := models.NetworkAdapter{
Slot: fqdd,
Location: strings.TrimSpace(firstNonEmpty(props["devicedescription"], fqdd)),
Present: true,
Model: model,
Description: strings.TrimSpace(props["protocol"]),
Vendor: strings.TrimSpace(firstNonEmpty(props["vendorname"], props["manufacturer"])),
VendorID: parseHexOrDec(firstNonEmpty(props["pcivendorid"], props["vendorid"])),
DeviceID: parseHexOrDec(firstNonEmpty(props["pcideviceid"], props["deviceid"])),
Vendor: vendor,
VendorID: vendorID,
DeviceID: deviceID,
SerialNumber: strings.TrimSpace(props["serialnumber"]),
PartNumber: strings.TrimSpace(props["partnumber"]),
Firmware: strings.TrimSpace(firstNonEmpty(
@@ -489,6 +529,7 @@ func parseNICView(props map[string]string, result *models.AnalysisResult) {
props["controllerbiosversion"],
)),
PortCount: inferPortCountFromFQDD(fqdd),
NUMANode: parseIntLoose(props["cpuaffinity"]),
Status: normalizeStatus(props["primarystatus"]),
}
if mac != "" {
@@ -542,10 +583,11 @@ func parseControllerView(props map[string]string, result *models.AnalysisResult)
DeviceClass: "storage-controller",
Manufacturer: strings.TrimSpace(firstNonEmpty(props["devicecardmanufacturer"], props["manufacturer"])),
PartNumber: strings.TrimSpace(firstNonEmpty(props["ppid"], props["boardpartnumber"])),
NUMANode: parseIntLoose(props["cpuaffinity"]),
Status: normalizeStatus(props["primarystatus"]),
})
addFirmware(result, firstNonEmpty(name, fqdd), props["controllerfirmwareversion"], "storage controller")
addFirmware(result, firstNonEmpty(name, fqdd), props["controllerfirmwareversion"], firstNonEmpty(fqdd, "storage controller"))
}
func parseControllerBatteryView(props map[string]string, result *models.AnalysisResult) {
@@ -1131,6 +1173,7 @@ func mergeStorage(dst *models.Storage, src models.Storage) {
}
setIfEmpty(&dst.Location, src.Location)
setIfEmpty(&dst.Status, src.Status)
dst.Details = mergeDellDetails(dst.Details, src.Details)
}
func dedupeVolumes(items []models.StorageVolume) []models.StorageVolume {
@@ -1202,6 +1245,22 @@ func mergePSU(dst *models.PSU, src models.PSU) {
dst.InputVoltage = src.InputVoltage
}
setIfEmpty(&dst.InputType, src.InputType)
dst.Details = mergeDellDetails(dst.Details, src.Details)
}
func mergeDellDetails(primary, secondary map[string]any) map[string]any {
if len(secondary) == 0 {
return primary
}
if primary == nil {
primary = make(map[string]any, len(secondary))
}
for key, value := range secondary {
if _, ok := primary[key]; !ok {
primary[key] = value
}
}
return primary
}
func dedupeNetworkAdapters(items []models.NetworkAdapter) []models.NetworkAdapter {