378 lines
9.9 KiB
Go
378 lines
9.9 KiB
Go
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"`
|
|
}
|
|
|
|
// 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 {
|
|
return nil
|
|
}
|
|
|
|
var devices []models.PCIeDevice
|
|
for _, pcie := range pcieInfo {
|
|
if pcie.Present != 1 {
|
|
continue
|
|
}
|
|
|
|
// Convert PCIe speed to GEN notation
|
|
maxSpeed := fmt.Sprintf("GEN%d", pcie.MaxLinkSpeed)
|
|
currentSpeed := fmt.Sprintf("GEN%d", pcie.CurrentLinkSpeed)
|
|
|
|
// 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 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,
|
|
VendorID: pcie.VendorID,
|
|
DeviceID: pcie.DeviceID,
|
|
BDF: bdf,
|
|
DeviceClass: deviceClass,
|
|
Manufacturer: manufacturer,
|
|
LinkWidth: pcie.CurrentLinkWidth,
|
|
LinkSpeed: currentSpeed,
|
|
MaxLinkWidth: pcie.MaxLinkWidth,
|
|
MaxLinkSpeed: maxSpeed,
|
|
PartNumber: partNumber,
|
|
SerialNumber: strings.TrimSpace(pcie.SerialNum),
|
|
}
|
|
|
|
devices = append(devices, device)
|
|
}
|
|
|
|
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:
|
|
// 1 = Mass Storage Controller
|
|
// 2 = Network Controller
|
|
// 3 = Display Controller (GPU)
|
|
// 4 = Multimedia Controller
|
|
|
|
switch devType {
|
|
case 1:
|
|
if devSubtype == 4 {
|
|
return "RAID Controller"
|
|
}
|
|
return "Storage Controller"
|
|
case 2:
|
|
return "Network Controller"
|
|
case 3:
|
|
// GPU
|
|
if strings.Contains(strings.ToUpper(deviceName), "H100") {
|
|
return "GPU (H100)"
|
|
}
|
|
if strings.Contains(strings.ToUpper(deviceName), "A100") {
|
|
return "GPU (A100)"
|
|
}
|
|
if strings.Contains(strings.ToUpper(deviceName), "NVIDIA") {
|
|
return "GPU"
|
|
}
|
|
return "Display Controller"
|
|
case 4:
|
|
return "Multimedia Controller"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
// ParseGPUs extracts GPU data from PCIe devices and sensors
|
|
func ParseGPUs(pcieDevices []models.PCIeDevice, sensors []models.SensorReading) []models.GPU {
|
|
var gpus []models.GPU
|
|
|
|
// Find GPU devices
|
|
for _, pcie := range pcieDevices {
|
|
if !strings.Contains(strings.ToLower(pcie.DeviceClass), "gpu") &&
|
|
!strings.Contains(strings.ToLower(pcie.DeviceClass), "display") {
|
|
continue
|
|
}
|
|
|
|
// Skip integrated graphics (ASPEED, etc.)
|
|
if strings.Contains(pcie.Manufacturer, "ASPEED") {
|
|
continue
|
|
}
|
|
|
|
gpu := models.GPU{
|
|
Slot: pcie.Slot,
|
|
Location: pcie.Slot,
|
|
Model: pcie.DeviceClass,
|
|
Manufacturer: pcie.Manufacturer,
|
|
SerialNumber: pcie.SerialNumber,
|
|
MaxLinkWidth: pcie.MaxLinkWidth,
|
|
MaxLinkSpeed: pcie.MaxLinkSpeed,
|
|
CurrentLinkWidth: pcie.LinkWidth,
|
|
CurrentLinkSpeed: pcie.LinkSpeed,
|
|
Status: "OK",
|
|
}
|
|
|
|
// Extract GPU number from slot name (e.g., "PCIE7" -> 7)
|
|
slotNum := extractSlotNumber(pcie.Slot)
|
|
|
|
// Find temperature sensors for this GPU
|
|
for _, sensor := range sensors {
|
|
sensorName := strings.ToUpper(sensor.Name)
|
|
|
|
// Match GPU temperature sensor (e.g., "GPU7_Temp")
|
|
if strings.Contains(sensorName, fmt.Sprintf("GPU%d_TEMP", slotNum)) {
|
|
if sensor.RawValue != "" {
|
|
fmt.Sscanf(sensor.RawValue, "%d", &gpu.Temperature)
|
|
}
|
|
}
|
|
|
|
// Match GPU memory temperature (e.g., "GPU7_Mem_Temp")
|
|
if strings.Contains(sensorName, fmt.Sprintf("GPU%d_MEM_TEMP", slotNum)) {
|
|
if sensor.RawValue != "" {
|
|
fmt.Sscanf(sensor.RawValue, "%d", &gpu.MemTemperature)
|
|
}
|
|
}
|
|
|
|
// Match PCIe slot temperature (e.g., "PCIE7_GPU_TLM_T")
|
|
if strings.Contains(sensorName, fmt.Sprintf("PCIE%d_GPU_TLM_T", slotNum)) {
|
|
if sensor.RawValue != "" && gpu.Temperature == 0 {
|
|
fmt.Sscanf(sensor.RawValue, "%d", &gpu.Temperature)
|
|
}
|
|
}
|
|
}
|
|
|
|
gpus = append(gpus, gpu)
|
|
}
|
|
|
|
return gpus
|
|
}
|
|
|
|
// extractSlotNumber extracts slot number from location string
|
|
// e.g., "CPU0_PE3_AC_PCIE7" -> 7
|
|
func extractSlotNumber(location string) int {
|
|
parts := strings.Split(location, "_")
|
|
for _, part := range parts {
|
|
if strings.HasPrefix(part, "PCIE") || strings.HasPrefix(part, "#CPU") {
|
|
var num int
|
|
fmt.Sscanf(part, "PCIE%d", &num)
|
|
if num > 0 {
|
|
return num
|
|
}
|
|
}
|
|
}
|
|
return 0
|
|
}
|