feat: improve inspur parsing and pci.ids integration
This commit is contained in:
69
internal/parser/vendors/inspur/asset.go
vendored
69
internal/parser/vendors/inspur/asset.go
vendored
@@ -3,12 +3,15 @@ package inspur
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
|
||||
)
|
||||
|
||||
var rawHexPCIDeviceRegex = regexp.MustCompile(`(?i)^0x[0-9a-f]+$`)
|
||||
|
||||
// AssetJSON represents the structure of Inspur asset.json file
|
||||
type AssetJSON struct {
|
||||
VersionInfo []struct {
|
||||
@@ -207,8 +210,8 @@ func ParseAssetJSON(content []byte) (*models.HardwareConfig, error) {
|
||||
VendorID: pcie.VendorId,
|
||||
DeviceID: pcie.DeviceId,
|
||||
BDF: formatBDF(pcie.BusNumber, pcie.DeviceNumber, pcie.FunctionNumber),
|
||||
LinkWidth: pcie.NegotiatedLinkWidth,
|
||||
LinkSpeed: pcieLinkSpeedToString(pcie.CurrentLinkSpeed),
|
||||
LinkWidth: pcie.NegotiatedLinkWidth,
|
||||
LinkSpeed: pcieLinkSpeedToString(pcie.CurrentLinkSpeed),
|
||||
MaxLinkWidth: pcie.MaxLinkWidth,
|
||||
MaxLinkSpeed: pcieLinkSpeedToString(pcie.MaxLinkSpeed),
|
||||
DeviceClass: pcieClassToString(pcie.ClassCode, pcie.SubClassCode),
|
||||
@@ -225,25 +228,22 @@ func ParseAssetJSON(content []byte) (*models.HardwareConfig, error) {
|
||||
}
|
||||
// Use device name from PCI IDs database if available
|
||||
if deviceName != "" {
|
||||
device.DeviceClass = deviceName
|
||||
device.DeviceClass = normalizeModelLabel(deviceName)
|
||||
}
|
||||
config.PCIeDevices = append(config.PCIeDevices, device)
|
||||
|
||||
// Extract GPUs (class 3 = display controller)
|
||||
if pcie.ClassCode == 3 {
|
||||
gpuModel := deviceName
|
||||
if gpuModel == "" {
|
||||
gpuModel = pcieClassToString(pcie.ClassCode, pcie.SubClassCode)
|
||||
}
|
||||
gpuModel := normalizeGPUModel(pcie.VendorId, pcie.DeviceId, deviceName, pcie.ClassCode, pcie.SubClassCode)
|
||||
gpu := models.GPU{
|
||||
Slot: pcie.LocString,
|
||||
Model: gpuModel,
|
||||
Manufacturer: vendor,
|
||||
VendorID: pcie.VendorId,
|
||||
DeviceID: pcie.DeviceId,
|
||||
BDF: formatBDF(pcie.BusNumber, pcie.DeviceNumber, pcie.FunctionNumber),
|
||||
CurrentLinkWidth: pcie.NegotiatedLinkWidth,
|
||||
CurrentLinkSpeed: pcieLinkSpeedToString(pcie.CurrentLinkSpeed),
|
||||
Slot: pcie.LocString,
|
||||
Model: gpuModel,
|
||||
Manufacturer: vendor,
|
||||
VendorID: pcie.VendorId,
|
||||
DeviceID: pcie.DeviceId,
|
||||
BDF: formatBDF(pcie.BusNumber, pcie.DeviceNumber, pcie.FunctionNumber),
|
||||
CurrentLinkWidth: pcie.NegotiatedLinkWidth,
|
||||
CurrentLinkSpeed: pcieLinkSpeedToString(pcie.CurrentLinkSpeed),
|
||||
MaxLinkWidth: pcie.MaxLinkWidth,
|
||||
MaxLinkSpeed: pcieLinkSpeedToString(pcie.MaxLinkSpeed),
|
||||
}
|
||||
@@ -260,6 +260,45 @@ func ParseAssetJSON(content []byte) (*models.HardwareConfig, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func normalizeModelLabel(v string) string {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(strings.Fields(v), " ")
|
||||
}
|
||||
|
||||
func normalizeGPUModel(vendorID, deviceID int, model string, classCode, subClass int) string {
|
||||
model = normalizeModelLabel(model)
|
||||
|
||||
if model == "" || rawHexPCIDeviceRegex.MatchString(model) || isGenericGPUModelLabel(model) {
|
||||
if pciModel := normalizeModelLabel(pciids.DeviceName(vendorID, deviceID)); pciModel != "" {
|
||||
model = pciModel
|
||||
}
|
||||
}
|
||||
|
||||
if model == "" || isGenericGPUModelLabel(model) {
|
||||
model = pcieClassToString(classCode, subClass)
|
||||
}
|
||||
|
||||
// Last fallback for unknown NVIDIA display devices: expose PCI DeviceID
|
||||
// instead of generic "3D Controller".
|
||||
if (model == "" || strings.EqualFold(model, "3D Controller")) && vendorID == 0x10de && deviceID > 0 {
|
||||
return fmt.Sprintf("0x%04X", deviceID)
|
||||
}
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
func isGenericGPUModelLabel(model string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(model)) {
|
||||
case "", "gpu", "display", "display controller", "vga", "3d controller", "other", "unknown":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func memoryTypeToString(memType int) string {
|
||||
switch memType {
|
||||
case 26:
|
||||
|
||||
48
internal/parser/vendors/inspur/asset_gpu_model_test.go
vendored
Normal file
48
internal/parser/vendors/inspur/asset_gpu_model_test.go
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package inspur
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseAssetJSON_NVIDIAGPUModelFromPCIIDs(t *testing.T) {
|
||||
raw := []byte(`{
|
||||
"VersionInfo": [],
|
||||
"CpuInfo": [],
|
||||
"MemInfo": {"MemCommonInfo": [], "DimmInfo": []},
|
||||
"HddInfo": [],
|
||||
"PcieInfo": [{
|
||||
"VendorId": 4318,
|
||||
"DeviceId": 9019,
|
||||
"BusNumber": 12,
|
||||
"DeviceNumber": 0,
|
||||
"FunctionNumber": 0,
|
||||
"MaxLinkWidth": 16,
|
||||
"MaxLinkSpeed": 5,
|
||||
"NegotiatedLinkWidth": 16,
|
||||
"CurrentLinkSpeed": 5,
|
||||
"ClassCode": 3,
|
||||
"SubClassCode": 2,
|
||||
"PcieSlot": 11,
|
||||
"LocString": "#CPU0_PCIE2",
|
||||
"PartNumber": null,
|
||||
"SerialNumber": null,
|
||||
"Mac": []
|
||||
}]
|
||||
}`)
|
||||
|
||||
hw, err := ParseAssetJSON(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAssetJSON failed: %v", err)
|
||||
}
|
||||
if len(hw.GPUs) != 1 {
|
||||
t.Fatalf("expected 1 GPU, got %d", len(hw.GPUs))
|
||||
}
|
||||
if hw.GPUs[0].Model != "GH100 [H200 NVL]" {
|
||||
t.Fatalf("expected model GH100 [H200 NVL], got %q", hw.GPUs[0].Model)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeGPUModel_FallbackToDeviceIDForUnknownNVIDIA(t *testing.T) {
|
||||
got := normalizeGPUModel(0x10de, 0xbeef, "0xBEEF\t", 3, 2)
|
||||
if got != "0xBEEF" {
|
||||
t.Fatalf("expected 0xBEEF, got %q", got)
|
||||
}
|
||||
}
|
||||
90
internal/parser/vendors/inspur/component.go
vendored
90
internal/parser/vendors/inspur/component.go
vendored
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
|
||||
)
|
||||
|
||||
// ParseComponentLog parses component.log file and extracts detailed hardware info
|
||||
@@ -52,20 +53,20 @@ func ParseComponentLogEvents(content []byte) []models.Event {
|
||||
// MemoryRESTInfo represents the RESTful Memory info structure
|
||||
type MemoryRESTInfo struct {
|
||||
MemModules []struct {
|
||||
MemModID int `json:"mem_mod_id"`
|
||||
ConfigStatus int `json:"config_status"`
|
||||
MemModSlot string `json:"mem_mod_slot"`
|
||||
MemModStatus int `json:"mem_mod_status"`
|
||||
MemModSize int `json:"mem_mod_size"`
|
||||
MemModType string `json:"mem_mod_type"`
|
||||
MemModTechnology string `json:"mem_mod_technology"`
|
||||
MemModFrequency int `json:"mem_mod_frequency"`
|
||||
MemModCurrentFreq int `json:"mem_mod_current_frequency"`
|
||||
MemModVendor string `json:"mem_mod_vendor"`
|
||||
MemModPartNum string `json:"mem_mod_part_num"`
|
||||
MemModSerial string `json:"mem_mod_serial_num"`
|
||||
MemModRanks int `json:"mem_mod_ranks"`
|
||||
Status string `json:"status"`
|
||||
MemModID int `json:"mem_mod_id"`
|
||||
ConfigStatus int `json:"config_status"`
|
||||
MemModSlot string `json:"mem_mod_slot"`
|
||||
MemModStatus int `json:"mem_mod_status"`
|
||||
MemModSize int `json:"mem_mod_size"`
|
||||
MemModType string `json:"mem_mod_type"`
|
||||
MemModTechnology string `json:"mem_mod_technology"`
|
||||
MemModFrequency int `json:"mem_mod_frequency"`
|
||||
MemModCurrentFreq int `json:"mem_mod_current_frequency"`
|
||||
MemModVendor string `json:"mem_mod_vendor"`
|
||||
MemModPartNum string `json:"mem_mod_part_num"`
|
||||
MemModSerial string `json:"mem_mod_serial_num"`
|
||||
MemModRanks int `json:"mem_mod_ranks"`
|
||||
Status string `json:"status"`
|
||||
} `json:"mem_modules"`
|
||||
TotalMemoryCount int `json:"total_memory_count"`
|
||||
PresentMemoryCount int `json:"present_memory_count"`
|
||||
@@ -112,21 +113,21 @@ func parseMemoryInfo(text string, hw *models.HardwareConfig) {
|
||||
// PSURESTInfo represents the RESTful PSU info structure
|
||||
type PSURESTInfo struct {
|
||||
PowerSupplies []struct {
|
||||
ID int `json:"id"`
|
||||
Present int `json:"present"`
|
||||
VendorID string `json:"vendor_id"`
|
||||
Model string `json:"model"`
|
||||
SerialNum string `json:"serial_num"`
|
||||
PartNum string `json:"part_num"`
|
||||
FwVer string `json:"fw_ver"`
|
||||
InputType string `json:"input_type"`
|
||||
Status string `json:"status"`
|
||||
RatedPower int `json:"rated_power"`
|
||||
PSInPower int `json:"ps_in_power"`
|
||||
PSOutPower int `json:"ps_out_power"`
|
||||
PSInVolt float64 `json:"ps_in_volt"`
|
||||
PSOutVolt float64 `json:"ps_out_volt"`
|
||||
PSUMaxTemp int `json:"psu_max_temperature"`
|
||||
ID int `json:"id"`
|
||||
Present int `json:"present"`
|
||||
VendorID string `json:"vendor_id"`
|
||||
Model string `json:"model"`
|
||||
SerialNum string `json:"serial_num"`
|
||||
PartNum string `json:"part_num"`
|
||||
FwVer string `json:"fw_ver"`
|
||||
InputType string `json:"input_type"`
|
||||
Status string `json:"status"`
|
||||
RatedPower int `json:"rated_power"`
|
||||
PSInPower int `json:"ps_in_power"`
|
||||
PSOutPower int `json:"ps_out_power"`
|
||||
PSInVolt float64 `json:"ps_in_volt"`
|
||||
PSOutVolt float64 `json:"ps_out_volt"`
|
||||
PSUMaxTemp int `json:"psu_max_temperature"`
|
||||
} `json:"power_supplies"`
|
||||
PresentPowerReading int `json:"present_power_reading"`
|
||||
}
|
||||
@@ -304,17 +305,28 @@ func parseNetworkAdapterInfo(text string, hw *models.HardwareConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
model := normalizeModelLabel(adapter.Model)
|
||||
if model == "" || looksLikeRawDeviceID(model) {
|
||||
if resolved := normalizeModelLabel(pciids.DeviceName(adapter.VendorID, adapter.DeviceID)); resolved != "" {
|
||||
model = resolved
|
||||
}
|
||||
}
|
||||
vendor := normalizeModelLabel(adapter.Vendor)
|
||||
if vendor == "" {
|
||||
vendor = normalizeModelLabel(pciids.VendorName(adapter.VendorID))
|
||||
}
|
||||
|
||||
hw.NetworkAdapters = append(hw.NetworkAdapters, models.NetworkAdapter{
|
||||
Slot: fmt.Sprintf("Slot %d", adapter.Slot),
|
||||
Location: adapter.Location,
|
||||
Present: adapter.Present == 1,
|
||||
Model: strings.TrimSpace(adapter.Model),
|
||||
Vendor: strings.TrimSpace(adapter.Vendor),
|
||||
Model: model,
|
||||
Vendor: vendor,
|
||||
VendorID: adapter.VendorID,
|
||||
DeviceID: adapter.DeviceID,
|
||||
SerialNumber: strings.TrimSpace(adapter.SN),
|
||||
PartNumber: strings.TrimSpace(adapter.PN),
|
||||
Firmware: adapter.FwVer,
|
||||
SerialNumber: normalizeRedisValue(adapter.SN),
|
||||
PartNumber: normalizeRedisValue(adapter.PN),
|
||||
Firmware: normalizeRedisValue(adapter.FwVer),
|
||||
PortCount: adapter.PortNum,
|
||||
PortType: adapter.PortType,
|
||||
MACAddresses: macs,
|
||||
@@ -323,6 +335,16 @@ func parseNetworkAdapterInfo(text string, hw *models.HardwareConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
var rawDeviceIDLikeRegex = regexp.MustCompile(`(?i)^(?:0x)?[0-9a-f]{3,4}$`)
|
||||
|
||||
func looksLikeRawDeviceID(v string) bool {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return true
|
||||
}
|
||||
return rawDeviceIDLikeRegex.MatchString(v)
|
||||
}
|
||||
|
||||
func parseMemoryEvents(text string) []models.Event {
|
||||
var events []models.Event
|
||||
|
||||
|
||||
52
internal/parser/vendors/inspur/component_test.go
vendored
Normal file
52
internal/parser/vendors/inspur/component_test.go
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package inspur
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
)
|
||||
|
||||
func TestParseNetworkAdapterInfo_ResolvesModelFromPCIIDsForRawHexModel(t *testing.T) {
|
||||
text := `RESTful Network Adapter info:
|
||||
{
|
||||
"sys_adapters": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "NIC1",
|
||||
"Location": "#CPU0_PCIE4",
|
||||
"present": 1,
|
||||
"slot": 4,
|
||||
"vendor_id": 32902,
|
||||
"device_id": 5409,
|
||||
"vendor": "",
|
||||
"model": "0x1521",
|
||||
"fw_ver": "",
|
||||
"status": "OK",
|
||||
"sn": "",
|
||||
"pn": "",
|
||||
"port_num": 4,
|
||||
"port_type": "Base-T",
|
||||
"ports": []
|
||||
}
|
||||
]
|
||||
}
|
||||
RESTful fan`
|
||||
|
||||
hw := &models.HardwareConfig{}
|
||||
parseNetworkAdapterInfo(text, hw)
|
||||
|
||||
if len(hw.NetworkAdapters) != 1 {
|
||||
t.Fatalf("expected 1 network adapter, got %d", len(hw.NetworkAdapters))
|
||||
}
|
||||
got := hw.NetworkAdapters[0]
|
||||
if got.Model == "" {
|
||||
t.Fatalf("expected NIC model resolved from pci.ids, got empty")
|
||||
}
|
||||
if !strings.Contains(strings.ToUpper(got.Model), "I350") {
|
||||
t.Fatalf("expected I350 in model, got %q", got.Model)
|
||||
}
|
||||
if got.Vendor == "" {
|
||||
t.Fatalf("expected NIC vendor resolved from pci.ids")
|
||||
}
|
||||
}
|
||||
17
internal/parser/vendors/inspur/parser.go
vendored
17
internal/parser/vendors/inspur/parser.go
vendored
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
// parserVersion - version of this parser module
|
||||
// IMPORTANT: Increment this version when making changes to parser logic!
|
||||
const parserVersion = "1.1.0"
|
||||
const parserVersion = "1.2.1"
|
||||
|
||||
func init() {
|
||||
parser.Register(&Parser{})
|
||||
@@ -125,6 +125,12 @@ func (p *Parser) Parse(files []parser.ExtractedFile) (*models.AnalysisResult, er
|
||||
result.Events = append(result.Events, componentEvents...)
|
||||
}
|
||||
|
||||
// Enrich runtime component data from Redis snapshot (serials, FW, telemetry),
|
||||
// when text logs miss these fields.
|
||||
if f := parser.FindFileByName(files, "redis-dump.rdb"); f != nil && result.Hardware != nil {
|
||||
enrichFromRedisDump(f.Content, result.Hardware)
|
||||
}
|
||||
|
||||
// Parse IDL-like logs (plain and structured JSON logs with embedded IDL messages)
|
||||
idlFiles := parser.FindFileByPattern(files, "/idl.log", "idl_json.log", "run_json.log")
|
||||
for _, f := range idlFiles {
|
||||
@@ -199,14 +205,9 @@ func (p *Parser) parseDeviceFruSDR(content []byte, result *models.AnalysisResult
|
||||
// This supplements data from asset.json with serial numbers, firmware, etc.
|
||||
pcieDevicesFromREST := ParsePCIeDevices(content)
|
||||
|
||||
// Merge PCIe data: keep asset.json data but add RESTful data if available
|
||||
// Merge PCIe data: asset.json is the base inventory, RESTful data enriches names/links/serials.
|
||||
if result.Hardware != nil {
|
||||
// If asset.json didn't have PCIe devices, use RESTful data
|
||||
if len(result.Hardware.PCIeDevices) == 0 && len(pcieDevicesFromREST) > 0 {
|
||||
result.Hardware.PCIeDevices = pcieDevicesFromREST
|
||||
}
|
||||
// If we have both, merge them (RESTful data takes precedence for detailed info)
|
||||
// For now, we keep asset.json data which has more details
|
||||
result.Hardware.PCIeDevices = MergePCIeDevices(result.Hardware.PCIeDevices, pcieDevicesFromREST)
|
||||
}
|
||||
|
||||
// Parse GPU devices and add temperature data from sensors
|
||||
|
||||
217
internal/parser/vendors/inspur/pcie.go
vendored
217
internal/parser/vendors/inspur/pcie.go
vendored
@@ -3,36 +3,38 @@ package inspur
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
|
||||
)
|
||||
|
||||
// PCIeRESTInfo represents the RESTful PCIE Device info structure
|
||||
type PCIeRESTInfo []struct {
|
||||
ID int `json:"id"`
|
||||
Present int `json:"present"`
|
||||
Enable int `json:"enable"`
|
||||
Status int `json:"status"`
|
||||
VendorID int `json:"vendor_id"`
|
||||
VendorName string `json:"vendor_name"`
|
||||
DeviceID int `json:"device_id"`
|
||||
DeviceName string `json:"device_name"`
|
||||
BusNum int `json:"bus_num"`
|
||||
DevNum int `json:"dev_num"`
|
||||
FuncNum int `json:"func_num"`
|
||||
MaxLinkWidth int `json:"max_link_width"`
|
||||
MaxLinkSpeed int `json:"max_link_speed"`
|
||||
CurrentLinkWidth int `json:"current_link_width"`
|
||||
CurrentLinkSpeed int `json:"current_link_speed"`
|
||||
Slot int `json:"slot"`
|
||||
Location string `json:"location"`
|
||||
DeviceLocator string `json:"DeviceLocator"`
|
||||
DevType int `json:"dev_type"`
|
||||
DevSubtype int `json:"dev_subtype"`
|
||||
PartNum string `json:"part_num"`
|
||||
SerialNum string `json:"serial_num"`
|
||||
FwVer string `json:"fw_ver"`
|
||||
ID int `json:"id"`
|
||||
Present int `json:"present"`
|
||||
Enable int `json:"enable"`
|
||||
Status int `json:"status"`
|
||||
VendorID int `json:"vendor_id"`
|
||||
VendorName string `json:"vendor_name"`
|
||||
DeviceID int `json:"device_id"`
|
||||
DeviceName string `json:"device_name"`
|
||||
BusNum int `json:"bus_num"`
|
||||
DevNum int `json:"dev_num"`
|
||||
FuncNum int `json:"func_num"`
|
||||
MaxLinkWidth int `json:"max_link_width"`
|
||||
MaxLinkSpeed int `json:"max_link_speed"`
|
||||
CurrentLinkWidth int `json:"current_link_width"`
|
||||
CurrentLinkSpeed int `json:"current_link_speed"`
|
||||
Slot int `json:"slot"`
|
||||
Location string `json:"location"`
|
||||
DeviceLocator string `json:"DeviceLocator"`
|
||||
DevType int `json:"dev_type"`
|
||||
DevSubtype int `json:"dev_subtype"`
|
||||
PartNum string `json:"part_num"`
|
||||
SerialNum string `json:"serial_num"`
|
||||
FwVer string `json:"fw_ver"`
|
||||
}
|
||||
|
||||
// ParsePCIeDevices parses RESTful PCIE Device info from devicefrusdr.log
|
||||
@@ -73,9 +75,27 @@ func ParsePCIeDevices(content []byte) []models.PCIeDevice {
|
||||
|
||||
// Determine device class based on dev_type
|
||||
deviceClass := determineDeviceClass(pcie.DevType, pcie.DevSubtype, pcie.DeviceName)
|
||||
_, pciDeviceName := pciids.DeviceInfo(pcie.VendorID, pcie.DeviceID)
|
||||
|
||||
// Build BDF string
|
||||
bdf := fmt.Sprintf("%04x/%02x/%02x/%02x", 0, pcie.BusNum, pcie.DevNum, pcie.FuncNum)
|
||||
// Build BDF string in canonical form (bb:dd.f)
|
||||
bdf := formatBDF(pcie.BusNum, pcie.DevNum, pcie.FuncNum)
|
||||
|
||||
partNumber := strings.TrimSpace(pcie.PartNum)
|
||||
if partNumber == "" {
|
||||
partNumber = sanitizePCIeDeviceName(pcie.DeviceName)
|
||||
}
|
||||
if partNumber == "" {
|
||||
partNumber = normalizeModelLabel(pciDeviceName)
|
||||
}
|
||||
if isGenericPCIeClass(deviceClass) {
|
||||
if resolved := normalizeModelLabel(pciDeviceName); resolved != "" {
|
||||
deviceClass = resolved
|
||||
}
|
||||
}
|
||||
manufacturer := strings.TrimSpace(pcie.VendorName)
|
||||
if manufacturer == "" {
|
||||
manufacturer = normalizeModelLabel(pciids.VendorName(pcie.VendorID))
|
||||
}
|
||||
|
||||
device := models.PCIeDevice{
|
||||
Slot: pcie.Location,
|
||||
@@ -83,12 +103,12 @@ func ParsePCIeDevices(content []byte) []models.PCIeDevice {
|
||||
DeviceID: pcie.DeviceID,
|
||||
BDF: bdf,
|
||||
DeviceClass: deviceClass,
|
||||
Manufacturer: pcie.VendorName,
|
||||
Manufacturer: manufacturer,
|
||||
LinkWidth: pcie.CurrentLinkWidth,
|
||||
LinkSpeed: currentSpeed,
|
||||
MaxLinkWidth: pcie.MaxLinkWidth,
|
||||
MaxLinkSpeed: maxSpeed,
|
||||
PartNumber: strings.TrimSpace(pcie.PartNum),
|
||||
PartNumber: partNumber,
|
||||
SerialNumber: strings.TrimSpace(pcie.SerialNum),
|
||||
}
|
||||
|
||||
@@ -98,6 +118,149 @@ func ParsePCIeDevices(content []byte) []models.PCIeDevice {
|
||||
return devices
|
||||
}
|
||||
|
||||
var rawHexDeviceNameRegex = regexp.MustCompile(`(?i)^0x[0-9a-f]+$`)
|
||||
|
||||
func sanitizePCIeDeviceName(name string) string {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.EqualFold(name, "N/A") {
|
||||
return ""
|
||||
}
|
||||
if rawHexDeviceNameRegex.MatchString(name) {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// MergePCIeDevices enriches base devices (from asset.json) with detailed RESTful PCIe data.
|
||||
// Matching is done by BDF first, then by slot fallback.
|
||||
func MergePCIeDevices(base []models.PCIeDevice, rest []models.PCIeDevice) []models.PCIeDevice {
|
||||
if len(rest) == 0 {
|
||||
return base
|
||||
}
|
||||
if len(base) == 0 {
|
||||
return append([]models.PCIeDevice(nil), rest...)
|
||||
}
|
||||
|
||||
type ref struct {
|
||||
index int
|
||||
}
|
||||
byBDF := make(map[string]ref, len(base))
|
||||
bySlot := make(map[string]ref, len(base))
|
||||
|
||||
for i := range base {
|
||||
bdf := normalizePCIeBDF(base[i].BDF)
|
||||
if bdf != "" {
|
||||
byBDF[bdf] = ref{index: i}
|
||||
}
|
||||
slot := strings.ToLower(strings.TrimSpace(base[i].Slot))
|
||||
if slot != "" {
|
||||
bySlot[slot] = ref{index: i}
|
||||
}
|
||||
}
|
||||
|
||||
for _, detailed := range rest {
|
||||
idx := -1
|
||||
if bdf := normalizePCIeBDF(detailed.BDF); bdf != "" {
|
||||
if found, ok := byBDF[bdf]; ok {
|
||||
idx = found.index
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
slot := strings.ToLower(strings.TrimSpace(detailed.Slot))
|
||||
if slot != "" {
|
||||
if found, ok := bySlot[slot]; ok {
|
||||
idx = found.index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if idx == -1 {
|
||||
base = append(base, detailed)
|
||||
newIdx := len(base) - 1
|
||||
if bdf := normalizePCIeBDF(detailed.BDF); bdf != "" {
|
||||
byBDF[bdf] = ref{index: newIdx}
|
||||
}
|
||||
if slot := strings.ToLower(strings.TrimSpace(detailed.Slot)); slot != "" {
|
||||
bySlot[slot] = ref{index: newIdx}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
enrichPCIeDevice(&base[idx], detailed)
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
func enrichPCIeDevice(dst *models.PCIeDevice, src models.PCIeDevice) {
|
||||
if dst == nil {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(dst.Slot) == "" {
|
||||
dst.Slot = src.Slot
|
||||
}
|
||||
if strings.TrimSpace(dst.BDF) == "" {
|
||||
dst.BDF = src.BDF
|
||||
}
|
||||
if dst.VendorID == 0 {
|
||||
dst.VendorID = src.VendorID
|
||||
}
|
||||
if dst.DeviceID == 0 {
|
||||
dst.DeviceID = src.DeviceID
|
||||
}
|
||||
if strings.TrimSpace(dst.Manufacturer) == "" {
|
||||
dst.Manufacturer = src.Manufacturer
|
||||
}
|
||||
if strings.TrimSpace(dst.SerialNumber) == "" {
|
||||
dst.SerialNumber = src.SerialNumber
|
||||
}
|
||||
if strings.TrimSpace(dst.PartNumber) == "" {
|
||||
dst.PartNumber = src.PartNumber
|
||||
}
|
||||
if strings.TrimSpace(dst.LinkSpeed) == "" || strings.EqualFold(strings.TrimSpace(dst.LinkSpeed), "unknown") {
|
||||
dst.LinkSpeed = src.LinkSpeed
|
||||
}
|
||||
if strings.TrimSpace(dst.MaxLinkSpeed) == "" || strings.EqualFold(strings.TrimSpace(dst.MaxLinkSpeed), "unknown") {
|
||||
dst.MaxLinkSpeed = src.MaxLinkSpeed
|
||||
}
|
||||
if dst.LinkWidth == 0 {
|
||||
dst.LinkWidth = src.LinkWidth
|
||||
}
|
||||
if dst.MaxLinkWidth == 0 {
|
||||
dst.MaxLinkWidth = src.MaxLinkWidth
|
||||
}
|
||||
if isGenericPCIeClass(dst.DeviceClass) && !isGenericPCIeClass(src.DeviceClass) {
|
||||
dst.DeviceClass = src.DeviceClass
|
||||
}
|
||||
}
|
||||
|
||||
func normalizePCIeBDF(bdf string) string {
|
||||
bdf = strings.TrimSpace(strings.ToLower(bdf))
|
||||
if bdf == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if strings.Contains(bdf, "/") {
|
||||
parts := strings.Split(bdf, "/")
|
||||
if len(parts) == 4 {
|
||||
return fmt.Sprintf("%s:%s.%s", parts[1], parts[2], parts[3])
|
||||
}
|
||||
}
|
||||
return bdf
|
||||
}
|
||||
|
||||
func isGenericPCIeClass(class string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(class)) {
|
||||
case "", "unknown", "other", "bridge", "network", "storage", "sas", "sata", "display", "vga", "3d controller", "serial bus":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// determineDeviceClass maps device type to human-readable class
|
||||
func determineDeviceClass(devType, devSubtype int, deviceName string) string {
|
||||
// dev_type mapping:
|
||||
|
||||
77
internal/parser/vendors/inspur/pcie_test.go
vendored
Normal file
77
internal/parser/vendors/inspur/pcie_test.go
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package inspur
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
)
|
||||
|
||||
func TestParsePCIeDevices_UsesDeviceNameAsModelWhenPartNumberMissing(t *testing.T) {
|
||||
content := []byte(`RESTful PCIE Device info:
|
||||
[{"id":1,"present":1,"vendor_id":32902,"vendor_name":"Intel","device_id":5409,"device_name":"I350T4V2","bus_num":69,"dev_num":0,"func_num":0,"max_link_width":4,"max_link_speed":2,"current_link_width":4,"current_link_speed":2,"location":"#CPU0_PCIE4","dev_type":2,"dev_subtype":0,"part_num":"","serial_num":"","fw_ver":""}]
|
||||
BMC sdr Info:`)
|
||||
|
||||
devices := ParsePCIeDevices(content)
|
||||
if len(devices) != 1 {
|
||||
t.Fatalf("expected 1 device, got %d", len(devices))
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergePCIeDevices_EnrichesGenericAssetEntry(t *testing.T) {
|
||||
base := []models.PCIeDevice{
|
||||
{
|
||||
Slot: "#CPU1_PCIE9",
|
||||
BDF: "98:00.0",
|
||||
VendorID: 0x9005,
|
||||
DeviceID: 0x028f,
|
||||
DeviceClass: "SAS",
|
||||
Manufacturer: "Adaptec / Microsemi",
|
||||
},
|
||||
}
|
||||
rest := []models.PCIeDevice{
|
||||
{
|
||||
Slot: "#CPU1_PCIE9",
|
||||
BDF: "98:00.0",
|
||||
VendorID: 0x9005,
|
||||
DeviceID: 0x028f,
|
||||
DeviceClass: "Storage Controller",
|
||||
Manufacturer: "Microchip",
|
||||
PartNumber: "PM8222-SHBA",
|
||||
},
|
||||
}
|
||||
|
||||
got := MergePCIeDevices(base, rest)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("expected 1 merged device, got %d", len(got))
|
||||
}
|
||||
if got[0].PartNumber != "PM8222-SHBA" {
|
||||
t.Fatalf("expected merged part number PM8222-SHBA, got %q", got[0].PartNumber)
|
||||
}
|
||||
}
|
||||
|
||||
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":""}]
|
||||
BMC sdr Info:`)
|
||||
|
||||
devices := ParsePCIeDevices(content)
|
||||
if len(devices) != 1 {
|
||||
t.Fatalf("expected 1 device, got %d", len(devices))
|
||||
}
|
||||
if devices[0].PartNumber == "" {
|
||||
t.Fatalf("expected part number resolved from pci.ids, got empty")
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(devices[0].PartNumber)), "0x") {
|
||||
t.Fatalf("expected resolved name instead of raw hex, got %q", devices[0].PartNumber)
|
||||
}
|
||||
if devices[0].Manufacturer == "" {
|
||||
t.Fatalf("expected manufacturer resolved from pci.ids")
|
||||
}
|
||||
}
|
||||
559
internal/parser/vendors/inspur/redis_dump.go
vendored
Normal file
559
internal/parser/vendors/inspur/redis_dump.go
vendored
Normal file
@@ -0,0 +1,559 @@
|
||||
package inspur
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
)
|
||||
|
||||
var (
|
||||
reRedisGPUKey = regexp.MustCompile(`GPUInfo:REDIS_GPUINFO_T([0-9]+):([A-Za-z0-9_]+)`)
|
||||
reRedisNICKey = regexp.MustCompile(`RedisNicInfo:redis_nic_info_t:stNicDeviceInfo([0-9]+):([A-Za-z0-9_]+)`)
|
||||
reRedisRAIDSerial = regexp.MustCompile(`RAIDMSCCInfo:redis_pcie_mscc_raid_info_t([0-9]+):RAIDInfo:SerialNum`)
|
||||
reRedisPCIESNPN = regexp.MustCompile(`AssetInfoPCIE:SNPN([0-9]+):(SN|PN)`)
|
||||
)
|
||||
|
||||
type redisGPUSnapshot struct {
|
||||
ByIndex map[int]map[string]string
|
||||
}
|
||||
|
||||
type redisNICSnapshot struct {
|
||||
ByIndex map[int]map[string]string
|
||||
}
|
||||
|
||||
type redisPCIESerialSnapshot struct {
|
||||
ByPart map[string]string
|
||||
}
|
||||
|
||||
func enrichFromRedisDump(content []byte, hw *models.HardwareConfig) {
|
||||
if hw == nil || len(content) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
gpuSnap := parseRedisGPUSnapshot(content)
|
||||
nicSnap := parseRedisNICSnapshot(content)
|
||||
raidSerials := parseRedisRAIDSerials(content)
|
||||
pcieSnap := parseRedisPCIESerialSnapshot(content)
|
||||
|
||||
applyRedisGPUEnrichment(hw, gpuSnap)
|
||||
applyRedisNICEnrichment(hw, nicSnap)
|
||||
applyRedisPCIESNPNEnrichment(hw, pcieSnap)
|
||||
applyRedisPCIeEnrichment(hw, raidSerials)
|
||||
}
|
||||
|
||||
func parseRedisRAIDSerials(content []byte) []string {
|
||||
matches := reRedisRAIDSerial.FindAllSubmatchIndex(content, -1)
|
||||
if len(matches) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
seen := make(map[string]bool, len(matches))
|
||||
serials := make([]string, 0, len(matches))
|
||||
for _, m := range matches {
|
||||
if len(m) < 4 {
|
||||
continue
|
||||
}
|
||||
value := normalizeRedisValue(extractRedisCandidateValue(content, m[1]))
|
||||
if value == "" || seen[value] {
|
||||
continue
|
||||
}
|
||||
seen[value] = true
|
||||
serials = append(serials, value)
|
||||
}
|
||||
return serials
|
||||
}
|
||||
|
||||
func parseRedisPCIESerialSnapshot(content []byte) redisPCIESerialSnapshot {
|
||||
type rec struct {
|
||||
PN string
|
||||
SN string
|
||||
}
|
||||
tmp := make(map[int]rec)
|
||||
|
||||
matches := reRedisPCIESNPN.FindAllSubmatchIndex(content, -1)
|
||||
for _, m := range matches {
|
||||
if len(m) < 6 {
|
||||
continue
|
||||
}
|
||||
idxStr := string(content[m[2]:m[3]])
|
||||
field := string(content[m[4]:m[5]])
|
||||
idx, err := strconv.Atoi(idxStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
value := normalizeRedisValue(extractRedisCandidateValue(content, m[1]))
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
r := tmp[idx]
|
||||
if field == "PN" {
|
||||
r.PN = value
|
||||
} else if field == "SN" {
|
||||
r.SN = value
|
||||
}
|
||||
tmp[idx] = r
|
||||
}
|
||||
|
||||
out := redisPCIESerialSnapshot{ByPart: make(map[string]string)}
|
||||
for _, r := range tmp {
|
||||
pn := normalizeRedisValue(r.PN)
|
||||
sn := normalizeRedisValue(r.SN)
|
||||
if pn == "" || sn == "" {
|
||||
continue
|
||||
}
|
||||
out.ByPart[strings.ToLower(strings.TrimSpace(pn))] = sn
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseRedisGPUSnapshot(content []byte) redisGPUSnapshot {
|
||||
snap := redisGPUSnapshot{ByIndex: make(map[int]map[string]string)}
|
||||
matches := reRedisGPUKey.FindAllSubmatchIndex(content, -1)
|
||||
for _, m := range matches {
|
||||
if len(m) < 6 {
|
||||
continue
|
||||
}
|
||||
|
||||
idxStr := string(content[m[2]:m[3]])
|
||||
field := string(content[m[4]:m[5]])
|
||||
idx, err := strconv.Atoi(idxStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
value := extractRedisInlineValue(content, m[1])
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
byField, ok := snap.ByIndex[idx]
|
||||
if !ok {
|
||||
byField = make(map[string]string)
|
||||
snap.ByIndex[idx] = byField
|
||||
}
|
||||
byField[field] = value
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
func parseRedisNICSnapshot(content []byte) redisNICSnapshot {
|
||||
snap := redisNICSnapshot{ByIndex: make(map[int]map[string]string)}
|
||||
matches := reRedisNICKey.FindAllSubmatchIndex(content, -1)
|
||||
for _, m := range matches {
|
||||
if len(m) < 6 {
|
||||
continue
|
||||
}
|
||||
|
||||
idxStr := string(content[m[2]:m[3]])
|
||||
field := string(content[m[4]:m[5]])
|
||||
idx, err := strconv.Atoi(idxStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
value := extractRedisInlineValue(content, m[1])
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
byField, ok := snap.ByIndex[idx]
|
||||
if !ok {
|
||||
byField = make(map[string]string)
|
||||
snap.ByIndex[idx] = byField
|
||||
}
|
||||
byField[field] = value
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
func extractRedisInlineValue(content []byte, start int) string {
|
||||
if start < 0 || start >= len(content) {
|
||||
return ""
|
||||
}
|
||||
|
||||
i := start
|
||||
for i < len(content) && content[i] <= 0x20 {
|
||||
i++
|
||||
}
|
||||
if i >= len(content) {
|
||||
return ""
|
||||
}
|
||||
|
||||
j := i
|
||||
for j < len(content) {
|
||||
c := content[j]
|
||||
if c == 0 || c < 0x20 || c > 0x7e {
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
if j <= i {
|
||||
return ""
|
||||
}
|
||||
|
||||
raw := strings.TrimSpace(string(content[i:j]))
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
decoded := maybeDecodeHexString(raw)
|
||||
if decoded != "" {
|
||||
return decoded
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
func extractRedisCandidateValue(content []byte, start int) string {
|
||||
// Fast-path for simple inline string values.
|
||||
if v := extractRedisInlineValue(content, start); normalizeRedisValue(v) != "" {
|
||||
return v
|
||||
}
|
||||
|
||||
if start < 0 || start >= len(content) {
|
||||
return ""
|
||||
}
|
||||
|
||||
end := start + 256
|
||||
if end > len(content) {
|
||||
end = len(content)
|
||||
}
|
||||
window := content[start:end]
|
||||
|
||||
for _, token := range splitAlphaNumTokens(window) {
|
||||
if len(token) < 6 {
|
||||
continue
|
||||
}
|
||||
lower := strings.ToLower(token)
|
||||
if strings.Contains(lower, "redis") || strings.Contains(lower, "sensor") || strings.Contains(lower, "fullsdr") {
|
||||
continue
|
||||
}
|
||||
if decoded := maybeDecodeHexString(token); normalizeRedisValue(decoded) != "" {
|
||||
return decoded
|
||||
}
|
||||
if normalizeRedisValue(token) != "" {
|
||||
return token
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func splitAlphaNumTokens(b []byte) []string {
|
||||
var out []string
|
||||
start := -1
|
||||
for i := 0; i < len(b); i++ {
|
||||
c := rune(b[i])
|
||||
if unicode.IsLetter(c) || unicode.IsDigit(c) {
|
||||
if start == -1 {
|
||||
start = i
|
||||
}
|
||||
continue
|
||||
}
|
||||
if start != -1 {
|
||||
out = append(out, string(b[start:i]))
|
||||
start = -1
|
||||
}
|
||||
}
|
||||
if start != -1 {
|
||||
out = append(out, string(b[start:]))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func maybeDecodeHexString(s string) string {
|
||||
if len(s) < 8 || len(s)%2 != 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, c := range s {
|
||||
if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
decoded := strings.TrimSpace(strings.TrimRight(string(b), "\x00"))
|
||||
if decoded == "" {
|
||||
return ""
|
||||
}
|
||||
for _, c := range decoded {
|
||||
if c < 0x20 || c > 0x7e {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
func applyRedisGPUEnrichment(hw *models.HardwareConfig, snap redisGPUSnapshot) {
|
||||
if len(hw.GPUs) == 0 || len(snap.ByIndex) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
type redisGPU struct {
|
||||
Index int
|
||||
Data map[string]string
|
||||
}
|
||||
redisGPUs := make([]redisGPU, 0, len(snap.ByIndex))
|
||||
for idx, data := range snap.ByIndex {
|
||||
if data == nil {
|
||||
continue
|
||||
}
|
||||
if data["NV_GPU_SerialNumber"] == "" && data["NV_GPU_FWVersion"] == "" && data["NV_GPU_UUID"] == "" {
|
||||
continue
|
||||
}
|
||||
redisGPUs = append(redisGPUs, redisGPU{Index: idx, Data: data})
|
||||
}
|
||||
if len(redisGPUs) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Slice(redisGPUs, func(i, j int) bool { return redisGPUs[i].Index < redisGPUs[j].Index })
|
||||
|
||||
target := make([]*models.GPU, 0, len(hw.GPUs))
|
||||
for i := range hw.GPUs {
|
||||
gpu := &hw.GPUs[i]
|
||||
if isNVIDIAGPU(gpu) {
|
||||
target = append(target, gpu)
|
||||
}
|
||||
}
|
||||
if len(target) == 0 || len(target) != len(redisGPUs) {
|
||||
return
|
||||
}
|
||||
sort.Slice(target, func(i, j int) bool {
|
||||
left := strings.TrimSpace(target[i].BDF)
|
||||
right := strings.TrimSpace(target[j].BDF)
|
||||
if left != "" && right != "" {
|
||||
return left < right
|
||||
}
|
||||
return strings.TrimSpace(target[i].Slot) < strings.TrimSpace(target[j].Slot)
|
||||
})
|
||||
|
||||
for i := range target {
|
||||
applyRedisGPUFields(target[i], redisGPUs[i].Data)
|
||||
}
|
||||
}
|
||||
|
||||
func isNVIDIAGPU(gpu *models.GPU) bool {
|
||||
if gpu == nil {
|
||||
return false
|
||||
}
|
||||
if gpu.VendorID == 0x10de {
|
||||
return true
|
||||
}
|
||||
man := strings.ToLower(strings.TrimSpace(gpu.Manufacturer))
|
||||
return strings.Contains(man, "nvidia")
|
||||
}
|
||||
|
||||
func applyRedisGPUFields(gpu *models.GPU, fields map[string]string) {
|
||||
if gpu == nil || fields == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if serial := normalizeRedisValue(fields["NV_GPU_SerialNumber"]); serial != "" && isMissingGPUField(gpu.SerialNumber) {
|
||||
gpu.SerialNumber = serial
|
||||
}
|
||||
if fw := normalizeRedisValue(fields["NV_GPU_FWVersion"]); fw != "" && isMissingGPUField(gpu.Firmware) {
|
||||
gpu.Firmware = fw
|
||||
}
|
||||
if uuid := normalizeRedisValue(fields["NV_GPU_UUID"]); uuid != "" && isMissingGPUField(gpu.UUID) {
|
||||
gpu.UUID = uuid
|
||||
}
|
||||
|
||||
if part := normalizeRedisValue(fields["NVGPUPartNumber"]); part != "" && isMissingGPUField(gpu.PartNumber) {
|
||||
gpu.PartNumber = part
|
||||
}
|
||||
if model := normalizeRedisValue(fields["NVGPUMarketingName"]); model != "" && isGenericGPUModel(gpu.Model) {
|
||||
gpu.Model = model
|
||||
}
|
||||
|
||||
if gpu.ClockSpeed == 0 {
|
||||
if mhz, ok := parseIntField(fields["OperatingSpeedMHz"]); ok {
|
||||
gpu.ClockSpeed = mhz
|
||||
}
|
||||
}
|
||||
if gpu.Power == 0 {
|
||||
if pwr, ok := parseIntField(fields["GPUTotalPower"]); ok {
|
||||
gpu.Power = pwr
|
||||
}
|
||||
}
|
||||
if gpu.Temperature == 0 {
|
||||
if temp, ok := parseIntField(fields["Temp"]); ok {
|
||||
gpu.Temperature = temp
|
||||
}
|
||||
}
|
||||
if gpu.MemTemperature == 0 {
|
||||
if temp, ok := parseIntField(fields["MemTemp"]); ok {
|
||||
gpu.MemTemperature = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseIntField(v string) (int, bool) {
|
||||
v = normalizeRedisValue(v)
|
||||
if v == "" {
|
||||
return 0, false
|
||||
}
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
func normalizeRedisValue(v string) string {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return ""
|
||||
}
|
||||
l := strings.ToLower(v)
|
||||
if l == "n/a" || l == "na" || l == "null" || l == "unknown" {
|
||||
return ""
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func isMissingGPUField(v string) bool {
|
||||
return normalizeRedisValue(v) == ""
|
||||
}
|
||||
|
||||
func isGenericGPUModel(model string) bool {
|
||||
m := strings.ToLower(strings.TrimSpace(model))
|
||||
switch m {
|
||||
case "", "unknown", "display", "display controller", "3d controller", "vga", "gpu":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func applyRedisNICEnrichment(hw *models.HardwareConfig, snap redisNICSnapshot) {
|
||||
if len(hw.NetworkAdapters) == 0 || len(snap.ByIndex) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
type redisNIC struct {
|
||||
Index int
|
||||
Data map[string]string
|
||||
}
|
||||
redisNICs := make([]redisNIC, 0, len(snap.ByIndex))
|
||||
for idx, data := range snap.ByIndex {
|
||||
if data == nil {
|
||||
continue
|
||||
}
|
||||
if normalizeRedisValue(data["FWVersion"]) == "" {
|
||||
continue
|
||||
}
|
||||
redisNICs = append(redisNICs, redisNIC{Index: idx, Data: data})
|
||||
}
|
||||
if len(redisNICs) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Slice(redisNICs, func(i, j int) bool { return redisNICs[i].Index < redisNICs[j].Index })
|
||||
|
||||
target := make([]*models.NetworkAdapter, 0, len(hw.NetworkAdapters))
|
||||
for i := range hw.NetworkAdapters {
|
||||
nic := &hw.NetworkAdapters[i]
|
||||
if nic.Present {
|
||||
target = append(target, nic)
|
||||
}
|
||||
}
|
||||
if len(target) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Slice(target, func(i, j int) bool {
|
||||
left := strings.TrimSpace(target[i].Location)
|
||||
right := strings.TrimSpace(target[j].Location)
|
||||
if left != "" && right != "" {
|
||||
return left < right
|
||||
}
|
||||
return strings.TrimSpace(target[i].Slot) < strings.TrimSpace(target[j].Slot)
|
||||
})
|
||||
|
||||
limit := len(target)
|
||||
if len(redisNICs) < limit {
|
||||
limit = len(redisNICs)
|
||||
}
|
||||
for i := 0; i < limit; i++ {
|
||||
nic := target[i]
|
||||
data := redisNICs[i].Data
|
||||
|
||||
if fw := normalizeRedisValue(data["FWVersion"]); fw != "" && normalizeRedisValue(nic.Firmware) == "" {
|
||||
nic.Firmware = fw
|
||||
}
|
||||
if serial := normalizeRedisValue(data["SerialNum"]); serial != "" && normalizeRedisValue(nic.SerialNumber) == "" {
|
||||
nic.SerialNumber = serial
|
||||
}
|
||||
if part := normalizeRedisValue(data["PartNum"]); part != "" && normalizeRedisValue(nic.PartNumber) == "" {
|
||||
nic.PartNumber = part
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applyRedisPCIeEnrichment(hw *models.HardwareConfig, raidSerials []string) {
|
||||
if hw == nil || len(hw.PCIeDevices) == 0 || len(raidSerials) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
target := make([]*models.PCIeDevice, 0, len(hw.PCIeDevices))
|
||||
for i := range hw.PCIeDevices {
|
||||
dev := &hw.PCIeDevices[i]
|
||||
if normalizeRedisValue(dev.SerialNumber) != "" {
|
||||
continue
|
||||
}
|
||||
class := strings.ToLower(strings.TrimSpace(dev.DeviceClass))
|
||||
part := strings.ToLower(strings.TrimSpace(dev.PartNumber))
|
||||
if strings.Contains(class, "raid") || strings.Contains(class, "sas") || strings.Contains(class, "storage") ||
|
||||
strings.Contains(part, "raid") || strings.Contains(part, "sas") || strings.Contains(part, "hba") {
|
||||
target = append(target, dev)
|
||||
}
|
||||
}
|
||||
if len(target) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
sort.Slice(target, func(i, j int) bool {
|
||||
left := strings.TrimSpace(target[i].BDF)
|
||||
right := strings.TrimSpace(target[j].BDF)
|
||||
if left != "" && right != "" {
|
||||
return left < right
|
||||
}
|
||||
return strings.TrimSpace(target[i].Slot) < strings.TrimSpace(target[j].Slot)
|
||||
})
|
||||
|
||||
limit := len(target)
|
||||
if len(raidSerials) < limit {
|
||||
limit = len(raidSerials)
|
||||
}
|
||||
for i := 0; i < limit; i++ {
|
||||
target[i].SerialNumber = raidSerials[i]
|
||||
}
|
||||
}
|
||||
|
||||
func applyRedisPCIESNPNEnrichment(hw *models.HardwareConfig, snap redisPCIESerialSnapshot) {
|
||||
if hw == nil || len(hw.PCIeDevices) == 0 || len(snap.ByPart) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range hw.PCIeDevices {
|
||||
dev := &hw.PCIeDevices[i]
|
||||
if normalizeRedisValue(dev.SerialNumber) != "" {
|
||||
continue
|
||||
}
|
||||
part := strings.ToLower(strings.TrimSpace(dev.PartNumber))
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if serial := normalizeRedisValue(snap.ByPart[part]); serial != "" {
|
||||
dev.SerialNumber = serial
|
||||
}
|
||||
}
|
||||
}
|
||||
144
internal/parser/vendors/inspur/redis_dump_test.go
vendored
Normal file
144
internal/parser/vendors/inspur/redis_dump_test.go
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
package inspur
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
)
|
||||
|
||||
func TestExtractRedisInlineValue_DecodesHexEncodedString(t *testing.T) {
|
||||
data := []byte("RedisNicInfo:redis_nic_info_t:stNicDeviceInfo0:FWVersion 32362e34332e32353636000000000000\x00tail")
|
||||
key := []byte("RedisNicInfo:redis_nic_info_t:stNicDeviceInfo0:FWVersion")
|
||||
pos := indexBytes(data, key)
|
||||
if pos < 0 {
|
||||
t.Fatal("key not found")
|
||||
}
|
||||
|
||||
got := extractRedisInlineValue(data, pos+len(key))
|
||||
if got != "26.43.2566" {
|
||||
t.Fatalf("expected decoded fw 26.43.2566, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyRedisGPUEnrichment_FillsSerialFirmwareUUID(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
GPUs: []models.GPU{
|
||||
{Slot: "#CPU0_PCIE2", BDF: "0c:00.0", VendorID: 0x10de, Model: "3D Controller"},
|
||||
{Slot: "#CPU0_PCIE1", BDF: "58:00.0", VendorID: 0x10de, Model: "3D Controller"},
|
||||
},
|
||||
}
|
||||
|
||||
snap := redisGPUSnapshot{
|
||||
ByIndex: map[int]map[string]string{
|
||||
1: {
|
||||
"NV_GPU_SerialNumber": "1321125009572",
|
||||
"NV_GPU_FWVersion": "96.00.B7.00.02",
|
||||
"NV_GPU_UUID": "GPU-AAA",
|
||||
},
|
||||
2: {
|
||||
"NV_GPU_SerialNumber": "1321125010420",
|
||||
"NV_GPU_FWVersion": "96.00.B7.00.02",
|
||||
"NV_GPU_UUID": "GPU-BBB",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
applyRedisGPUEnrichment(hw, snap)
|
||||
|
||||
if hw.GPUs[0].SerialNumber != "1321125009572" || hw.GPUs[0].Firmware != "96.00.B7.00.02" || hw.GPUs[0].UUID != "GPU-AAA" {
|
||||
t.Fatalf("unexpected gpu0 enrichment: %+v", hw.GPUs[0])
|
||||
}
|
||||
if hw.GPUs[1].SerialNumber != "1321125010420" || hw.GPUs[1].Firmware != "96.00.B7.00.02" || hw.GPUs[1].UUID != "GPU-BBB" {
|
||||
t.Fatalf("unexpected gpu1 enrichment: %+v", hw.GPUs[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyRedisGPUEnrichment_SkipsOnCountMismatch(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
GPUs: []models.GPU{
|
||||
{Slot: "#CPU0_PCIE2", BDF: "0c:00.0", VendorID: 0x10de, Model: "3D Controller"},
|
||||
},
|
||||
}
|
||||
snap := redisGPUSnapshot{
|
||||
ByIndex: map[int]map[string]string{
|
||||
1: {"NV_GPU_SerialNumber": "1321125009572"},
|
||||
2: {"NV_GPU_SerialNumber": "1321125010420"},
|
||||
},
|
||||
}
|
||||
|
||||
applyRedisGPUEnrichment(hw, snap)
|
||||
if hw.GPUs[0].SerialNumber != "" {
|
||||
t.Fatalf("expected no enrichment on count mismatch, got %q", hw.GPUs[0].SerialNumber)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRedisRAIDSerials_DecodesHexSerial(t *testing.T) {
|
||||
raw := []byte("RAIDMSCCInfo:redis_pcie_mscc_raid_info_t0:RAIDInfo:SerialNum\x80%@`5341523531314532 \x00tail")
|
||||
got := parseRedisRAIDSerials(raw)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("expected 1 raid serial, got %d", len(got))
|
||||
}
|
||||
if got[0] != "SAR511E2" {
|
||||
t.Fatalf("expected decoded serial SAR511E2, got %q", got[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyRedisPCIeEnrichment_FillsStorageControllerSerial(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
PCIeDevices: []models.PCIeDevice{
|
||||
{Slot: "#CPU1_PCIE9", BDF: "98:00.0", DeviceClass: "Smart Storage PQI SAS", PartNumber: "PM8222-SHBA"},
|
||||
{Slot: "#CPU0_PCIE3", BDF: "32:00.0", DeviceClass: "Fibre Channel", PartNumber: "LPE32002"},
|
||||
},
|
||||
}
|
||||
|
||||
applyRedisPCIeEnrichment(hw, []string{"SAR511E2"})
|
||||
|
||||
if hw.PCIeDevices[0].SerialNumber != "SAR511E2" {
|
||||
t.Fatalf("expected PM8222 serial SAR511E2, got %q", hw.PCIeDevices[0].SerialNumber)
|
||||
}
|
||||
if hw.PCIeDevices[1].SerialNumber != "" {
|
||||
t.Fatalf("expected non-storage device serial untouched, got %q", hw.PCIeDevices[1].SerialNumber)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRedisPCIESerialSnapshot_MapsPNToSN(t *testing.T) {
|
||||
raw := []byte("" +
|
||||
"AssetInfoPCIE:SNPN9:PN PM8222-SHBA\x00" +
|
||||
"AssetInfoPCIE:SNPN9:SN SAR511E2\x00")
|
||||
|
||||
snap := parseRedisPCIESerialSnapshot(raw)
|
||||
got := snap.ByPart["pm8222-shba"]
|
||||
if got != "SAR511E2" {
|
||||
t.Fatalf("expected SN SAR511E2 for PM8222-SHBA, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyRedisPCIESNPNEnrichment_FillsByPartNumber(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
PCIeDevices: []models.PCIeDevice{
|
||||
{Slot: "#CPU1_PCIE9", PartNumber: "PM8222-SHBA"},
|
||||
},
|
||||
}
|
||||
snap := redisPCIESerialSnapshot{ByPart: map[string]string{"pm8222-shba": "SAR511E2"}}
|
||||
|
||||
applyRedisPCIESNPNEnrichment(hw, snap)
|
||||
if hw.PCIeDevices[0].SerialNumber != "SAR511E2" {
|
||||
t.Fatalf("expected serial SAR511E2, got %q", hw.PCIeDevices[0].SerialNumber)
|
||||
}
|
||||
}
|
||||
|
||||
func indexBytes(haystack, needle []byte) int {
|
||||
for i := 0; i+len(needle) <= len(haystack); i++ {
|
||||
match := true
|
||||
for j := 0; j < len(needle); j++ {
|
||||
if haystack[i+j] != needle[j] {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if match {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
41507
internal/parser/vendors/pciids/pci.ids
vendored
Normal file
41507
internal/parser/vendors/pciids/pci.ids
vendored
Normal file
File diff suppressed because it is too large
Load Diff
222
internal/parser/vendors/pciids/pciids.go
vendored
222
internal/parser/vendors/pciids/pciids.go
vendored
@@ -1,12 +1,27 @@
|
||||
package pciids
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed pci.ids
|
||||
embeddedPCIIDs string
|
||||
|
||||
loadOnce sync.Once
|
||||
vendors map[int]string
|
||||
devices map[string]string
|
||||
)
|
||||
|
||||
// VendorName returns vendor name by PCI Vendor ID
|
||||
func VendorName(vendorID int) string {
|
||||
loadPCIIDs()
|
||||
if name, ok := vendors[vendorID]; ok {
|
||||
return name
|
||||
}
|
||||
@@ -15,6 +30,7 @@ func VendorName(vendorID int) string {
|
||||
|
||||
// DeviceName returns device name by Vendor ID and Device ID
|
||||
func DeviceName(vendorID, deviceID int) string {
|
||||
loadPCIIDs()
|
||||
key := fmt.Sprintf("%04x:%04x", vendorID, deviceID)
|
||||
if name, ok := devices[key]; ok {
|
||||
return name
|
||||
@@ -46,7 +62,6 @@ func VendorNameFromString(s string) string {
|
||||
} else if c >= 'a' && c <= 'f' {
|
||||
id = id*16 + int(c-'a'+10)
|
||||
} else {
|
||||
// Not a valid hex string, return original
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -54,124 +69,99 @@ func VendorNameFromString(s string) string {
|
||||
return VendorName(id)
|
||||
}
|
||||
|
||||
// Common PCI Vendor IDs
|
||||
// Source: https://pci-ids.ucw.cz/
|
||||
var vendors = map[int]string{
|
||||
// Storage controllers and SSDs
|
||||
0x1E0F: "KIOXIA",
|
||||
0x144D: "Samsung Electronics",
|
||||
0x1C5C: "SK Hynix",
|
||||
0x15B7: "SanDisk (Western Digital)",
|
||||
0x1179: "Toshiba",
|
||||
0x8086: "Intel",
|
||||
0x1344: "Micron Technology",
|
||||
0x126F: "Silicon Motion",
|
||||
0x1987: "Phison Electronics",
|
||||
0x1CC1: "ADATA Technology",
|
||||
0x2646: "Kingston Technology",
|
||||
0x1E95: "Solid State Storage Technology",
|
||||
0x025E: "Solidigm",
|
||||
0x1D97: "Shenzhen Longsys Electronics",
|
||||
0x1E4B: "MAXIO Technology",
|
||||
func loadPCIIDs() {
|
||||
loadOnce.Do(func() {
|
||||
vendors = make(map[int]string)
|
||||
devices = make(map[string]string)
|
||||
|
||||
// Network adapters
|
||||
0x15B3: "Mellanox Technologies",
|
||||
0x14E4: "Broadcom",
|
||||
0x10EC: "Realtek Semiconductor",
|
||||
0x1077: "QLogic",
|
||||
0x19A2: "Emulex",
|
||||
0x1137: "Cisco Systems",
|
||||
0x1924: "Solarflare Communications",
|
||||
0x177D: "Cavium",
|
||||
0x1D6A: "Aquantia",
|
||||
0x1FC9: "Tehuti Networks",
|
||||
0x18D4: "Chelsio Communications",
|
||||
parsePCIIDs(strings.NewReader(embeddedPCIIDs), vendors, devices)
|
||||
|
||||
// GPU / Graphics
|
||||
0x10DE: "NVIDIA",
|
||||
0x1002: "AMD/ATI",
|
||||
0x102B: "Matrox Electronics",
|
||||
0x1A03: "ASPEED Technology",
|
||||
|
||||
// Storage controllers (RAID/HBA)
|
||||
0x1000: "LSI Logic / Broadcom",
|
||||
0x9005: "Adaptec / Microsemi",
|
||||
0x1028: "Dell",
|
||||
0x103C: "Hewlett-Packard",
|
||||
0x17D3: "Areca Technology",
|
||||
0x1CC4: "Union Memory",
|
||||
|
||||
// Server vendors
|
||||
0x1014: "IBM",
|
||||
0x15D9: "Supermicro",
|
||||
0x8088: "Inspur",
|
||||
|
||||
// Other common
|
||||
0x1022: "AMD",
|
||||
0x1106: "VIA Technologies",
|
||||
0x10B5: "PLX Technology",
|
||||
0x1B21: "ASMedia Technology",
|
||||
0x1B4B: "Marvell Technology",
|
||||
0x197B: "JMicron Technology",
|
||||
for _, path := range candidatePCIIDsPaths() {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
parsePCIIDs(f, vendors, devices)
|
||||
_ = f.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Device IDs (vendor:device -> name)
|
||||
var devices = map[string]string{
|
||||
// NVIDIA GPUs (0x10DE)
|
||||
"10de:26b9": "L40S 48GB",
|
||||
"10de:26b1": "L40 48GB",
|
||||
"10de:2684": "RTX 4090",
|
||||
"10de:2704": "RTX 4080",
|
||||
"10de:2782": "RTX 4070 Ti",
|
||||
"10de:2786": "RTX 4070",
|
||||
"10de:27b8": "RTX 4060 Ti",
|
||||
"10de:2882": "RTX 4060",
|
||||
"10de:2204": "RTX 3090",
|
||||
"10de:2208": "RTX 3080 Ti",
|
||||
"10de:2206": "RTX 3080",
|
||||
"10de:2484": "RTX 3070",
|
||||
"10de:2503": "RTX 3060",
|
||||
"10de:20b0": "A100 80GB",
|
||||
"10de:20b2": "A100 40GB",
|
||||
"10de:20f1": "A10",
|
||||
"10de:2236": "A10G",
|
||||
"10de:25b6": "A16",
|
||||
"10de:20b5": "A30",
|
||||
"10de:20b7": "A30X",
|
||||
"10de:1db4": "V100 32GB",
|
||||
"10de:1db1": "V100 16GB",
|
||||
"10de:1e04": "RTX 2080 Ti",
|
||||
"10de:1e07": "RTX 2080",
|
||||
"10de:1f02": "RTX 2070",
|
||||
"10de:26ba": "L40S-PCIE-48G",
|
||||
"10de:2330": "H100 80GB PCIe",
|
||||
"10de:2331": "H100 80GB SXM5",
|
||||
"10de:2322": "H100 NVL",
|
||||
"10de:2324": "H200",
|
||||
func candidatePCIIDsPaths() []string {
|
||||
paths := []string{
|
||||
"pci.ids",
|
||||
"/usr/share/hwdata/pci.ids",
|
||||
"/usr/share/misc/pci.ids",
|
||||
"/opt/homebrew/share/pciids/pci.ids",
|
||||
}
|
||||
|
||||
// AMD GPUs (0x1002)
|
||||
"1002:744c": "Instinct MI250X",
|
||||
"1002:7408": "Instinct MI100",
|
||||
"1002:73a5": "RX 6950 XT",
|
||||
"1002:73bf": "RX 6900 XT",
|
||||
"1002:73df": "RX 6700 XT",
|
||||
"1002:7480": "RX 7900 XTX",
|
||||
"1002:7483": "RX 7900 XT",
|
||||
|
||||
// ASPEED (0x1A03) - BMC VGA
|
||||
"1a03:2000": "AST2500 VGA",
|
||||
"1a03:1150": "AST2600 VGA",
|
||||
|
||||
// Intel GPUs
|
||||
"8086:56c0": "Data Center GPU Flex 170",
|
||||
"8086:56c1": "Data Center GPU Flex 140",
|
||||
|
||||
// Mellanox/NVIDIA NICs (0x15B3)
|
||||
"15b3:1017": "ConnectX-5 100GbE",
|
||||
"15b3:1019": "ConnectX-5 Ex",
|
||||
"15b3:101b": "ConnectX-6",
|
||||
"15b3:101d": "ConnectX-6 Dx",
|
||||
"15b3:101f": "ConnectX-6 Lx",
|
||||
"15b3:1021": "ConnectX-7",
|
||||
"15b3:a2d6": "ConnectX-4 Lx",
|
||||
// Env paths have highest priority, so they are applied last.
|
||||
if env := strings.TrimSpace(os.Getenv("LOGPILE_PCI_IDS_PATH")); env != "" {
|
||||
for _, p := range strings.Split(env, string(os.PathListSeparator)) {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
paths = append(paths, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func parsePCIIDs(r interface{ Read([]byte) (int, error) }, outVendors map[int]string, outDevices map[string]string) {
|
||||
scanner := bufio.NewScanner(r)
|
||||
currentVendor := -1
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Subdevice line (tab-tab) - ignored for now
|
||||
if strings.HasPrefix(line, "\t\t") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Device line
|
||||
if strings.HasPrefix(line, "\t") {
|
||||
if currentVendor < 0 {
|
||||
continue
|
||||
}
|
||||
trimmed := strings.TrimLeft(line, "\t")
|
||||
fields := strings.Fields(trimmed)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
deviceID, err := strconv.ParseInt(fields[0], 16, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSpace(trimmed[len(fields[0]):])
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
key := fmt.Sprintf("%04x:%04x", currentVendor, int(deviceID))
|
||||
outDevices[key] = name
|
||||
continue
|
||||
}
|
||||
|
||||
// Vendor line
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
currentVendor = -1
|
||||
continue
|
||||
}
|
||||
vendorID, err := strconv.ParseInt(fields[0], 16, 32)
|
||||
if err != nil {
|
||||
currentVendor = -1
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSpace(line[len(fields[0]):])
|
||||
if name == "" {
|
||||
currentVendor = -1
|
||||
continue
|
||||
}
|
||||
currentVendor = int(vendorID)
|
||||
outVendors[currentVendor] = name
|
||||
}
|
||||
}
|
||||
|
||||
38
internal/parser/vendors/pciids/pciids_external_test.go
vendored
Normal file
38
internal/parser/vendors/pciids/pciids_external_test.go
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package pciids
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExternalPCIIDsLookup(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
idsPath := filepath.Join(dir, "pci.ids")
|
||||
content := "" +
|
||||
"# sample\n" +
|
||||
"10de NVIDIA Corporation\n" +
|
||||
"\t233b NVIDIA H200 SXM\n" +
|
||||
"8086 Intel Corporation\n" +
|
||||
"\t1521 I350 Gigabit Network Connection\n"
|
||||
|
||||
if err := os.WriteFile(idsPath, []byte(content), 0o644); err != nil {
|
||||
t.Fatalf("write pci.ids: %v", err)
|
||||
}
|
||||
|
||||
t.Setenv("LOGPILE_PCI_IDS_PATH", idsPath)
|
||||
loadOnce = sync.Once{}
|
||||
vendors = nil
|
||||
devices = nil
|
||||
|
||||
if got := DeviceName(0x10de, 0x233b); got != "NVIDIA H200 SXM" {
|
||||
t.Fatalf("expected external device name, got %q", got)
|
||||
}
|
||||
if got := VendorName(0x10de); got != "NVIDIA Corporation" {
|
||||
t.Fatalf("expected external vendor name, got %q", got)
|
||||
}
|
||||
if got := DeviceName(0x8086, 0x1521); got != "I350 Gigabit Network Connection" {
|
||||
t.Fatalf("expected external intel device name, got %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user