fix(inspur): parse CPUs from component.log and fix DIMM present detection
Two bugs in onekeylog archives that lack asset.json: - CPU count was always 0: ParseComponentLog never parsed the "RESTful CPU info" section. Added parseCPUInfo as a fallback when hw.CPUs is empty (asset.json remains the primary source when present). Also worked around a Go JSON case-insensitive collision between "proc_id" (int) and "PROC_ID" (string CPUID) by adding an explicit PROC_ID field with an exact-case tag. - Only 1 of 2 DIMMs shown: Present condition required mem_mod_size > 0, but some BMC firmware reports size=0 for a physically installed module while still providing serial and part number. Now treats a DIMM as present when status=1 and any of size/serial/partnum is non-empty. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
76
internal/parser/vendors/inspur/component.go
vendored
76
internal/parser/vendors/inspur/component.go
vendored
@@ -19,6 +19,11 @@ func ParseComponentLog(content []byte, hw *models.HardwareConfig) {
|
|||||||
|
|
||||||
text := string(content)
|
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)
|
// Parse RESTful Memory info (detailed memory data)
|
||||||
parseMemoryInfo(text, hw)
|
parseMemoryInfo(text, hw)
|
||||||
|
|
||||||
@@ -61,6 +66,70 @@ func ParseComponentLogSensors(content []byte) []models.SensorReading {
|
|||||||
return out
|
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
|
// MemoryRESTInfo represents the RESTful Memory info structure
|
||||||
type MemoryRESTInfo struct {
|
type MemoryRESTInfo struct {
|
||||||
MemModules []struct {
|
MemModules []struct {
|
||||||
@@ -112,9 +181,10 @@ func parseMemoryInfo(text string, hw *models.HardwareConfig) {
|
|||||||
}
|
}
|
||||||
for _, mem := range memInfo.MemModules {
|
for _, mem := range memInfo.MemModules {
|
||||||
item := models.MemoryDIMM{
|
item := models.MemoryDIMM{
|
||||||
Slot: mem.MemModSlot,
|
Slot: mem.MemModSlot,
|
||||||
Location: mem.MemModSlot,
|
Location: mem.MemModSlot,
|
||||||
Present: mem.MemModStatus == 1 && mem.MemModSize > 0,
|
// 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
|
SizeMB: mem.MemModSize * 1024, // Convert GB to MB
|
||||||
Type: mem.MemModType,
|
Type: mem.MemModType,
|
||||||
Technology: strings.TrimSpace(mem.MemModTechnology),
|
Technology: strings.TrimSpace(mem.MemModTechnology),
|
||||||
|
|||||||
80
internal/parser/vendors/inspur/cpu_mem_fix_test.go
vendored
Normal file
80
internal/parser/vendors/inspur/cpu_mem_fix_test.go
vendored
Normal file
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
2
internal/parser/vendors/inspur/parser.go
vendored
2
internal/parser/vendors/inspur/parser.go
vendored
@@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
// parserVersion - version of this parser module
|
// parserVersion - version of this parser module
|
||||||
// IMPORTANT: Increment this version when making changes to parser logic!
|
// IMPORTANT: Increment this version when making changes to parser logic!
|
||||||
const parserVersion = "1.8"
|
const parserVersion = "1.9"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
parser.Register(&Parser{})
|
parser.Register(&Parser{})
|
||||||
|
|||||||
Reference in New Issue
Block a user