156 lines
4.5 KiB
Go
156 lines
4.5 KiB
Go
package nvidia
|
|
|
|
import (
|
|
"bufio"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/models"
|
|
"git.mchus.pro/mchus/logpile/internal/parser"
|
|
)
|
|
|
|
var (
|
|
// Regex to extract devname mappings from fieldiag command line
|
|
// Example: "devname=0000:ba:00.0,SXM5_SN_1653925027099"
|
|
devnameRegex = regexp.MustCompile(`devname=([\da-fA-F:\.]+),(\w+)`)
|
|
// Regex to capture BDF from commands like:
|
|
// "$ lspci -vvvs 0000:05:00.0" or "$ lspci -vvs 0000:05:00.0"
|
|
lspciBDFRegex = regexp.MustCompile(`^\$\s+lspci\s+-[^\s]*\s+([0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7])\s*$`)
|
|
// Example: "Capabilities: [2f0 v1] Device Serial Number 99-d3-61-c8-ac-2d-b0-48"
|
|
deviceSerialRegex = regexp.MustCompile(`Device Serial Number\s+([0-9a-fA-F\-:]+)`)
|
|
)
|
|
|
|
// ParseInventoryLog parses inventory/output.log to extract GPU serial numbers
|
|
// from fieldiag devname parameters (e.g., "SXM5_SN_1653925027099")
|
|
func ParseInventoryLog(content []byte, result *models.AnalysisResult) error {
|
|
if result.Hardware == nil || len(result.Hardware.GPUs) == 0 {
|
|
// No GPUs to update
|
|
return nil
|
|
}
|
|
|
|
scanner := bufio.NewScanner(strings.NewReader(string(content)))
|
|
|
|
// First pass: build mapping of PCI BDF -> Slot name and serial number from fieldiag command line
|
|
pciToSlot := make(map[string]string)
|
|
pciToSerial := make(map[string]string)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
// Look for fieldiag command with devname parameters
|
|
if strings.Contains(line, "devname=") && strings.Contains(line, "fieldiag") {
|
|
matches := devnameRegex.FindAllStringSubmatch(line, -1)
|
|
for _, match := range matches {
|
|
if len(match) == 3 {
|
|
pciBDF := match[1]
|
|
slotName := match[2]
|
|
// Extract slot number and serial from name like "SXM5_SN_1653925027099"
|
|
if strings.HasPrefix(slotName, "SXM") {
|
|
parts := strings.Split(slotName, "_")
|
|
if len(parts) >= 1 {
|
|
// Convert "SXM5" to "GPUSXM5"
|
|
slot := "GPU" + parts[0]
|
|
pciToSlot[pciBDF] = slot
|
|
}
|
|
// Extract serial number from "SXM5_SN_1653925027099"
|
|
if len(parts) == 3 && parts[1] == "SN" {
|
|
serial := parts[2]
|
|
pciToSerial[pciBDF] = serial
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Second pass: assign serial numbers to GPUs based on slot mapping
|
|
for i := range result.Hardware.GPUs {
|
|
slot := result.Hardware.GPUs[i].Slot
|
|
// Find the PCI BDF for this slot
|
|
var foundSerial string
|
|
for pciBDF, mappedSlot := range pciToSlot {
|
|
if mappedSlot == slot {
|
|
// Found matching slot, get serial number
|
|
if serial, ok := pciToSerial[pciBDF]; ok {
|
|
foundSerial = serial
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if foundSerial != "" {
|
|
result.Hardware.GPUs[i].SerialNumber = foundSerial
|
|
}
|
|
}
|
|
|
|
// Third pass: parse lspci "Device Serial Number" by BDF (useful for NVSwitch serials).
|
|
bdfToDeviceSerial := make(map[string]string)
|
|
currentBDF := ""
|
|
scanner = bufio.NewScanner(strings.NewReader(string(content)))
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
if m := lspciBDFRegex.FindStringSubmatch(line); len(m) == 2 {
|
|
currentBDF = strings.ToLower(strings.TrimSpace(m[1]))
|
|
continue
|
|
}
|
|
|
|
if currentBDF == "" {
|
|
continue
|
|
}
|
|
|
|
if m := deviceSerialRegex.FindStringSubmatch(line); len(m) == 2 {
|
|
serial := strings.TrimSpace(m[1])
|
|
if serial != "" {
|
|
bdfToDeviceSerial[currentBDF] = serial
|
|
}
|
|
currentBDF = ""
|
|
}
|
|
}
|
|
|
|
// Apply to PCIe devices first (includes NVSwitch).
|
|
for i := range result.Hardware.PCIeDevices {
|
|
dev := &result.Hardware.PCIeDevices[i]
|
|
if strings.TrimSpace(dev.SerialNumber) != "" {
|
|
continue
|
|
}
|
|
bdf := strings.ToLower(strings.TrimSpace(dev.BDF))
|
|
if bdf == "" {
|
|
continue
|
|
}
|
|
if serial := bdfToDeviceSerial[bdf]; serial != "" {
|
|
dev.SerialNumber = serial
|
|
}
|
|
}
|
|
|
|
// Apply to GPUs only if GPU serial is still empty (do not overwrite prod serial from devname).
|
|
for i := range result.Hardware.GPUs {
|
|
gpu := &result.Hardware.GPUs[i]
|
|
if strings.TrimSpace(gpu.SerialNumber) != "" {
|
|
continue
|
|
}
|
|
bdf := strings.ToLower(strings.TrimSpace(gpu.BDF))
|
|
if bdf == "" {
|
|
continue
|
|
}
|
|
if serial := bdfToDeviceSerial[bdf]; serial != "" {
|
|
gpu.SerialNumber = serial
|
|
}
|
|
}
|
|
|
|
return scanner.Err()
|
|
}
|
|
|
|
// findInventoryOutputLog finds the inventory/output.log file
|
|
func findInventoryOutputLog(files []parser.ExtractedFile) *parser.ExtractedFile {
|
|
for _, f := range files {
|
|
// Look for inventory/output.log
|
|
path := strings.ToLower(f.Path)
|
|
if strings.Contains(path, "inventory/output.log") ||
|
|
strings.Contains(path, "inventory\\output.log") {
|
|
return &f
|
|
}
|
|
}
|
|
return nil
|
|
}
|