gpu: collect reset-required/row-remap status and classify Xid codes by severity

nvidia-smi exposes reset_status.reset_required and remapped_rows.* only on
newer drivers for Ampere+ GPUs; queried via a separate exec call since an
unrecognized field name fails the whole --query-gpu command and would have
wiped out unrelated telemetry (temp/ECC/power) on older drivers otherwise.

Also refines Xid severity in both the ingest dmesg collector and the
always-on kmsg watcher: Xid 64 (row-remap InfoROM write failure) now
escalates to Critical instead of the generic warning every other Xid got,
and fixes the SAT-window flush path which previously hardcoded "Warning"
and ignored pattern severity entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-07-07 19:28:59 +03:00
co-authored by Claude Sonnet 5
parent d850d4fc3a
commit 1175e6ccd8
10 changed files with 477 additions and 69 deletions
+32 -24
View File
@@ -10,6 +10,7 @@ import (
"time"
"bee/audit/internal/app"
"bee/audit/internal/collector"
"bee/audit/internal/platform"
)
@@ -40,6 +41,7 @@ type kmsgEvent struct {
raw string
ids []string // BDF addresses or device names extracted
category string
severity string // "warning" or "critical", resolved once at parse time
}
func newKmsgWatcher(statusDB *app.ComponentStatusDB) *kmsgWatcher {
@@ -148,8 +150,18 @@ func (w *kmsgWatcher) flushWindow(window *kmsgWindow) {
return
}
source := "watchdog:kmsg"
// Collect unique component keys from events.
seen := map[string]string{} // componentKey → first raw line
// Collect unique component keys from events, escalating to Critical if any
// matching event for that component was critical (e.g. Xid 64 remap failure).
seen := map[string]string{} // componentKey → first raw line
severities := map[string]string{} // componentKey → worst severity seen
record := func(key string, evt kmsgEvent) {
if _, exists := seen[key]; !exists {
seen[key] = evt.raw
severities[key] = capitalizeSeverity(evt.severity)
} else if evt.severity == "critical" {
severities[key] = "Critical"
}
}
for _, evt := range window.events {
if len(evt.ids) == 0 {
// MCE or un-identified error.
@@ -157,9 +169,7 @@ func (w *kmsgWatcher) flushWindow(window *kmsgWindow) {
if evt.category == "memory" {
key = "memory:all"
}
if _, exists := seen[key]; !exists {
seen[key] = evt.raw
}
record(key, evt)
continue
}
for _, id := range evt.ids {
@@ -174,14 +184,12 @@ func (w *kmsgWatcher) flushWindow(window *kmsgWindow) {
default:
key = "pcie:" + normalizeBDF(id)
}
if _, exists := seen[key]; !exists {
seen[key] = evt.raw
}
record(key, evt)
}
}
for key, detail := range seen {
detail = "kernel error during SAT (" + strings.Join(window.targets, ",") + "): " + truncate(detail, 120)
w.statusDB.Record(key, source, "Warning", detail)
w.statusDB.Record(key, source, severities[key], detail)
}
}
@@ -193,21 +201,7 @@ func (w *kmsgWatcher) flushImmediate(evt kmsgEvent) {
}
const source = "watchdog:kmsg"
detail := "kernel: " + truncate(evt.raw, 120)
var severity string
for _, p := range platform.HardwareErrorPatterns {
if p.Re.MatchString(evt.raw) {
if p.Severity == "critical" {
severity = "Critical"
} else {
severity = "Warning"
}
break
}
}
if severity == "" {
severity = "Warning"
}
severity := capitalizeSeverity(evt.severity)
if len(evt.ids) == 0 {
key := "cpu:all"
@@ -250,10 +244,15 @@ func parseKmsgLine(raw string) (kmsgEvent, bool) {
if m == nil {
continue
}
severity := p.Severity
if sev, ok := collector.XidSeverity(msg); ok {
severity = sev
}
evt := kmsgEvent{
timestamp: time.Now(),
raw: msg,
category: p.Category,
severity: severity,
}
if p.BDFGroup > 0 && p.BDFGroup < len(m) {
evt.ids = append(evt.ids, normalizeBDF(m[p.BDFGroup]))
@@ -266,6 +265,15 @@ func parseKmsgLine(raw string) (kmsgEvent, bool) {
return kmsgEvent{}, false
}
// capitalizeSeverity converts an ErrorPattern severity ("warning"/"critical")
// to the Title-case convention ComponentStatusDB.Record expects.
func capitalizeSeverity(s string) string {
if s == "critical" {
return "Critical"
}
return "Warning"
}
// normalizeBDF normalizes a PCIe BDF to the 4-part form "0000:c8:00.0".
func normalizeBDF(bdf string) string {
bdf = strings.ToLower(strings.TrimSpace(bdf))