Add GPU serial number extraction for NVIDIA diagnostics
Parse inventory/output.log to extract GPU serial numbers from lspci output, expose them via serials API, and add GPU category to web UI. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
145
internal/parser/vendors/nvidia/parser_test.go
vendored
Normal file
145
internal/parser/vendors/nvidia/parser_test.go
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
package nvidia
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/parser"
|
||||
)
|
||||
|
||||
func TestNVIDIAParser_RealArchive(t *testing.T) {
|
||||
// Test with the real archive that was reported as problematic
|
||||
archivePath := filepath.Join("../../../../example", "A514359X5A09844_logs-20260115-151707.tar")
|
||||
|
||||
// Check if file exists
|
||||
if _, err := os.Stat(archivePath); os.IsNotExist(err) {
|
||||
t.Skip("Test archive not found, skipping test")
|
||||
}
|
||||
|
||||
// Extract files from archive
|
||||
files, err := parser.ExtractArchive(archivePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to extract archive: %v", err)
|
||||
}
|
||||
|
||||
// Check if inventory/output.log exists
|
||||
hasInventoryLog := false
|
||||
for _, f := range files {
|
||||
if filepath.Base(f.Path) == "output.log" {
|
||||
t.Logf("Found file: %s", f.Path)
|
||||
}
|
||||
if f.Path == "./inventory/output.log" || f.Path == "inventory/output.log" {
|
||||
hasInventoryLog = true
|
||||
t.Logf("Found inventory/output.log with %d bytes", len(f.Content))
|
||||
}
|
||||
}
|
||||
if !hasInventoryLog {
|
||||
t.Error("inventory/output.log not found in extracted files")
|
||||
}
|
||||
|
||||
// Create parser and parse
|
||||
p := &Parser{}
|
||||
result, err := p.Parse(files)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse archive: %v", err)
|
||||
}
|
||||
|
||||
// Verify basic system info
|
||||
if result.Hardware.BoardInfo.Manufacturer == "" {
|
||||
t.Error("Expected Manufacturer to be set")
|
||||
}
|
||||
if result.Hardware.BoardInfo.ProductName == "" {
|
||||
t.Error("Expected ProductName to be set")
|
||||
}
|
||||
if result.Hardware.BoardInfo.SerialNumber == "" {
|
||||
t.Error("Expected SerialNumber to be set")
|
||||
}
|
||||
|
||||
t.Logf("System Info:")
|
||||
t.Logf(" Manufacturer: %s", result.Hardware.BoardInfo.Manufacturer)
|
||||
t.Logf(" Product: %s", result.Hardware.BoardInfo.ProductName)
|
||||
t.Logf(" Serial: %s", result.Hardware.BoardInfo.SerialNumber)
|
||||
|
||||
// Verify GPUs were found
|
||||
if len(result.Hardware.GPUs) == 0 {
|
||||
t.Error("Expected to find GPUs")
|
||||
}
|
||||
|
||||
t.Logf("\nFound %d GPUs:", len(result.Hardware.GPUs))
|
||||
|
||||
gpusWithSerials := 0
|
||||
for _, gpu := range result.Hardware.GPUs {
|
||||
t.Logf(" %s: %s (Firmware: %s, Serial: %s, BDF: %s)",
|
||||
gpu.Slot, gpu.Model, gpu.Firmware, gpu.SerialNumber, gpu.BDF)
|
||||
|
||||
if gpu.SerialNumber != "" {
|
||||
gpusWithSerials++
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that GPU serial numbers were extracted
|
||||
if gpusWithSerials == 0 {
|
||||
t.Error("Expected at least some GPUs to have serial numbers")
|
||||
}
|
||||
|
||||
t.Logf("\nGPUs with serial numbers: %d/%d", gpusWithSerials, len(result.Hardware.GPUs))
|
||||
|
||||
// Check events for SXM2 failures
|
||||
t.Logf("\nTotal events: %d", len(result.Events))
|
||||
|
||||
// Look for the specific serial or SXM2
|
||||
sxm2Events := 0
|
||||
for _, event := range result.Events {
|
||||
desc := event.Description + " " + event.RawData + " " + event.EventType
|
||||
if contains(desc, "SXM2") || contains(desc, "1653925025827") {
|
||||
t.Logf(" SXM2 Event: [%s] %s (Severity: %s)", event.EventType, event.Description, event.Severity)
|
||||
sxm2Events++
|
||||
}
|
||||
}
|
||||
|
||||
if sxm2Events == 0 {
|
||||
t.Error("Expected to find events for SXM2 (faulty GPU 1653925025827)")
|
||||
}
|
||||
t.Logf("\nSXM2 failure events: %d", sxm2Events)
|
||||
}
|
||||
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || len(s) > len(substr) &&
|
||||
(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
|
||||
findSubstring(s, substr)))
|
||||
}
|
||||
|
||||
func findSubstring(s, substr string) bool {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestFormatGPUSerial(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: "14-17-dc-65-77-2d-b0-48",
|
||||
expected: "48:B0:2D:77:65:DC:17:14",
|
||||
},
|
||||
{
|
||||
input: "f2-fd-85-e0-2f-2d-b0-48",
|
||||
expected: "48:B0:2D:2F:E0:85:FD:F2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
result := formatGPUSerial(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("formatGPUSerial(%s) = %s, want %s", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user