// Package nvidia_bug_report provides parser for NVIDIA bug report files // Generated by nvidia-bug-report.sh script package nvidia_bug_report import ( "fmt" "regexp" "strings" "time" "git.mchus.pro/mchus/logpile/internal/models" "git.mchus.pro/mchus/logpile/internal/parser" ) // parserVersion - version of this parser module const parserVersion = "1.2" 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})$`) var timezoneAbbrevToOffset = map[string]string{ "UTC": "+00:00", "GMT": "+00:00", "EST": "-05:00", "EDT": "-04:00", "CST": "-06:00", "CDT": "-05:00", "MST": "-07:00", "MDT": "-06:00", "PST": "-08:00", "PDT": "-07:00", } func init() { parser.Register(&Parser{}) } // Parser implements VendorParser for NVIDIA bug reports type Parser struct{} // Name returns human-readable parser name func (p *Parser) Name() string { return "NVIDIA Bug Report Parser" } // Vendor returns vendor identifier func (p *Parser) Vendor() string { return "nvidia_bug_report" } // Version returns parser version func (p *Parser) Version() string { return parserVersion } // Detect checks if this is an NVIDIA bug report // Returns confidence 0-100 func (p *Parser) Detect(files []parser.ExtractedFile) int { // Only detect if there's exactly one file if len(files) != 1 { return 0 } file := files[0] // Check filename if !strings.Contains(strings.ToLower(file.Path), "nvidia-bug-report") { return 0 } // Check content markers content := string(file.Content) if !strings.Contains(content, "nvidia-bug-report.sh") || !strings.Contains(content, "NVIDIA bug report log file") { return 0 } // High confidence for nvidia-bug-report files return 85 } // Parse parses NVIDIA bug report file func (p *Parser) Parse(files []parser.ExtractedFile) (*models.AnalysisResult, error) { result := &models.AnalysisResult{ Events: make([]models.Event, 0), FRU: make([]models.FRUInfo, 0), Sensors: make([]models.SensorReading, 0), } // Initialize hardware config result.Hardware = &models.HardwareConfig{ CPUs: make([]models.CPU, 0), Memory: make([]models.MemoryDIMM, 0), GPUs: make([]models.GPU, 0), PowerSupply: make([]models.PSU, 0), } if len(files) == 0 { return result, nil } content := string(files[0].Content) if collectedAt, tzOffset, ok := parseBugReportCollectedAt(content); ok { result.CollectedAt = collectedAt.UTC() result.SourceTimezone = tzOffset } // Parse system information parseSystemInfo(content, result) // Parse CPU information parseCPUInfo(content, result) // Parse memory modules parseMemoryModules(content, result) // Parse power supplies parsePSUInfo(content, result) // Parse GPU information parseGPUInfo(content, result) // Parse network adapters parseNetworkAdapters(content, result) // Parse driver version parseDriverVersion(content, result) return result, nil } func parseBugReportCollectedAt(content string) (time.Time, string, bool) { matches := bugReportDateLineRegex.FindStringSubmatch(content) if len(matches) != 2 { return time.Time{}, "", false } raw := strings.TrimSpace(matches[1]) if raw == "" { return time.Time{}, "", false } if m := dateWithTZAbbrevRegex.FindStringSubmatch(raw); len(m) == 4 { if offset, ok := timezoneAbbrevToOffset[strings.ToUpper(strings.TrimSpace(m[2]))]; ok { layout := "Mon Jan 2 15:04:05 -07:00 2006" normalized := strings.TrimSpace(m[1]) + " " + offset + " " + strings.TrimSpace(m[3]) if ts, err := time.Parse(layout, normalized); err == nil { return ts, offset, true } } } layouts := []string{ "Mon Jan 2 15:04:05 MST 2006", "Mon Jan 2 15:04:05 2006", } for _, layout := range layouts { ts, err := time.Parse(layout, raw) if err != nil { continue } return ts, formatOffset(ts), true } return time.Time{}, "", false } func formatOffset(t time.Time) string { _, sec := t.Zone() sign := '+' if sec < 0 { sign = '-' sec = -sec } h := sec / 3600 m := (sec % 3600) / 60 return fmt.Sprintf("%c%02d:%02d", sign, h, m) }