feat(models): resolve CPU serial from source PPIN as common fallback

Centralize CPU identity in models.ResolveCPUSerialNumber: an explicit
source serial wins, otherwise a valid source PPIN is used, placeholders
rejected. Dell, H3C and Redfish apply it while parsing; canonical-device
and Reanimator conversion apply it again at the output boundary. No
identity is synthesized from socket/model/board serial.

See ADL-058.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-08-27 17:05:40 +03:00
co-authored by Claude Sonnet 5
parent 9d701885da
commit 07c270cda2
13 changed files with 122 additions and 19 deletions
+4 -2
View File
@@ -19,7 +19,7 @@ import (
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
)
const parserVersion = "3.0"
const parserVersion = "3.1"
func init() {
parser.Register(&Parser{})
@@ -323,6 +323,7 @@ func parseCPUView(props map[string]string, result *models.AnalysisResult) {
if model == "" {
return
}
ppin := strings.TrimSpace(props["ppin"])
cpu := models.CPU{
Socket: parseSocketFromFQDD(firstNonEmpty(props["fqdd"], props["instanceid"])),
Model: model,
@@ -331,7 +332,8 @@ func parseCPUView(props map[string]string, result *models.AnalysisResult) {
Threads: parseIntLoose(props["numberofenabledthreads"]),
FrequencyMHz: parseIntLoose(props["currentclockspeed"]),
MaxFreqMHz: parseIntLoose(props["maxclockspeed"]),
PPIN: strings.TrimSpace(props["ppin"]),
PPIN: ppin,
SerialNumber: models.ResolveCPUSerialNumber(props["serialnumber"], ppin),
Status: normalizeStatus(props["primarystatus"]),
}
result.Hardware.CPUs = append(result.Hardware.CPUs, cpu)
+3
View File
@@ -152,6 +152,9 @@ func TestParseNestedTSRZip(t *testing.T) {
if got := result.Hardware.CPUs[0].Model; got != "Intel(R) Xeon(R) Gold 6330" {
t.Fatalf("unexpected cpu model: %q", got)
}
if got := result.Hardware.CPUs[0].SerialNumber; got != "ABCD" {
t.Fatalf("expected CPU serial fallback from PPIN, got %q", got)
}
if len(result.Hardware.NetworkAdapters) != 1 {
t.Fatalf("expected 1 network adapter, got %d", len(result.Hardware.NetworkAdapters))
+9 -6
View File
@@ -20,8 +20,8 @@ import (
)
const (
parserVersionG5 = "2.1"
parserVersionG6 = "2.1"
parserVersionG5 = "2.2"
parserVersionG6 = "2.2"
)
func init() {
@@ -506,6 +506,8 @@ func parseHardwareInfoNetworkAdapters(content []byte) []models.NetworkAdapter {
func parseCPUFromHardwareInfoSection(sectionName string, section map[string]string) models.CPU {
socket := parseSectionIndex(sectionName, `(?i)processor\s+(\d+)`)
statusRaw := getSectionValue(section, "Status")
ppin := getSectionValue(section, "CPU PPIN", "PPIN")
serial := getSectionValue(section, "Processor ID", "Serial Number")
return models.CPU{
Socket: socket,
@@ -516,8 +518,8 @@ func parseCPUFromHardwareInfoSection(sectionName string, section map[string]stri
L1CacheKB: parseMaybeIntLoose(getSectionValue(section, "L1 Cache")),
L2CacheKB: parseMaybeIntLoose(getSectionValue(section, "L2 Cache")),
L3CacheKB: parseMaybeIntLoose(getSectionValue(section, "L3 Cache")),
PPIN: getSectionValue(section, "CPU PPIN", "PPIN"),
SerialNumber: getSectionValue(section, "Processor ID", "Serial Number"),
PPIN: ppin,
SerialNumber: models.ResolveCPUSerialNumber(serial, ppin),
Status: normalizeComponentStatus(statusRaw),
}
}
@@ -1503,6 +1505,7 @@ func parseCPUXML(content []byte, result *models.AnalysisResult) {
}
socket := parseSocketID(node.XMLName.Local)
ppin := strings.TrimSpace(fields["PPIN"])
cpu := models.CPU{
Socket: socket,
Model: model,
@@ -1511,8 +1514,8 @@ func parseCPUXML(content []byte, result *models.AnalysisResult) {
Threads: parseMaybeInt(fields["TotalThreads"]),
FrequencyMHz: parseMaybeInt(fields["ProcessorSpeed"]),
MaxFreqMHz: parseMaybeInt(fields["ProcessorMaxSpeed"]),
SerialNumber: strings.TrimSpace(fields["SerialNumber"]),
PPIN: strings.TrimSpace(fields["PPIN"]),
SerialNumber: models.ResolveCPUSerialNumber(fields["SerialNumber"], ppin),
PPIN: ppin,
Status: normalizePresenceStatus(fields["Status"]),
}
+3
View File
@@ -831,6 +831,9 @@ Pre-Init,Info,0x0,Management Subsystem Health,Health,Assertion event,Pre-Init,"M
if result.Hardware.CPUs[0].FrequencyMHz != 2800 {
t.Fatalf("expected CPU frequency 2800MHz, got %d", result.Hardware.CPUs[0].FrequencyMHz)
}
if got := result.Hardware.CPUs[0].SerialNumber; got != "49-A9-50-C0-15-9F-2D-DC" {
t.Fatalf("expected CPU serial fallback from PPIN, got %q", got)
}
if len(result.Hardware.Memory) != 2 {
t.Fatalf("expected 2 DIMMs from hardware_info.ini, got %d", len(result.Hardware.Memory))