feat: add standalone server topology view

This commit is contained in:
Mikhail Chusavitin
2026-09-14 10:29:36 +03:00
parent 5b8be0e464
commit 9ac38c2914
28 changed files with 1287 additions and 31 deletions
+22 -3
View File
@@ -488,7 +488,7 @@ func parsePCIeDeviceView(props map[string]string, result *models.AnalysisResult)
DeviceID: deviceID,
BDF: formatBDF(props["busnumber"], props["devicenumber"], props["functionnumber"]),
Manufacturer: manufacturer,
NUMANode: parseIntLoose(props["cpuaffinity"]),
NUMANode: parseOptionalIntLoose(props["cpuaffinity"]),
Status: normalizeStatus(props["primarystatus"]),
}
result.Hardware.PCIeDevices = append(result.Hardware.PCIeDevices, p)
@@ -535,7 +535,7 @@ func parseNICView(props map[string]string, result *models.AnalysisResult) {
props["controllerbiosversion"],
)),
PortCount: inferPortCountFromFQDD(fqdd),
NUMANode: parseIntLoose(props["cpuaffinity"]),
NUMANode: parseOptionalIntLoose(props["cpuaffinity"]),
Status: normalizeStatus(props["primarystatus"]),
}
if mac != "" {
@@ -589,7 +589,7 @@ func parseControllerView(props map[string]string, result *models.AnalysisResult)
DeviceClass: "storage-controller",
Manufacturer: strings.TrimSpace(firstNonEmpty(props["devicecardmanufacturer"], props["manufacturer"])),
PartNumber: strings.TrimSpace(firstNonEmpty(props["ppid"], props["boardpartnumber"])),
NUMANode: parseIntLoose(props["cpuaffinity"]),
NUMANode: parseOptionalIntLoose(props["cpuaffinity"]),
Status: normalizeStatus(props["primarystatus"]),
})
@@ -983,6 +983,25 @@ func parseIntLoose(v string) int {
return n
}
// parseOptionalIntLoose preserves zero as a real value while keeping missing
// and textual N/A values distinct. CPUAffinity is the motivating field: NUMA
// node 0 is valid, whereas "Not Applicable" means there is no affinity.
func parseOptionalIntLoose(v string) *int {
v = strings.TrimSpace(v)
if v == "" {
return nil
}
num := firstNumberRE.FindString(v)
if num == "" {
return nil
}
n, err := strconv.Atoi(num)
if err != nil {
return nil
}
return &n
}
func parseInt64Loose(v string) int64 {
v = strings.TrimSpace(v)
if v == "" {
+23 -12
View File
@@ -390,7 +390,7 @@ func TestParseDellInfiniBandView(t *testing.T) {
}
// TestParseDellCPUAffinity verifies that CPUAffinity is parsed into NUMANode for
// NIC, PCIe, and controller views. "Not Applicable" must result in NUMANode=0.
// NIC, PCIe, and controller views. Zero is valid; "Not Applicable" is nil.
func TestParseDellCPUAffinity(t *testing.T) {
const viewXML = `<CIM><MESSAGE><SIMPLEREQ>
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_SystemView">
@@ -439,27 +439,38 @@ func TestParseDellCPUAffinity(t *testing.T) {
}
// NIC CPUAffinity=1 → NUMANode=1
nicBySlot := make(map[string]int)
nicBySlot := make(map[string]*int)
for _, nic := range result.Hardware.NetworkAdapters {
nicBySlot[nic.Slot] = nic.NUMANode
}
if nicBySlot["NIC.Slot.2-1-1"] != 1 {
t.Errorf("NIC.Slot.2-1-1 NUMANode = %d, want 1", nicBySlot["NIC.Slot.2-1-1"])
if got := nicBySlot["NIC.Slot.2-1-1"]; got == nil || *got != 1 {
t.Errorf("NIC.Slot.2-1-1 NUMANode = %v, want 1", got)
}
if nicBySlot["InfiniBand.Slot.1-1"] != 2 {
t.Errorf("InfiniBand.Slot.1-1 NUMANode = %d, want 2", nicBySlot["InfiniBand.Slot.1-1"])
if got := nicBySlot["InfiniBand.Slot.1-1"]; got == nil || *got != 2 {
t.Errorf("InfiniBand.Slot.1-1 NUMANode = %v, want 2", got)
}
// PCIe device CPUAffinity=2 → NUMANode=2; controller CPUAffinity="Not Applicable" → NUMANode=0
pcieBySlot := make(map[string]int)
// PCIe device CPUAffinity=2 → NUMANode=2; controller CPUAffinity="Not Applicable" → nil.
pcieBySlot := make(map[string]*int)
for _, pcie := range result.Hardware.PCIeDevices {
pcieBySlot[pcie.Slot] = pcie.NUMANode
}
if pcieBySlot["Slot.7-1"] != 2 {
t.Errorf("Slot.7-1 NUMANode = %d, want 2", pcieBySlot["Slot.7-1"])
if got := pcieBySlot["Slot.7-1"]; got == nil || *got != 2 {
t.Errorf("Slot.7-1 NUMANode = %v, want 2", got)
}
if pcieBySlot["RAID.Slot.1-1"] != 0 {
t.Errorf("RAID.Slot.1-1 NUMANode = %d, want 0 (Not Applicable)", pcieBySlot["RAID.Slot.1-1"])
if got := pcieBySlot["RAID.Slot.1-1"]; got != nil {
t.Errorf("RAID.Slot.1-1 NUMANode = %v, want nil (Not Applicable)", got)
}
}
func TestParseOptionalIntLoosePreservesZero(t *testing.T) {
if got := parseOptionalIntLoose("0"); got == nil || *got != 0 {
t.Fatalf("parseOptionalIntLoose(0) = %v, want pointer to 0", got)
}
for _, value := range []string{"", "N/A", "Not Applicable"} {
if got := parseOptionalIntLoose(value); got != nil {
t.Fatalf("parseOptionalIntLoose(%q) = %v, want nil", value, got)
}
}
}