diff --git a/internal/parser/vendors/inspur/component.go b/internal/parser/vendors/inspur/component.go index a5d2841..dd4f40d 100644 --- a/internal/parser/vendors/inspur/component.go +++ b/internal/parser/vendors/inspur/component.go @@ -19,6 +19,11 @@ func ParseComponentLog(content []byte, hw *models.HardwareConfig) { text := string(content) + // Parse RESTful CPU info — fallback when asset.json is absent + if len(hw.CPUs) == 0 { + parseCPUInfo(text, hw) + } + // Parse RESTful Memory info (detailed memory data) parseMemoryInfo(text, hw) @@ -61,6 +66,70 @@ func ParseComponentLogSensors(content []byte) []models.SensorReading { return out } +// CPURESTInfo represents the RESTful CPU info structure in component.log +type CPURESTInfo struct { + Processors []struct { + ProcID int `json:"proc_id"` + CPUID string `json:"PROC_ID"` // uppercase key — prevents case-insensitive collision with proc_id + Manufacturer string `json:"Manufacturer"` + MaxSpeedMHz int `json:"MaxSpeedMHz"` + ConfigStatus int `json:"configStatus"` + ProcName string `json:"proc_name"` + ProcStatus int `json:"proc_status"` + ProcSpeed int `json:"proc_speed"` + CoreCount int `json:"proc_core_count"` + ThreadCount int `json:"proc_thread_count"` + TDP int `json:"proc_tdp"` + L1Cache int `json:"proc_l1cache_size"` + L2Cache int `json:"proc_l2cache_size"` + L3Cache int `json:"proc_l3cache_size"` + MicroCode string `json:"micro_code"` + PPIN string `json:"ppin"` + Status string `json:"status"` + } `json:"processors"` +} + +func parseCPUInfo(text string, hw *models.HardwareConfig) { + re := regexp.MustCompile(`RESTful CPU info:\s*(\{[\s\S]*?\})\s*RESTful Memory`) + match := re.FindStringSubmatch(text) + if match == nil { + return + } + + jsonStr := strings.ReplaceAll(match[1], "\n", "") + var cpuInfo CPURESTInfo + if err := json.Unmarshal([]byte(jsonStr), &cpuInfo); err != nil { + return + } + + seenMicrocode := make(map[string]bool) + for _, proc := range cpuInfo.Processors { + if proc.ProcStatus != 1 && proc.ConfigStatus != 1 { + continue + } + hw.CPUs = append(hw.CPUs, models.CPU{ + Socket: proc.ProcID, + Model: strings.TrimSpace(proc.ProcName), + Cores: proc.CoreCount, + Threads: proc.ThreadCount, + FrequencyMHz: proc.ProcSpeed, + MaxFreqMHz: proc.MaxSpeedMHz, + L1CacheKB: proc.L1Cache, + L2CacheKB: proc.L2Cache, + L3CacheKB: proc.L3Cache, + TDP: proc.TDP, + PPIN: proc.PPIN, + }) + if proc.MicroCode != "" && !seenMicrocode[proc.MicroCode] { + hw.Firmware = append(hw.Firmware, models.FirmwareInfo{ + DeviceName: fmt.Sprintf("CPU%d Microcode", proc.ProcID), + Version: proc.MicroCode, + }) + seenMicrocode[proc.MicroCode] = true + } + } +} + // MemoryRESTInfo represents the RESTful Memory info structure type MemoryRESTInfo struct { MemModules []struct { @@ -112,9 +181,10 @@ func parseMemoryInfo(text string, hw *models.HardwareConfig) { } for _, mem := range memInfo.MemModules { item := models.MemoryDIMM{ - Slot: mem.MemModSlot, - Location: mem.MemModSlot, - Present: mem.MemModStatus == 1 && mem.MemModSize > 0, + Slot: mem.MemModSlot, + Location: mem.MemModSlot, + // status=1 with a known serial/part is definitely present even if BMC reports size=0 + Present: mem.MemModStatus == 1 && (mem.MemModSize > 0 || strings.TrimSpace(mem.MemModSerial) != "" || strings.TrimSpace(mem.MemModPartNum) != ""), SizeMB: mem.MemModSize * 1024, // Convert GB to MB Type: mem.MemModType, Technology: strings.TrimSpace(mem.MemModTechnology), diff --git a/internal/parser/vendors/inspur/cpu_mem_fix_test.go b/internal/parser/vendors/inspur/cpu_mem_fix_test.go new file mode 100644 index 0000000..2f14858 --- /dev/null +++ b/internal/parser/vendors/inspur/cpu_mem_fix_test.go @@ -0,0 +1,80 @@ +package inspur + +import ( + "strings" + "testing" + + "git.mchus.pro/mchus/logpile/internal/models" +) + +const cpuMemComponentLog = `RESTful version info: +[] +RESTful CPU info: +{ "processors": [ { "proc_id": 0, "PROC_ID": "A6-06-06-00-FF-FB-EB-BF", "InstructionSet": "x86-64", "Manufacturer": "Intel(R) Corporation", "MaxSpeedMHz": 3100, "configStatus": 1, "proc_name": "Intel(R) Xeon(R) Gold 6330 CPU @ 2.00GHz", "proc_status": 1, "proc_speed": 2000, "proc_core_count": 28, "proc_used_core_count": 28, "proc_thread_count": 56, "proc_tdp": 205, "proc_l1cache_size": 80, "proc_l2cache_size": 1280, "proc_l3cache_size": 43008, "micro_code": "0x0D000410", "ppin": "47149E2253E81688", "status": "OK" }, { "proc_id": 1, "PROC_ID": "A6-06-06-00-FF-FB-EB-BF", "InstructionSet": "x86-64", "Manufacturer": "Intel(R) Corporation", "MaxSpeedMHz": 3100, "configStatus": 1, "proc_name": "Intel(R) Xeon(R) Gold 6330 CPU @ 2.00GHz", "proc_status": 1, "proc_speed": 2000, "proc_core_count": 28, "proc_thread_count": 56, "proc_tdp": 205, "proc_l1cache_size": 80, "proc_l2cache_size": 1280, "proc_l3cache_size": 43008, "micro_code": "0x0D000410", "ppin": "475AC1221D41F557", "status": "OK" } ] } +RESTful Memory info: +{ "mem_modules": [ { "mem_mod_id": 0, "config_status": 1, "mem_mod_slot": "CPU0_C0D0", "mem_mod_status": 1, "mem_mod_size": 32, "mem_mod_type": "DDR4", "mem_mod_technology": "Synchronous", "mem_mod_frequency": 3200, "mem_mod_current_frequency": 2933, "mem_mod_vendor": "Samsung", "mem_mod_part_num": "M393A4K40EB3-CWE", "mem_mod_serial_num": "S1440202433526FC12", "mem_mod_ranks": 2, "status": "OK" }, { "mem_mod_id": 16, "config_status": 1, "mem_mod_slot": "CPU1_C0D0", "mem_mod_status": 1, "mem_mod_size": 0, "mem_mod_type": "DDR4", "mem_mod_technology": "Synchronous", "mem_mod_frequency": 3200, "mem_mod_current_frequency": 2933, "mem_mod_vendor": "Samsung", "mem_mod_part_num": "M393A4K40EB3-CWE", "mem_mod_serial_num": "K0UX000401205D2037", "mem_mod_ranks": 2, "status": "OK" } ], "total_memory_count": 2, "present_memory_count": 2, "mem_total_mem_size": 32 } +RESTful HDD info: +[] +RESTful PSU info: +{ "power_supplies": [] } +RESTful Network Adapter info: +{ "sys_adapters": [] } +RESTful fan info: +{ "fans": [] } +RESTful diskbackplane info: +[] +BMC done +` + +func TestParseCPUInfo_FromComponentLog(t *testing.T) { + hw := &models.HardwareConfig{} + ParseComponentLog([]byte(cpuMemComponentLog), hw) + + if len(hw.CPUs) != 2 { + t.Fatalf("expected 2 CPUs, got %d", len(hw.CPUs)) + } + if !strings.Contains(hw.CPUs[0].Model, "Gold 6330") { + t.Errorf("unexpected CPU model: %s", hw.CPUs[0].Model) + } + if hw.CPUs[0].Cores != 28 { + t.Errorf("expected 28 cores, got %d", hw.CPUs[0].Cores) + } + if hw.CPUs[0].PPIN != "47149E2253E81688" { + t.Errorf("unexpected PPIN: %s", hw.CPUs[0].PPIN) + } + if hw.CPUs[1].PPIN != "475AC1221D41F557" { + t.Errorf("unexpected CPU1 PPIN: %s", hw.CPUs[1].PPIN) + } +} + +func TestParseMemoryInfo_PresentWithZeroSize(t *testing.T) { + hw := &models.HardwareConfig{} + ParseComponentLog([]byte(cpuMemComponentLog), hw) + + presentCount := 0 + for _, m := range hw.Memory { + if m.Present { + presentCount++ + } + } + if presentCount != 2 { + t.Errorf("expected 2 present DIMMs, got %d", presentCount) + } + + // Find CPU1_C0D0 (size=0 but serial present) + found := false + for _, m := range hw.Memory { + if m.Slot == "CPU1_C0D0" { + found = true + if !m.Present { + t.Error("CPU1_C0D0 should be Present=true despite size=0") + } + if m.SerialNumber != "K0UX000401205D2037" { + t.Errorf("wrong serial: %s", m.SerialNumber) + } + } + } + if !found { + t.Error("CPU1_C0D0 not found in memory list") + } +} diff --git a/internal/parser/vendors/inspur/parser.go b/internal/parser/vendors/inspur/parser.go index 1c7a993..764dfa0 100644 --- a/internal/parser/vendors/inspur/parser.go +++ b/internal/parser/vendors/inspur/parser.go @@ -16,7 +16,7 @@ import ( // parserVersion - version of this parser module // IMPORTANT: Increment this version when making changes to parser logic! -const parserVersion = "1.8" +const parserVersion = "1.9" func init() { parser.Register(&Parser{})