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 "" }