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
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ab8636da04
commit
2fa0f78f94
+84
-27
@@ -10,19 +10,32 @@ import (
|
||||
"git.mchus.pro/mchus/logpile/internal/parser"
|
||||
)
|
||||
|
||||
// solSmartdDeviceRe matches smartd device info lines from SOLHostCapture.log.
|
||||
// Example:
|
||||
// smartd prints one device-info line per drive during startup. Two shapes occur,
|
||||
// depending on how the drive is attached:
|
||||
//
|
||||
// Device: /dev/sda [SAT], Micron_5400_MTFDDAK480TGA, S/N:2310400DC7E3, WWN:..., FW:D4CM003, 480 GB
|
||||
var solSmartdDeviceRe = regexp.MustCompile(
|
||||
`Device: /dev/\S+ \[SAT\], (.+?), S/N:(\S+),.*?FW:(\S+), ([\d.]+) (GB|TB)`,
|
||||
// SAT (SATA drive, ATA pass-through):
|
||||
// Device: /dev/sda [SAT], Micron_5400_MTFDDAK480TGA, S/N:2310400DC7E3, WWN:5-00a075-1400dc7e3, FW:D4CM003, 480 GB
|
||||
// SCSI (SAS drive, or SATA drive behind a SAS HBA in SCSI mode):
|
||||
// Device: /dev/sdb, [SEAGATE ST6000NM005B K0A1], lu id: 0x5000c500ee6e11f7, S/N: WX004FCC0000E22967PY, 6.00 TB
|
||||
//
|
||||
// The SCSI line has no firmware field and reports the model as a padded SCSI
|
||||
// INQUIRY triple "VENDOR PRODUCT REV".
|
||||
var (
|
||||
solSmartdSATDeviceRe = regexp.MustCompile(
|
||||
`Device: /dev/\S+ \[SAT\], (.+?), S/N:\s*(\S+),.*?FW:(\S+), ([\d.]+) (GB|TB)`,
|
||||
)
|
||||
solSmartdSCSIDeviceRe = regexp.MustCompile(
|
||||
`Device: /dev/\S+?, \[(.+?)\],.*?S/N:\s*(\S+), ([\d.]+) (GB|TB)`,
|
||||
)
|
||||
multiSpaceRe = regexp.MustCompile(`\s{2,}`)
|
||||
)
|
||||
|
||||
type solSmartdDevice struct {
|
||||
Model string
|
||||
Serial string
|
||||
Firmware string
|
||||
SizeGB int
|
||||
Model string
|
||||
Serial string
|
||||
Firmware string
|
||||
SizeGB int
|
||||
Interface string // "SATA" or "SAS"
|
||||
}
|
||||
|
||||
// parseSOLSmartdDevices extracts unique disk entries from SOLHostCapture.log content.
|
||||
@@ -32,31 +45,68 @@ func parseSOLSmartdDevices(content []byte) []solSmartdDevice {
|
||||
var out []solSmartdDevice
|
||||
|
||||
for _, line := range strings.Split(string(content), "\n") {
|
||||
m := solSmartdDeviceRe.FindStringSubmatch(line)
|
||||
if m == nil {
|
||||
dev, ok := parseSOLSmartdLine(line)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
serial := strings.TrimSpace(m[2])
|
||||
if serial == "" {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(serial)
|
||||
if _, ok := seen[key]; ok {
|
||||
key := strings.ToLower(dev.Serial)
|
||||
if _, dup := seen[key]; dup {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
|
||||
sizeGB := parseSolSizeGB(m[4], m[5])
|
||||
out = append(out, solSmartdDevice{
|
||||
Model: strings.TrimSpace(m[1]),
|
||||
Serial: serial,
|
||||
Firmware: strings.TrimSpace(m[3]),
|
||||
SizeGB: sizeGB,
|
||||
})
|
||||
out = append(out, dev)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseSOLSmartdLine(line string) (solSmartdDevice, bool) {
|
||||
if m := solSmartdSATDeviceRe.FindStringSubmatch(line); m != nil {
|
||||
serial := strings.TrimSpace(m[2])
|
||||
if serial == "" {
|
||||
return solSmartdDevice{}, false
|
||||
}
|
||||
return solSmartdDevice{
|
||||
Model: strings.TrimSpace(m[1]),
|
||||
Serial: serial,
|
||||
Firmware: strings.TrimSpace(m[3]),
|
||||
SizeGB: parseSolSizeGB(m[4], m[5]),
|
||||
Interface: "SATA",
|
||||
}, true
|
||||
}
|
||||
if m := solSmartdSCSIDeviceRe.FindStringSubmatch(line); m != nil {
|
||||
serial := strings.TrimSpace(m[2])
|
||||
if serial == "" {
|
||||
return solSmartdDevice{}, false
|
||||
}
|
||||
return solSmartdDevice{
|
||||
Model: scsiInquiryModel(m[1]),
|
||||
Serial: serial,
|
||||
SizeGB: parseSolSizeGB(m[3], m[4]),
|
||||
Interface: "SAS",
|
||||
}, true
|
||||
}
|
||||
return solSmartdDevice{}, false
|
||||
}
|
||||
|
||||
// scsiInquiryModel turns a padded SCSI INQUIRY string ("SEAGATE ST6000NM005B
|
||||
// K0A1") into "VENDOR PRODUCT", dropping the trailing revision. An "ATA" vendor
|
||||
// is a SATA drive bridged into SCSI mode — its product field already holds the
|
||||
// real model, so the vendor token is dropped.
|
||||
func scsiInquiryModel(raw string) string {
|
||||
fields := multiSpaceRe.Split(strings.TrimSpace(raw), -1)
|
||||
switch len(fields) {
|
||||
case 0:
|
||||
return strings.TrimSpace(raw)
|
||||
case 1:
|
||||
return fields[0]
|
||||
}
|
||||
vendor, product := fields[0], fields[1]
|
||||
if strings.EqualFold(vendor, "ATA") {
|
||||
return product
|
||||
}
|
||||
return vendor + " " + product
|
||||
}
|
||||
|
||||
// parseSolSizeGB converts smartd size string ("480", "3.84") + unit ("GB", "TB") to integer GB.
|
||||
// Uses decimal TB (1 TB = 1000 GB) matching disk manufacturer conventions.
|
||||
func parseSolSizeGB(value, unit string) int {
|
||||
@@ -215,7 +265,7 @@ func solEnrichByPlaceholder(hw *models.HardwareConfig, devices []solSmartdDevice
|
||||
hw.Storage[idx].Manufacturer = extractStorageManufacturer(d.Model)
|
||||
}
|
||||
if hw.Storage[idx].Interface == "" {
|
||||
hw.Storage[idx].Interface = "SATA"
|
||||
hw.Storage[idx].Interface = solDeviceInterface(d)
|
||||
}
|
||||
}
|
||||
return unmatched
|
||||
@@ -229,11 +279,18 @@ func solMakeStorage(d solSmartdDevice) models.Storage {
|
||||
SizeGB: d.SizeGB,
|
||||
Type: solStorageType(d.Model),
|
||||
Manufacturer: extractStorageManufacturer(d.Model),
|
||||
Interface: "SATA",
|
||||
Interface: solDeviceInterface(d),
|
||||
Present: true,
|
||||
}
|
||||
}
|
||||
|
||||
func solDeviceInterface(d solSmartdDevice) string {
|
||||
if d.Interface != "" {
|
||||
return d.Interface
|
||||
}
|
||||
return "SATA"
|
||||
}
|
||||
|
||||
// solStorageType infers SSD vs HDD from the model string.
|
||||
// Micron SSD models start with "MTFDD"; Intel SSDs contain "SSD".
|
||||
func solStorageType(model string) string {
|
||||
|
||||
Reference in New Issue
Block a user