diff --git a/bible-local/docs/nvidia-bug-report-analysis.md b/bible-local/docs/nvidia-bug-report-analysis.md new file mode 100644 index 0000000..6bd2b52 --- /dev/null +++ b/bible-local/docs/nvidia-bug-report-analysis.md @@ -0,0 +1,77 @@ +# Analyzing nvidia-bug-report.log.gz — agent instructions + +Short checklist for an AI agent (or a human) given a `nvidia-bug-report-*.log.gz` and +asked "what's wrong with this box." + +## 1. Structured inventory pass (LOGPile) + +Run it through LOGPile's `nvidia_bug_report` parser first — it gives board/CPU/memory/ +GPU/NIC/PSU inventory as structured JSON (`internal/parser/vendors/nvidia_bug_report/`). +As of this session the decompressor no longer hard-caps at 50MB (streams with a +gzip-bomb ratio guard instead — see `internal/parser/archive.go`), so large dumps +come through whole. Known gap: PSU `present` flag was buggy (fixed), sensors/FRU are +legitimately empty for this format (no `ipmitool sdr`/`fru` in host-side bug reports). + +## 2. Grep passes on the raw decompressed log + +In priority order: + +1. `Xid (PCI:` — GPU error reports. Note **all** `PCI:xxxx` addresses and Xid codes, + count occurrences, get first/last timestamp per GPU. +2. `SXid` — NVSwitch (fabric manager) errors on HGX/NVSwitch systems. +3. `GPU recovery action changed` — tracks a GPU through `None → Drain and Reset → + GPU Reset Required` — a GPU stuck at "Reset Required" for a long span is a live + incident, not a transient blip. +4. `fell off the bus`, `Uncorrectable`, `Double Bit ECC`, `Row Remap` — hardware + memory/PCIe failures. +5. Thermal/power: `thermal slowdown`, `power slowdown`, `Reason: SW\|HW`. +6. `nvidia-smi -q` section per GPU: `ECC Errors`, `Retired Pages`, `Remapped Rows`, + `GPU Recovery Action` — cross-check current (not historical) state at capture time. +7. `segfault`, kernel panics, `Call Trace`, `MCE`/machine-check in dmesg/journalctl + excerpts. + +Cross-reference: an Xid burst that stops before the `nvidia-smi -q` snapshot and shows +`GPU Recovery Action: None` + clean ECC counters at the end means the box already +self-recovered (e.g. via reboot) — still worth flagging, but not an active fire. + +## 3. Classify each Xid, don't just list it + +Look it up in NVIDIA's Xid catalog (link below) before writing a verdict. Same code +can mean different things depending on class: +- App-level GPU exception (bad kernel launch, illegal memory access) — usually not + hardware-actionable, correlate with the offending `pid`/process name in the log. +- NVLink/fabric fault (`MSE Degraded`, link training failures) — check + `knvlink*` messages, Fabric Manager logs — often requires GPU/node reset, can + recur → RMA candidate if repeated. +- ECC/memory retirement (uncorrectable DRAM, row-remap failures) — RMA candidate, + check `Retired Pages` / `Remapped Rows` counts. +- `XID 79` (GPU fell off the bus) — drain the node, treat as hardware failure. + +## 4. Escalation bar + +Recommend RMA/hardware escalation when: repeated fatal Xid/SXid on the same GPU across +multiple days, non-zero uncorrectable ECC or remap failures, or a GPU that required +manual reset more than once. Otherwise: flag as informational / correlate with workload. + +## What already exists online (recipes found 2026-08-26) + +- **Lambda Labs — `check-nvidia-bug-report.sh`**: an actual automated scanner for this + exact file, open source. Clones and runs against the extracted report; flags Xid + errors, thermal/power slowdown, segfaults, CPU throttling, "fell off the bus", + RmInit failures. This is the closest thing to a ready-made "recipe" — worth using + directly or mining its grep patterns. + https://github.com/lambdal-support/lambda-public-tools (see `check-nvidia-bug-report/`) + Guide: [Lambda Docs — Using the nvidia-bug-report.log file to troubleshoot your system](https://docs.lambda.ai/education/linux-usage/using-the-nvidia-bug-report.log-file-to-troubleshoot-your-system/) +- **NVIDIA — GPU Debug Guidelines**: official step-by-step (collect → `dcgmi diag` → + classify Xid → escalate/RMA criteria), the authoritative human-oriented recipe. + https://docs.nvidia.com/deploy/gpu-debug-guidelines/index.html +- **NVIDIA — Xid Errors reference**: the Xid code catalog itself (meanings, no fixed + hardware/software verdict per code — context-dependent). + https://docs.nvidia.com/deploy/xid-errors/introduction.html +- **AWS re:Post — Troubleshoot Xid errors in NVIDIA GPU-accelerated instances**: + cloud-instance-flavored version of the same workflow. + https://repost.aws/knowledge-center/ec2-linux-troubleshoot-xid-errors + +No public "AI agent prompt/recipe" specifically for feeding this file to an LLM was +found — the closest things are the shell-script scanners above; an agent should treat +their grep pattern lists as a starting checklist (§2 above already folds them in). diff --git a/internal/parser/vendors/nvidia_bug_report/errors.go b/internal/parser/vendors/nvidia_bug_report/errors.go new file mode 100644 index 0000000..22b9fcc --- /dev/null +++ b/internal/parser/vendors/nvidia_bug_report/errors.go @@ -0,0 +1,230 @@ +package nvidia_bug_report + +import ( + "bufio" + "fmt" + "regexp" + "strings" + "time" + + "git.mchus.pro/mchus/logpile/internal/models" + "git.mchus.pro/mchus/logpile/internal/parser" +) + +// syslogLineRegex matches the kernel/journal line prefix nvidia-bug-report.sh +// captures from dmesg/journalctl: "Aug 24 13:52:04 hostname kernel: ". +var syslogLineRegex = regexp.MustCompile(`^(\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})\s+\S+\s+(.+)$`) + +// xidRegex matches NVRM Xid (GPU) and SXid (NVSwitch/fabric manager) error +// reports, e.g. "NVRM: Xid (PCI:0000:dc:00): 31, pid=..." or +// "NVRM: Xid (PCI:0000:1a:00): 150, MSE Degraded Fatal ...". +var xidRegex = regexp.MustCompile(`NVRM:\s*(S?Xid)\s*\(PCI:([0-9a-fA-F:.]+)\):\s*(\d+),\s*(.*)$`) + +// nvlinkRxDetectFailRegex matches a specific NVLink topology-discovery retry +// message that was observed to flood the log 100k+ times in a single burst +// around a real NVLink fault; individual lines carry no extra information +// beyond "it happened again", so they are aggregated into one summary event +// instead of one event per line. +var nvlinkRxDetectFailRegex = regexp.MustCompile(`knvlink\w*RxDetect\w*: Failed to update Rx Detect Link mask`) + +// fellOffBusRegex matches the classic "GPU fell off/has fallen off the bus" +// hardware fault for the (rare) case it's logged outside an Xid line — the +// same fault is usually also reported as Xid 79, already covered above. +var fellOffBusRegex = regexp.MustCompile(`(?i)fell off the bus|fallen off the bus`) + +// hardwareLeaningXidCodes are Xid codes NVIDIA's GPU Debug Guidelines +// (https://docs.nvidia.com/deploy/gpu-debug-guidelines/index.html) and Xid +// catalog associate with likely hardware failures (ECC/memory retirement, +// NVLink/fabric faults, GPU falling off the bus) as opposed to an +// application-triggered exception. This is a heuristic severity hint for +// triage, not a definitive verdict — the actual cause is always +// context-dependent; see bible-local/docs/nvidia-bug-report-analysis.md. +var hardwareLeaningXidCodes = map[string]bool{ + "48": true, "63": true, "64": true, "74": true, "79": true, + "92": true, "94": true, "95": true, "119": true, "120": true, + "122": true, "123": true, "134": true, "137": true, "150": true, "154": true, "155": true, +} + +// gpuFault accumulates the worst status seen for one GPU's PCI address, so +// the fault can be written back onto that GPU's own inventory record — an +// event in the Event Logs table is easy to miss; a GPU card showing +// "Critical" is not. +type gpuFault struct { + severity models.Severity + lastTS time.Time + description string +} + +func (f *gpuFault) absorb(severity models.Severity, ts time.Time, description string) { + if severity == models.SeverityCritical || f.severity != models.SeverityCritical { + f.severity = severity + } + if ts.After(f.lastTS) { + f.lastTS = ts + f.description = description + } +} + +// parseXidAndFaultEvents scans the raw bug report for Xid/SXid GPU error +// reports and a small set of other well-known hardware fault patterns, +// records them as events, and writes the worst fault seen for each GPU back +// onto that GPU's own Status/ErrorDescription/StatusChangedAt fields so the +// inventory record itself reflects the fault, not just the event log. This +// does not attempt to classify every message — see +// bible-local/docs/nvidia-bug-report-analysis.md for the full manual triage +// checklist. +func parseXidAndFaultEvents(content string, result *models.AnalysisResult) { + scanner := bufio.NewScanner(strings.NewReader(content)) + scanner.Buffer(make([]byte, 1024*1024), 1024*1024) + + collectedYear := time.Now().Year() + if !result.CollectedAt.IsZero() { + collectedYear = result.CollectedAt.Year() + } + + faultsByPCI := make(map[string]*gpuFault) + recordFault := func(pci string, severity models.Severity, ts time.Time, description string) { + if pci == "" { + return + } + f, ok := faultsByPCI[pci] + if !ok { + f = &gpuFault{} + faultsByPCI[pci] = f + } + f.absorb(severity, ts, description) + } + + var nvlinkFloodCount int + var nvlinkFloodFirst, nvlinkFloodLast time.Time + + for scanner.Scan() { + line := scanner.Text() + m := syslogLineRegex.FindStringSubmatch(line) + if m == nil { + continue + } + timeStr, message := m[1], m[2] + ts := parseSyslogTimestamp(timeStr, collectedYear, result.CollectedAt) + + if xm := xidRegex.FindStringSubmatch(message); xm != nil { + kind, pci, code, rest := xm[1], xm[2], xm[3], strings.TrimSpace(xm[4]) + + severity := models.SeverityWarning + if hardwareLeaningXidCodes[code] || strings.Contains(rest, "GPU Reset Required") { + severity = models.SeverityCritical + } + + eventType := "GPU Xid Error" + if kind == "SXid" { + eventType = "NVSwitch SXid Error" + } + description := fmt.Sprintf("%s %s reported on %s", kind, code, pci) + + result.Events = append(result.Events, models.Event{ + Timestamp: ts, + Source: "NVIDIA Driver", + SensorType: "GPU", + SensorName: pci, + EventType: eventType, + Severity: severity, + Description: description, + RawData: rest, + }) + if kind == "Xid" { + recordFault(pci, severity, ts, description+": "+rest) + } + continue + } + + if fellOffBusRegex.MatchString(message) { + result.Events = append(result.Events, models.Event{ + Timestamp: ts, + Source: "NVIDIA Driver", + SensorType: "GPU", + EventType: "GPU Fell Off The Bus", + Severity: models.SeverityCritical, + Description: "GPU fell off the PCIe bus", + RawData: strings.TrimSpace(message), + }) + continue + } + + if nvlinkRxDetectFailRegex.MatchString(message) { + nvlinkFloodCount++ + // Track true chronological min/max, not scan order: a + // nvidia-bug-report.sh dump concatenates multiple overlapping + // log sources (e.g. dmesg -T and journalctl excerpts covering + // different but overlapping windows), so later lines in the + // file are not guaranteed to be later in time. + if !ts.IsZero() { + if nvlinkFloodFirst.IsZero() || ts.Before(nvlinkFloodFirst) { + nvlinkFloodFirst = ts + } + if ts.After(nvlinkFloodLast) { + nvlinkFloodLast = ts + } + } + } + } + + if nvlinkFloodCount > 0 { + severity := models.SeverityWarning + if nvlinkFloodCount > 100 { + severity = models.SeverityCritical + } + result.Events = append(result.Events, models.Event{ + Timestamp: nvlinkFloodLast, + Source: "NVIDIA Driver", + SensorType: "GPU", + EventType: "NVLink Rx Detect Failure Burst", + Severity: severity, + Description: fmt.Sprintf("NVLink Rx Detect Link mask update failed %d times between %s and %s", nvlinkFloodCount, nvlinkFloodFirst.Format(time.RFC3339), nvlinkFloodLast.Format(time.RFC3339)), + }) + } + + applyGPUFaultStatus(result, faultsByPCI) +} + +// applyGPUFaultStatus writes each GPU's worst known fault onto its own +// Status/ErrorDescription/StatusChangedAt fields, matched by PCI address +// (Xid lines report "0000:1a:00", GPU.BDF is "0000:1a:00.0" — compare on the +// bus:device:slot prefix, ignoring the trailing PCI function digit). +func applyGPUFaultStatus(result *models.AnalysisResult, faultsByPCI map[string]*gpuFault) { + if len(faultsByPCI) == 0 || result.Hardware == nil { + return + } + for i := range result.Hardware.GPUs { + gpu := &result.Hardware.GPUs[i] + base := gpu.BDF + if idx := strings.LastIndex(base, "."); idx != -1 { + base = base[:idx] + } + f, ok := faultsByPCI[base] + if !ok || gpu.Status == "Excluded" { + continue + } + gpu.Status = string(f.severity) + gpu.ErrorDescription = f.description + ts := f.lastTS + gpu.StatusChangedAt = &ts + } +} + +// parseSyslogTimestamp parses a syslog-style "Mon _2 15:04:05" timestamp +// (no year) using collectedYear as the assumed year, then corrects for a +// year rollover if that guess would put the event implausibly after the +// report was collected (e.g. a December log line in a January report). +func parseSyslogTimestamp(raw string, collectedYear int, collectedAt time.Time) time.Time { + ts, err := parser.ParseInDefaultArchiveLocation("Jan _2 15:04:05 2006", fmt.Sprintf("%s %d", raw, collectedYear)) + if err != nil { + return time.Time{} + } + if !collectedAt.IsZero() && ts.After(collectedAt.Add(48*time.Hour)) { + ts, err = parser.ParseInDefaultArchiveLocation("Jan _2 15:04:05 2006", fmt.Sprintf("%s %d", raw, collectedYear-1)) + if err != nil { + return time.Time{} + } + } + return ts +} diff --git a/internal/parser/vendors/nvidia_bug_report/errors_test.go b/internal/parser/vendors/nvidia_bug_report/errors_test.go new file mode 100644 index 0000000..3096589 --- /dev/null +++ b/internal/parser/vendors/nvidia_bug_report/errors_test.go @@ -0,0 +1,199 @@ +package nvidia_bug_report + +import ( + "strings" + "testing" + "time" + + "git.mchus.pro/mchus/logpile/internal/models" + "git.mchus.pro/mchus/logpile/internal/parser" +) + +func eventsByType(events []models.Event, eventType string) []models.Event { + var out []models.Event + for _, e := range events { + if e.EventType == eventType { + out = append(out, e) + } + } + return out +} + +func TestParseXidAndFaultEvents_ClassifiesByCode(t *testing.T) { + content := strings.Join([]string{ + "Jul 29 02:33:16 host kernel: NVRM: Xid (PCI:0000:dc:00): 31, pid=123, name=python, MMU Fault", + "Aug 19 21:17:46 host kernel: NVRM: Xid (PCI:0000:1a:00): 154, GPU recovery action changed from 0x0 (None) to 0x4 (Drain and Reset)", + "Aug 23 02:36:37 host kernel: NVRM: Xid (PCI:0000:1a:00): 154, GPU recovery action changed from 0x4 (Drain and Reset) to 0x1 (GPU Reset Required)", + }, "\n") + + result := &models.AnalysisResult{ + CollectedAt: time.Date(2026, 8, 24, 13, 56, 0, 0, time.UTC), + } + parseXidAndFaultEvents(content, result) + + xids := eventsByType(result.Events, "GPU Xid Error") + if len(xids) != 3 { + t.Fatalf("expected 3 Xid events, got %d", len(xids)) + } + + if xids[0].Severity != models.SeverityWarning { + t.Fatalf("expected Xid 31 to be warning, got %s", xids[0].Severity) + } + if xids[0].SensorName != "0000:dc:00" { + t.Fatalf("expected sensor name 0000:dc:00, got %q", xids[0].SensorName) + } + if xids[0].Timestamp.Month() != time.July || xids[0].Timestamp.Year() != 2026 { + t.Fatalf("expected July 2026 timestamp, got %s", xids[0].Timestamp) + } + + if xids[1].Severity != models.SeverityCritical { + t.Fatalf("expected Xid 154 (Drain and Reset) to be critical, got %s", xids[1].Severity) + } + if xids[2].Severity != models.SeverityCritical { + t.Fatalf("expected Xid 154 (GPU Reset Required) to be critical, got %s", xids[2].Severity) + } +} + +func TestParseXidAndFaultEvents_Xid137IsHardwareLeaning(t *testing.T) { + // Xid 137 (NVLink TLC RX PRIV Error) is an NVLink transport-layer + // hardware fault, typically seen right before a contained Xid 94 — + // must classify as critical, not the app-level default. + content := "Aug 18 04:45:34 host kernel: NVRM: Xid (PCI:0000:63:00): 137, TLC RX interrupt hit on link 2 on GPU0: PRIV Error" + + result := &models.AnalysisResult{CollectedAt: time.Date(2026, 8, 18, 17, 40, 0, 0, time.UTC)} + parseXidAndFaultEvents(content, result) + + xids := eventsByType(result.Events, "GPU Xid Error") + if len(xids) != 1 { + t.Fatalf("expected 1 Xid event, got %d", len(xids)) + } + if xids[0].Severity != models.SeverityCritical { + t.Fatalf("expected Xid 137 to be critical, got %s", xids[0].Severity) + } +} + +func TestParseXidAndFaultEvents_SXidUsesDistinctEventType(t *testing.T) { + content := "Aug 20 01:00:00 host kernel: NVRM: SXid (PCI:0000:90:00): 12028, some NVSwitch fault detail" + + result := &models.AnalysisResult{CollectedAt: time.Date(2026, 8, 24, 0, 0, 0, 0, time.UTC)} + parseXidAndFaultEvents(content, result) + + sxids := eventsByType(result.Events, "NVSwitch SXid Error") + if len(sxids) != 1 { + t.Fatalf("expected 1 SXid event, got %d", len(sxids)) + } +} + +func TestParseXidAndFaultEvents_AggregatesNVLinkFloodIntoOneEvent(t *testing.T) { + var lines []string + for i := 0; i < 500; i++ { + lines = append(lines, "Aug 24 08:49:26 host kernel: NVRM: knvlinkUpdatePostRxDetectLinkMask_IMPL: Failed to update Rx Detect Link mask!") + } + content := strings.Join(lines, "\n") + + result := &models.AnalysisResult{CollectedAt: time.Date(2026, 8, 24, 13, 0, 0, 0, time.UTC)} + parseXidAndFaultEvents(content, result) + + floods := eventsByType(result.Events, "NVLink Rx Detect Failure Burst") + if len(floods) != 1 { + t.Fatalf("expected exactly 1 aggregated flood event for 500 lines, got %d", len(floods)) + } + if floods[0].Severity != models.SeverityCritical { + t.Fatalf("expected flood of 500 to be critical, got %s", floods[0].Severity) + } + if !strings.Contains(floods[0].Description, "500 times") { + t.Fatalf("expected description to mention count, got %q", floods[0].Description) + } +} + +// TestParseXidAndFaultEvents_FloodTimeRangeIsChronologicalNotScanOrder is a +// regression test: a real nvidia-bug-report.sh dump concatenates multiple +// overlapping log sources (e.g. dmesg -T and journalctl excerpts), so a +// later line in the file is not guaranteed to be later in time. The flood +// summary's first/last timestamps must reflect the true chronological +// min/max of all matched lines, not the order they were scanned in. +func TestParseXidAndFaultEvents_FloodTimeRangeIsChronologicalNotScanOrder(t *testing.T) { + content := strings.Join([]string{ + // Appears first in the file but is chronologically the latest. + "Aug 24 08:49:26 host kernel: NVRM: knvlinkUpdatePostRxDetectLinkMask_IMPL: Failed to update Rx Detect Link mask!", + // Appears later in the file (a different, earlier-covering log section) but predates the line above. + "Aug 19 21:30:00 host kernel: NVRM: knvlinkUpdatePostRxDetectLinkMask_IMPL: Failed to update Rx Detect Link mask!", + }, "\n") + + result := &models.AnalysisResult{CollectedAt: time.Date(2026, 8, 24, 13, 0, 0, 0, time.UTC)} + parseXidAndFaultEvents(content, result) + + floods := eventsByType(result.Events, "NVLink Rx Detect Failure Burst") + if len(floods) != 1 { + t.Fatalf("expected 1 aggregated flood event, got %d", len(floods)) + } + if !strings.Contains(floods[0].Description, "2026-08-19") { + t.Fatalf("expected description to mention the true earliest date (2026-08-19), got %q", floods[0].Description) + } +} + +func TestParseXidAndFaultEvents_FellOffBus(t *testing.T) { + content := "Aug 24 08:00:00 host kernel: NVRM: GPU 0000:1a:00.0: GPU has fallen off the bus." + + result := &models.AnalysisResult{CollectedAt: time.Date(2026, 8, 24, 13, 0, 0, 0, time.UTC)} + parseXidAndFaultEvents(content, result) + + events := eventsByType(result.Events, "GPU Fell Off The Bus") + if len(events) != 1 { + t.Fatalf("expected 1 fell-off-bus event, got %d", len(events)) + } + if events[0].Severity != models.SeverityCritical { + t.Fatalf("expected critical severity, got %s", events[0].Severity) + } +} + +func TestParseXidAndFaultEvents_WritesFaultStatusOntoGPURecord(t *testing.T) { + content := strings.Join([]string{ + "Jul 29 02:33:16 host kernel: NVRM: Xid (PCI:0000:dc:00): 31, pid=123, name=python, MMU Fault", + "Aug 19 21:17:46 host kernel: NVRM: Xid (PCI:0000:1a:00): 154, GPU recovery action changed from 0x0 (None) to 0x4 (Drain and Reset)", + "Aug 23 02:36:37 host kernel: NVRM: Xid (PCI:0000:1a:00): 154, GPU recovery action changed from 0x4 (Drain and Reset) to 0x1 (GPU Reset Required)", + }, "\n") + + result := &models.AnalysisResult{ + CollectedAt: time.Date(2026, 8, 24, 13, 56, 0, 0, time.UTC), + Hardware: &models.HardwareConfig{ + GPUs: []models.GPU{ + {BDF: "0000:1a:00.0", Model: "NVIDIA B200"}, + {BDF: "0000:dc:00.0", Model: "NVIDIA B200"}, + {BDF: "0000:3b:00.0", Model: "NVIDIA B200"}, // untouched — no Xid for this one + }, + }, + } + parseXidAndFaultEvents(content, result) + + faulted := result.Hardware.GPUs[0] + if faulted.Status != "critical" { + t.Fatalf("expected 0000:1a:00.0 status critical, got %q", faulted.Status) + } + if faulted.ErrorDescription == "" { + t.Fatalf("expected a non-empty error description") + } + if faulted.StatusChangedAt == nil || !faulted.StatusChangedAt.Equal(time.Date(2026, 8, 23, 2, 36, 37, 0, parser.DefaultArchiveLocation())) { + t.Fatalf("expected status_changed_at to be the latest fault timestamp, got %v", faulted.StatusChangedAt) + } + + warned := result.Hardware.GPUs[1] + if warned.Status != "warning" { + t.Fatalf("expected 0000:dc:00.0 status warning, got %q", warned.Status) + } + + untouched := result.Hardware.GPUs[2] + if untouched.Status != "" { + t.Fatalf("expected 0000:3b:00.0 to be untouched, got status %q", untouched.Status) + } +} + +func TestParseSyslogTimestamp_HandlesYearRollover(t *testing.T) { + // Report collected early January; a log line stamped "Dec 30" must be + // attributed to the previous year, not the future. + collectedAt := time.Date(2027, 1, 5, 12, 0, 0, 0, time.UTC) + ts := parseSyslogTimestamp("Dec 30 10:00:00", collectedAt.Year(), collectedAt) + if ts.Year() != 2026 { + t.Fatalf("expected rollover to previous year 2026, got %d", ts.Year()) + } +} diff --git a/internal/parser/vendors/nvidia_bug_report/parser.go b/internal/parser/vendors/nvidia_bug_report/parser.go index 3043aac..351a06b 100644 --- a/internal/parser/vendors/nvidia_bug_report/parser.go +++ b/internal/parser/vendors/nvidia_bug_report/parser.go @@ -13,7 +13,7 @@ import ( ) // parserVersion - version of this parser module -const parserVersion = "1.2" +const parserVersion = "1.3" var bugReportDateLineRegex = regexp.MustCompile(`(?m)^Date:\s+(.+?)\s*$`) var dateWithTZAbbrevRegex = regexp.MustCompile(`^([A-Za-z]{3}\s+[A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})\s+([A-Za-z]{2,5})\s+(\d{4})$`) @@ -126,6 +126,9 @@ func (p *Parser) Parse(files []parser.ExtractedFile) (*models.AnalysisResult, er // Parse driver version parseDriverVersion(content, result) + // Parse Xid/SXid GPU error reports and other known hardware fault patterns + parseXidAndFaultEvents(content, result) + return result, nil } diff --git a/internal/parser/vendors/nvidia_bug_report/psu.go b/internal/parser/vendors/nvidia_bug_report/psu.go index df11c1f..2cc6591 100644 --- a/internal/parser/vendors/nvidia_bug_report/psu.go +++ b/internal/parser/vendors/nvidia_bug_report/psu.go @@ -32,6 +32,7 @@ func parsePSUInfo(content string, result *models.AnalysisResult) { if currentPSU != nil && currentPSU.Slot != "" { // Only add if PSU is present if strings.Contains(strings.ToLower(currentPSU.Status), "present") { + currentPSU.Present = true result.Hardware.PowerSupply = append(result.Hardware.PowerSupply, *currentPSU) } } @@ -91,6 +92,7 @@ func parsePSUInfo(content string, result *models.AnalysisResult) { // Save last PSU if exists if currentPSU != nil && currentPSU.Slot != "" { if strings.Contains(strings.ToLower(currentPSU.Status), "present") { + currentPSU.Present = true result.Hardware.PowerSupply = append(result.Hardware.PowerSupply, *currentPSU) } }