fix: parse Inspur PCIe CPU affinity
This commit is contained in:
+25
-20
@@ -40,6 +40,9 @@ func ParseComponentLog(content []byte, hw *models.HardwareConfig) {
|
||||
// Parse RESTful Network Adapter info
|
||||
parseNetworkAdapterInfo(text, hw)
|
||||
|
||||
// Combined component.log dumps may be the only PCIe inventory source.
|
||||
hw.PCIeDevices = MergePCIeDevices(hw.PCIeDevices, ParsePCIeDevices(content))
|
||||
|
||||
// Extract firmware from all components
|
||||
extractComponentFirmware(text, hw)
|
||||
}
|
||||
@@ -517,25 +520,26 @@ type NetworkAdapterRESTInfo struct {
|
||||
}
|
||||
|
||||
type sysAdapter struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Location string `json:"Location"`
|
||||
Present int `json:"present"`
|
||||
Slot int `json:"slot"`
|
||||
PcieBus int `json:"pcie_bus"`
|
||||
PcieDev int `json:"pcie_dev"`
|
||||
PcieFunc int `json:"pcie_func"`
|
||||
VendorID int `json:"vendor_id"`
|
||||
DeviceID int `json:"device_id"`
|
||||
Vendor string `json:"vendor"`
|
||||
Model string `json:"model"`
|
||||
FwVer string `json:"fw_ver"`
|
||||
Status string `json:"status"`
|
||||
SN string `json:"sn"`
|
||||
PN string `json:"pn"`
|
||||
PortNum int `json:"port_num"`
|
||||
PortType string `json:"port_type"`
|
||||
Ports []struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Location string `json:"Location"`
|
||||
DeviceLocator string `json:"location"`
|
||||
Present int `json:"present"`
|
||||
Slot int `json:"slot"`
|
||||
PcieBus int `json:"pcie_bus"`
|
||||
PcieDev int `json:"pcie_dev"`
|
||||
PcieFunc int `json:"pcie_func"`
|
||||
VendorID int `json:"vendor_id"`
|
||||
DeviceID int `json:"device_id"`
|
||||
Vendor string `json:"vendor"`
|
||||
Model string `json:"model"`
|
||||
FwVer string `json:"fw_ver"`
|
||||
Status string `json:"status"`
|
||||
SN string `json:"sn"`
|
||||
PN string `json:"pn"`
|
||||
PortNum int `json:"port_num"`
|
||||
PortType string `json:"port_type"`
|
||||
Ports []struct {
|
||||
ID int `json:"id"`
|
||||
MacAddr string `json:"mac_addr"`
|
||||
} `json:"ports"`
|
||||
@@ -579,7 +583,7 @@ func parseNetworkAdapterInfo(text string, hw *models.HardwareConfig) {
|
||||
}
|
||||
|
||||
base := models.NetworkAdapter{
|
||||
Location: adapter.Location,
|
||||
Location: inspurFirstNonEmpty(adapter.Location, adapter.DeviceLocator),
|
||||
Present: adapter.Present == 1,
|
||||
Model: model,
|
||||
Vendor: vendor,
|
||||
@@ -591,6 +595,7 @@ func parseNetworkAdapterInfo(text string, hw *models.HardwareConfig) {
|
||||
PortCount: adapter.PortNum,
|
||||
PortType: adapter.PortType,
|
||||
Status: adapter.Status,
|
||||
NUMANode: parseInspurCPUAffinity(adapter.Location, adapter.DeviceLocator),
|
||||
}
|
||||
|
||||
// If another source (asset.json PcieInfo) already recorded this card's PCI
|
||||
|
||||
@@ -49,6 +49,27 @@ RESTful fan`
|
||||
if got.Vendor == "" {
|
||||
t.Fatalf("expected NIC vendor resolved from pci.ids")
|
||||
}
|
||||
if got.NUMANode == nil || *got.NUMANode != 0 {
|
||||
t.Fatalf("expected CPU affinity 0, got %v", got.NUMANode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseComponentLog_AddsPCIeInventoryAndAffinity(t *testing.T) {
|
||||
text := `RESTful PCIE Device info:
|
||||
[{"id":1,"present":1,"vendor_id":6987,"vendor_name":"Marvell","device_id":37424,"device_name":"Marvell 9230 Raid","bus_num":101,"dev_num":0,"func_num":0,"max_link_width":2,"max_link_speed":2,"current_link_width":2,"current_link_speed":2,"location":"#CPU0_PE3_P4_R4_SL1","DeviceLocator":"CPU0_PE3_PCIE4","dev_type":1,"dev_subtype":6}]
|
||||
RESTful Network Adapter info:
|
||||
{"sys_adapters":[]}
|
||||
RESTful fan info:
|
||||
{"fans":[]}`
|
||||
|
||||
hw := &models.HardwareConfig{}
|
||||
ParseComponentLog([]byte(text), hw)
|
||||
if len(hw.PCIeDevices) != 1 {
|
||||
t.Fatalf("expected 1 PCIe device, got %d", len(hw.PCIeDevices))
|
||||
}
|
||||
if got := hw.PCIeDevices[0].NUMANode; got == nil || *got != 0 {
|
||||
t.Fatalf("expected CPU affinity 0, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkAdapterInfo_MergesIntoExistingInventory(t *testing.T) {
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
// parserVersion - version of this parser module
|
||||
// IMPORTANT: Increment this version when making changes to parser logic!
|
||||
const parserVersion = "2.6"
|
||||
const parserVersion = "3.0"
|
||||
|
||||
func init() {
|
||||
parser.Register(&Parser{})
|
||||
|
||||
+29
-31
@@ -65,20 +65,15 @@ func ParsePCIeSlotDeviceNames(content []byte) map[int]string {
|
||||
func parsePCIeRESTJSON(content []byte) (PCIeRESTInfo, bool) {
|
||||
text := string(content)
|
||||
startMarker := "RESTful PCIE Device info:"
|
||||
endMarker := "BMC sdr Info:"
|
||||
|
||||
startIdx := strings.Index(text, startMarker)
|
||||
if startIdx == -1 {
|
||||
return nil, false
|
||||
}
|
||||
endIdx := strings.Index(text[startIdx:], endMarker)
|
||||
if endIdx == -1 {
|
||||
endIdx = len(text) - startIdx
|
||||
}
|
||||
jsonText := strings.TrimSpace(text[startIdx+len(startMarker) : startIdx+endIdx])
|
||||
jsonText := strings.TrimSpace(text[startIdx+len(startMarker):])
|
||||
|
||||
var info PCIeRESTInfo
|
||||
if err := json.Unmarshal([]byte(jsonText), &info); err != nil {
|
||||
if err := json.NewDecoder(strings.NewReader(jsonText)).Decode(&info); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return info, true
|
||||
@@ -118,27 +113,8 @@ func ParsePCIeNVMeLocToSlot(content []byte) map[int]int {
|
||||
|
||||
// ParsePCIeDevices parses RESTful PCIE Device info from devicefrusdr.log
|
||||
func ParsePCIeDevices(content []byte) []models.PCIeDevice {
|
||||
text := string(content)
|
||||
|
||||
// Find RESTful PCIE Device info section
|
||||
startMarker := "RESTful PCIE Device info:"
|
||||
endMarker := "BMC sdr Info:"
|
||||
|
||||
startIdx := strings.Index(text, startMarker)
|
||||
if startIdx == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
endIdx := strings.Index(text[startIdx:], endMarker)
|
||||
if endIdx == -1 {
|
||||
endIdx = len(text) - startIdx
|
||||
}
|
||||
|
||||
jsonText := text[startIdx+len(startMarker) : startIdx+endIdx]
|
||||
jsonText = strings.TrimSpace(jsonText)
|
||||
|
||||
var pcieInfo PCIeRESTInfo
|
||||
if err := json.Unmarshal([]byte(jsonText), &pcieInfo); err != nil {
|
||||
pcieInfo, ok := parsePCIeRESTJSON(content)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -156,8 +132,9 @@ func ParsePCIeDevices(content []byte) []models.PCIeDevice {
|
||||
deviceClass := determineDeviceClass(pcie.DevType, pcie.DevSubtype, pcie.DeviceName)
|
||||
_, pciDeviceName := pciids.DeviceInfo(pcie.VendorID, pcie.DeviceID)
|
||||
|
||||
// Build BDF string in canonical form (bb:dd.f)
|
||||
bdf := formatBDF(pcie.BusNum, pcie.DevNum, pcie.FuncNum)
|
||||
// Use the domain-qualified BDF as the canonical slot identity. Keep the
|
||||
// physical locator as descriptive evidence for affinity parsing.
|
||||
bdf := fullBDF(pcie.BusNum, pcie.DevNum, pcie.FuncNum)
|
||||
|
||||
partNumber := strings.TrimSpace(pcie.PartNum)
|
||||
if partNumber == "" {
|
||||
@@ -177,7 +154,8 @@ func ParsePCIeDevices(content []byte) []models.PCIeDevice {
|
||||
}
|
||||
|
||||
device := models.PCIeDevice{
|
||||
Slot: pcie.Location,
|
||||
Slot: bdf,
|
||||
Description: strings.TrimSpace(pcie.Location),
|
||||
VendorID: pcie.VendorID,
|
||||
DeviceID: pcie.DeviceID,
|
||||
BDF: bdf,
|
||||
@@ -190,6 +168,7 @@ func ParsePCIeDevices(content []byte) []models.PCIeDevice {
|
||||
PartNumber: partNumber,
|
||||
SerialNumber: strings.TrimSpace(pcie.SerialNum),
|
||||
Status: pcieRESTStatus(pcie.Status),
|
||||
NUMANode: parseInspurCPUAffinity(pcie.Location, pcie.DeviceLocator),
|
||||
}
|
||||
|
||||
devices = append(devices, device)
|
||||
@@ -198,6 +177,22 @@ func ParsePCIeDevices(content []byte) []models.PCIeDevice {
|
||||
return devices
|
||||
}
|
||||
|
||||
var inspurCPUAffinityRegex = regexp.MustCompile(`(?i)(?:^|[^a-z0-9])CPU(\d+)(?:[^0-9]|$)`)
|
||||
|
||||
func parseInspurCPUAffinity(locations ...string) *int {
|
||||
for _, location := range locations {
|
||||
match := inspurCPUAffinityRegex.FindStringSubmatch(strings.TrimSpace(location))
|
||||
if match == nil {
|
||||
continue
|
||||
}
|
||||
value, err := strconv.Atoi(match[1])
|
||||
if err == nil {
|
||||
return &value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pcieRESTStatus maps the RESTful PCIE Device info "status" flag (1 = OK) to
|
||||
// the shared status vocabulary. Only the observed OK case is mapped — the
|
||||
// meaning of other values isn't confirmed in the source, so it's left
|
||||
@@ -329,6 +324,9 @@ func enrichPCIeDevice(dst *models.PCIeDevice, src models.PCIeDevice) {
|
||||
if dst.Present == nil {
|
||||
dst.Present = src.Present
|
||||
}
|
||||
if dst.NUMANode == nil {
|
||||
dst.NUMANode = src.NUMANode
|
||||
}
|
||||
if strings.TrimSpace(dst.Status) == "" {
|
||||
dst.Status = src.Status
|
||||
}
|
||||
|
||||
+39
-2
@@ -19,8 +19,34 @@ BMC sdr Info:`)
|
||||
if devices[0].PartNumber != "I350T4V2" {
|
||||
t.Fatalf("expected part/model I350T4V2, got %q", devices[0].PartNumber)
|
||||
}
|
||||
if devices[0].BDF != "45:00.0" {
|
||||
t.Fatalf("expected BDF 45:00.0, got %q", devices[0].BDF)
|
||||
if devices[0].BDF != "0000:45:00.0" || devices[0].Slot != devices[0].BDF {
|
||||
t.Fatalf("expected canonical BDF slot 0000:45:00.0, got slot=%q bdf=%q", devices[0].Slot, devices[0].BDF)
|
||||
}
|
||||
if devices[0].NUMANode == nil || *devices[0].NUMANode != 0 {
|
||||
t.Fatalf("expected CPU affinity 0, got %v", devices[0].NUMANode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePCIeDevices_ReadsAffinityFromCombinedComponentLog(t *testing.T) {
|
||||
content := []byte(`RESTful PCIE Device info:
|
||||
[{"id":3,"present":1,"vendor_id":5555,"vendor_name":"NVIDIA","device_id":4125,"device_name":"MCX623106AN-CDAT","bus_num":177,"dev_num":0,"func_num":0,"max_link_width":16,"max_link_speed":4,"current_link_width":16,"current_link_speed":4,"location":"Riser 3 Slot 1","DeviceLocator":"CPU1_PE1_PCIE1","dev_type":2,"dev_subtype":0}]
|
||||
RESTful Network Adapter info:
|
||||
{"sys_adapters":[]}`)
|
||||
|
||||
devices := ParsePCIeDevices(content)
|
||||
if len(devices) != 1 {
|
||||
t.Fatalf("expected 1 device, got %d", len(devices))
|
||||
}
|
||||
if devices[0].NUMANode == nil || *devices[0].NUMANode != 1 {
|
||||
t.Fatalf("expected CPU affinity 1 from DeviceLocator, got %v", devices[0].NUMANode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInspurCPUAffinity_UnknownRemainsNil(t *testing.T) {
|
||||
for _, location := range []string{"", "N/A", "Riser 2 Slot 1", "SCPU1_SLOT"} {
|
||||
if got := parseInspurCPUAffinity(location); got != nil {
|
||||
t.Fatalf("parseInspurCPUAffinity(%q) = %v, want nil", location, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +82,17 @@ func TestMergePCIeDevices_EnrichesGenericAssetEntry(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergePCIeDevices_EnrichesNUMAAffinity(t *testing.T) {
|
||||
node := 1
|
||||
got := MergePCIeDevices(
|
||||
[]models.PCIeDevice{{BDF: "98:00.0"}},
|
||||
[]models.PCIeDevice{{BDF: "98:00.0", NUMANode: &node}},
|
||||
)
|
||||
if len(got) != 1 || got[0].NUMANode == nil || *got[0].NUMANode != 1 {
|
||||
t.Fatalf("expected merged NUMA affinity 1, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePCIeDevices_ResolvesModelFromPCIIDsWhenDeviceNameIsRawHex(t *testing.T) {
|
||||
content := []byte(`RESTful PCIE Device info:
|
||||
[{"id":5,"present":1,"vendor_id":36869,"vendor_name":"","device_id":655,"device_name":"0x028F","bus_num":152,"dev_num":0,"func_num":0,"max_link_width":8,"max_link_speed":3,"current_link_width":8,"current_link_speed":3,"location":"#CPU1_PCIE9","dev_type":1,"dev_subtype":7,"part_num":"","serial_num":"","fw_ver":""}]
|
||||
|
||||
Reference in New Issue
Block a user