Files
logpile/internal/parser/vendors/inspur/asset.go
Michael Chus 83378fa761 Add firmware versions for all components
Extract firmware from:
- asset.json: BIOS, ME, BKC, CPU Microcode, HDD/SSD/NVMe
- component.log: PSU firmware, Network adapter firmware

Deduplicate entries to avoid showing same firmware twice.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 09:24:36 +03:00

411 lines
11 KiB
Go

package inspur
import (
"encoding/json"
"fmt"
"strings"
"git.mchus.pro/mchus/logpile/internal/models"
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
)
// AssetJSON represents the structure of Inspur asset.json file
type AssetJSON struct {
VersionInfo []struct {
DeviceID int `json:"DeviceId"`
DeviceName string `json:"DeviceName"`
DeviceRevision string `json:"DeviceRevision"`
BuildTime string `json:"BuildTime"`
} `json:"VersionInfo"`
CpuInfo []struct {
ProcessorName string `json:"ProcessorName"`
ProcessorID string `json:"ProcessorId"`
MicroCodeVer string `json:"MicroCodeVer"`
CurrentSpeed int `json:"CurrentSpeed"`
Core int `json:"Core"`
ThreadCount int `json:"ThreadCount"`
L1Cache int `json:"L1Cache"`
L2Cache int `json:"L2Cache"`
L3Cache int `json:"L3Cache"`
CpuTdp int `json:"CpuTdp"`
PPIN string `json:"PPIN"`
TurboEnableMaxSpeed int `json:"TurboEnableMaxSpeed"`
TurboCloseMaxSpeed int `json:"TurboCloseMaxSpeed"`
UPIBandwidth string `json:"UPIBandwidth"`
} `json:"CpuInfo"`
MemInfo struct {
MemCommonInfo []struct {
Manufacturer string `json:"Manufacturer"`
MaxSpeed int `json:"MaxSpeed"`
CurrentSpeed int `json:"CurrentSpeed"`
MemoryType int `json:"MemoryType"`
Rank int `json:"Rank"`
DataWidth int `json:"DataWidth"`
ConfiguredVoltage int `json:"ConfiguredVoltage"`
PhysicalSize int `json:"PhysicalSize"`
} `json:"MemCommonInfo"`
DimmInfo []struct {
SerialNumber string `json:"SerialNumber"`
PartNumber string `json:"PartNumber"`
AssetTag string `json:"AssetTag"`
} `json:"DimmInfo"`
} `json:"MemInfo"`
HddInfo []struct {
SerialNumber string `json:"SerialNumber"`
Manufacturer string `json:"Manufacturer"`
ModelName string `json:"ModelName"`
FirmwareVersion string `json:"FirmwareVersion"`
Capacity int `json:"Capacity"`
Location int `json:"Location"`
DiskInterfaceType int `json:"DiskInterfaceType"`
MediaType int `json:"MediaType"`
LocationString string `json:"LocationString"`
BlockSizeBytes int `json:"BlockSizeBytes"`
CapableSpeedGbs string `json:"CapableSpeedGbs"`
NegotiatedSpeedGbs string `json:"NegotiatedSpeedGbs"`
PcieSlot int `json:"PcieSlot"`
} `json:"HddInfo"`
PcieInfo []struct {
VendorId int `json:"VendorId"`
DeviceId int `json:"DeviceId"`
BusNumber int `json:"BusNumber"`
DeviceNumber int `json:"DeviceNumber"`
FunctionNumber int `json:"FunctionNumber"`
MaxLinkWidth int `json:"MaxLinkWidth"`
MaxLinkSpeed int `json:"MaxLinkSpeed"`
NegotiatedLinkWidth int `json:"NegotiatedLinkWidth"`
CurrentLinkSpeed int `json:"CurrentLinkSpeed"`
ClassCode int `json:"ClassCode"`
SubClassCode int `json:"SubClassCode"`
PcieSlot int `json:"PcieSlot"`
LocString string `json:"LocString"`
PartNumber *string `json:"PartNumber"`
SerialNumber *string `json:"SerialNumber"`
Mac []string `json:"Mac"`
} `json:"PcieInfo"`
}
// ParseAssetJSON parses Inspur asset.json content
func ParseAssetJSON(content []byte) (*models.HardwareConfig, error) {
var asset AssetJSON
if err := json.Unmarshal(content, &asset); err != nil {
return nil, err
}
config := &models.HardwareConfig{}
// Parse version info
for _, v := range asset.VersionInfo {
config.Firmware = append(config.Firmware, models.FirmwareInfo{
DeviceName: v.DeviceName,
Version: v.DeviceRevision,
BuildTime: v.BuildTime,
})
}
// Parse CPU info
seenMicrocode := make(map[string]bool)
for i, cpu := range asset.CpuInfo {
config.CPUs = append(config.CPUs, models.CPU{
Socket: i,
Model: strings.TrimSpace(cpu.ProcessorName),
Cores: cpu.Core,
Threads: cpu.ThreadCount,
FrequencyMHz: cpu.CurrentSpeed,
MaxFreqMHz: cpu.TurboEnableMaxSpeed,
L1CacheKB: cpu.L1Cache,
L2CacheKB: cpu.L2Cache,
L3CacheKB: cpu.L3Cache,
TDP: cpu.CpuTdp,
PPIN: cpu.PPIN,
})
// Add CPU microcode to firmware list (deduplicated)
if cpu.MicroCodeVer != "" && !seenMicrocode[cpu.MicroCodeVer] {
config.Firmware = append(config.Firmware, models.FirmwareInfo{
DeviceName: fmt.Sprintf("CPU%d Microcode", i),
Version: cpu.MicroCodeVer,
})
seenMicrocode[cpu.MicroCodeVer] = true
}
}
// Memory info is parsed from component.log (RESTful Memory info) which has more details
// Only use asset.json memory data as fallback if component.log is not available
if len(asset.MemInfo.MemCommonInfo) > 0 {
common := asset.MemInfo.MemCommonInfo[0]
for i, dimm := range asset.MemInfo.DimmInfo {
slot := fmt.Sprintf("DIMM%d", i)
config.Memory = append(config.Memory, models.MemoryDIMM{
Slot: slot,
Location: slot,
Present: true,
SizeMB: common.PhysicalSize * 1024,
Type: memoryTypeToString(common.MemoryType),
MaxSpeedMHz: common.MaxSpeed,
CurrentSpeedMHz: common.CurrentSpeed,
Manufacturer: common.Manufacturer,
SerialNumber: dimm.SerialNumber,
PartNumber: strings.TrimSpace(dimm.PartNumber),
Ranks: common.Rank,
})
}
}
// Parse storage info
seenHDDFW := make(map[string]bool)
for _, hdd := range asset.HddInfo {
storageType := "HDD"
if hdd.DiskInterfaceType == 5 {
storageType = "NVMe"
} else if hdd.MediaType == 1 {
storageType = "SSD"
}
// Resolve manufacturer: try vendor ID first, then model name extraction
modelName := strings.TrimSpace(hdd.ModelName)
manufacturer := resolveManufacturer(hdd.Manufacturer, modelName)
config.Storage = append(config.Storage, models.Storage{
Slot: hdd.LocationString,
Type: storageType,
Model: modelName,
SizeGB: hdd.Capacity,
SerialNumber: hdd.SerialNumber,
Manufacturer: manufacturer,
Firmware: hdd.FirmwareVersion,
Interface: diskInterfaceToString(hdd.DiskInterfaceType),
})
// Add HDD firmware to firmware list (deduplicated by model+version)
if hdd.FirmwareVersion != "" {
fwKey := modelName + ":" + hdd.FirmwareVersion
if !seenHDDFW[fwKey] {
slot := hdd.LocationString
if slot == "" {
slot = fmt.Sprintf("%s %dGB", storageType, hdd.Capacity)
}
config.Firmware = append(config.Firmware, models.FirmwareInfo{
DeviceName: fmt.Sprintf("%s (%s)", modelName, slot),
Version: hdd.FirmwareVersion,
})
seenHDDFW[fwKey] = true
}
}
}
// Parse PCIe info
for _, pcie := range asset.PcieInfo {
vendor, deviceName := pciids.DeviceInfo(pcie.VendorId, pcie.DeviceId)
device := models.PCIeDevice{
Slot: pcie.LocString,
VendorID: pcie.VendorId,
DeviceID: pcie.DeviceId,
BDF: formatBDF(pcie.BusNumber, pcie.DeviceNumber, pcie.FunctionNumber),
LinkWidth: pcie.NegotiatedLinkWidth,
LinkSpeed: pcieLinkSpeedToString(pcie.CurrentLinkSpeed),
MaxLinkWidth: pcie.MaxLinkWidth,
MaxLinkSpeed: pcieLinkSpeedToString(pcie.MaxLinkSpeed),
DeviceClass: pcieClassToString(pcie.ClassCode, pcie.SubClassCode),
Manufacturer: vendor,
}
if pcie.PartNumber != nil {
device.PartNumber = strings.TrimSpace(*pcie.PartNumber)
}
if pcie.SerialNumber != nil {
device.SerialNumber = strings.TrimSpace(*pcie.SerialNumber)
}
if len(pcie.Mac) > 0 {
device.MACAddresses = pcie.Mac
}
// Use device name from PCI IDs database if available
if deviceName != "" {
device.DeviceClass = deviceName
}
config.PCIeDevices = append(config.PCIeDevices, device)
// Extract GPUs (class 3 = display controller)
if pcie.ClassCode == 3 {
gpuModel := deviceName
if gpuModel == "" {
gpuModel = pcieClassToString(pcie.ClassCode, pcie.SubClassCode)
}
gpu := models.GPU{
Slot: pcie.LocString,
Model: gpuModel,
Manufacturer: vendor,
VendorID: pcie.VendorId,
DeviceID: pcie.DeviceId,
BDF: formatBDF(pcie.BusNumber, pcie.DeviceNumber, pcie.FunctionNumber),
LinkWidth: pcie.NegotiatedLinkWidth,
LinkSpeed: pcieLinkSpeedToString(pcie.CurrentLinkSpeed),
}
if pcie.PartNumber != nil {
gpu.PartNumber = strings.TrimSpace(*pcie.PartNumber)
}
if pcie.SerialNumber != nil {
gpu.SerialNumber = strings.TrimSpace(*pcie.SerialNumber)
}
config.GPUs = append(config.GPUs, gpu)
}
}
return config, nil
}
func memoryTypeToString(memType int) string {
switch memType {
case 26:
return "DDR4"
case 34:
return "DDR5"
default:
return "Unknown"
}
}
func diskInterfaceToString(ifType int) string {
switch ifType {
case 4:
return "SATA"
case 5:
return "NVMe"
case 6:
return "SAS"
default:
return "Unknown"
}
}
func pcieLinkSpeedToString(speed int) string {
switch speed {
case 1:
return "2.5 GT/s"
case 2:
return "5.0 GT/s"
case 3:
return "8.0 GT/s"
case 4:
return "16.0 GT/s"
case 5:
return "32.0 GT/s"
default:
return "Unknown"
}
}
func pcieClassToString(classCode, subClass int) string {
switch classCode {
case 1:
switch subClass {
case 0:
return "SCSI"
case 1:
return "IDE"
case 4:
return "RAID"
case 6:
return "SATA"
case 7:
return "SAS"
case 8:
return "NVMe"
default:
return "Storage"
}
case 2:
return "Network"
case 3:
switch subClass {
case 0:
return "VGA"
case 2:
return "3D Controller"
default:
return "Display"
}
case 4:
return "Multimedia"
case 6:
return "Bridge"
case 12:
return "Serial Bus"
default:
return "Other"
}
}
func formatBDF(bus, dev, fun int) string {
return fmt.Sprintf("%02x:%02x.%x", bus, dev, fun)
}
// resolveManufacturer resolves manufacturer name from various sources
func resolveManufacturer(rawManufacturer, modelName string) string {
raw := strings.TrimSpace(rawManufacturer)
// If it looks like a vendor ID (hex), try to resolve it
if raw != "" {
if name := pciids.VendorNameFromString(raw); name != "" {
return name
}
// If not a vendor ID but looks like a real name (has letters), use it
hasLetter := false
for _, c := range raw {
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') {
hasLetter = true
break
}
}
if hasLetter && len(raw) > 2 {
return raw
}
}
// Try to extract from model name
return extractStorageManufacturer(modelName)
}
// extractStorageManufacturer tries to extract manufacturer from model name
func extractStorageManufacturer(model string) string {
modelUpper := strings.ToUpper(model)
knownVendors := []struct {
prefix string
name string
}{
{"SAMSUNG", "Samsung"},
{"KIOXIA", "KIOXIA"},
{"TOSHIBA", "Toshiba"},
{"WDC", "Western Digital"},
{"WD", "Western Digital"},
{"SEAGATE", "Seagate"},
{"HGST", "HGST"},
{"INTEL", "Intel"},
{"MICRON", "Micron"},
{"KINGSTON", "Kingston"},
{"CRUCIAL", "Crucial"},
{"SK HYNIX", "SK Hynix"},
{"SKHYNIX", "SK Hynix"},
{"SANDISK", "SanDisk"},
{"LITEON", "Lite-On"},
{"PLEXTOR", "Plextor"},
{"ADATA", "ADATA"},
{"TRANSCEND", "Transcend"},
{"CORSAIR", "Corsair"},
{"SOLIDIGM", "Solidigm"},
}
for _, v := range knownVendors {
if strings.HasPrefix(modelUpper, v.prefix) {
return v.name
}
}
return ""
}