fix(parser): dedupe HPE AHS DIMM inventory per slot, newest snapshot wins
The AHS blackbox stores a DIMM inventory snapshot per POST cycle and parseDIMMs flattens all of them. dedupeMemory keyed on serial, so a slot whose module was swapped between captures survived once per historical occupant — a 24-slot board reported 26 modules and "same P/N" vs "mixed P/N" configs looked identical. dedupeMemory now collapses to one entry per non-empty slot with the last (most recent, since token order follows chronological blackbox-record order) occurrence winning. Slotless entries keep the serial/part fallback. See ADL-060. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
99d8f098ac
commit
632067fef5
+20
-1
@@ -1045,11 +1045,30 @@ func dedupeCPUs(items []models.CPU) []models.CPU {
|
||||
return out
|
||||
}
|
||||
|
||||
// dedupeMemory collapses the DIMM inventory to one entry per physical slot.
|
||||
//
|
||||
// The AHS blackbox carries a separate inventory snapshot for every POST cycle,
|
||||
// and parseDIMMs walks all of them. When a module is swapped between captures the
|
||||
// same slot shows up with different serials; keying the dedupe on serial alone
|
||||
// (as an earlier version did) let every historical occupant survive, inflating
|
||||
// the module count. Tokens are appended in blackbox-record order, which is
|
||||
// chronological, so the last occurrence for a slot is the most recent snapshot
|
||||
// and wins. Entries without a slot label fall back to serial-based dedupe.
|
||||
func dedupeMemory(items []models.MemoryDIMM) []models.MemoryDIMM {
|
||||
slotIndex := make(map[string]int)
|
||||
seen := make(map[string]bool)
|
||||
out := make([]models.MemoryDIMM, 0, len(items))
|
||||
for _, item := range items {
|
||||
key := valueOr(item.SerialNumber, item.Slot+"|"+item.PartNumber)
|
||||
if item.Slot != "" {
|
||||
if idx, ok := slotIndex[item.Slot]; ok {
|
||||
out[idx] = item
|
||||
continue
|
||||
}
|
||||
slotIndex[item.Slot] = len(out)
|
||||
out = append(out, item)
|
||||
continue
|
||||
}
|
||||
key := valueOr(item.SerialNumber, item.PartNumber)
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
"git.mchus.pro/mchus/logpile/internal/parser"
|
||||
)
|
||||
|
||||
@@ -153,6 +154,34 @@ func TestParseAHSInventory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDedupeMemorySlotSwap(t *testing.T) {
|
||||
// The AHS blackbox keeps one inventory snapshot per POST cycle. When a module
|
||||
// is swapped between captures the same slot appears with different serials, in
|
||||
// chronological (token stream) order. dedupeMemory must keep one entry per
|
||||
// slot, with the most recent snapshot winning.
|
||||
items := []models.MemoryDIMM{
|
||||
{Slot: "PROC 1 DIMM 1", PartNumber: "HMCG94AHBRA480N", SerialNumber: "AAA1"},
|
||||
{Slot: "PROC 1 DIMM 7", PartNumber: "HMCG94AHBRA487N", SerialNumber: "OLD7"},
|
||||
{Slot: "PROC 1 DIMM 1", PartNumber: "HMCG94AHBRA480N", SerialNumber: "AAA1"}, // repeated snapshot
|
||||
{Slot: "PROC 1 DIMM 7", PartNumber: "HMCG94AHBRA480N", SerialNumber: "NEW7"}, // module swapped
|
||||
}
|
||||
|
||||
out := dedupeMemory(items)
|
||||
if len(out) != 2 {
|
||||
t.Fatalf("expected 2 DIMMs (one per slot), got %d: %+v", len(out), out)
|
||||
}
|
||||
bySlot := map[string]models.MemoryDIMM{}
|
||||
for _, d := range out {
|
||||
bySlot[d.Slot] = d
|
||||
}
|
||||
if got := bySlot["PROC 1 DIMM 7"].SerialNumber; got != "NEW7" {
|
||||
t.Fatalf("expected newest occupant NEW7 for slot 7, got %q", got)
|
||||
}
|
||||
if got := bySlot["PROC 1 DIMM 1"].SerialNumber; got != "AAA1" {
|
||||
t.Fatalf("unexpected serial for slot 1: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAHSTruncatedEntry(t *testing.T) {
|
||||
p := &Parser{}
|
||||
// Build archive where the last entry's declared size exceeds available data.
|
||||
|
||||
Reference in New Issue
Block a user