// Package inspur provides parser for Inspur/Kaytus BMC diagnostic archives // Tested with: Inspur NF5468M7 / Kaytus KR4268X2 (onekeylog format) // // IMPORTANT: Increment parserVersion when modifying parser logic! // This helps track which version was used to parse specific logs. package inspur import ( "fmt" "strings" "git.mchus.pro/mchus/logpile/internal/models" "git.mchus.pro/mchus/logpile/internal/parser" ) // parserVersion - version of this parser module // IMPORTANT: Increment this version when making changes to parser logic! const parserVersion = "1.0.0" func init() { parser.Register(&Parser{}) } // Parser implements VendorParser for Inspur/Kaytus servers type Parser struct{} // Name returns human-readable parser name func (p *Parser) Name() string { return "Inspur/Kaytus BMC Parser" } // Vendor returns vendor identifier func (p *Parser) Vendor() string { return "inspur" } // Version returns parser version // IMPORTANT: Update parserVersion constant when modifying parser logic! func (p *Parser) Version() string { return parserVersion } // Detect checks if archive matches Inspur/Kaytus format // Returns confidence 0-100 func (p *Parser) Detect(files []parser.ExtractedFile) int { confidence := 0 for _, f := range files { path := strings.ToLower(f.Path) // Strong indicators for Inspur/Kaytus onekeylog format if strings.Contains(path, "onekeylog/") { confidence += 30 } if strings.Contains(path, "devicefrusdr.log") { confidence += 25 } if strings.Contains(path, "component/component.log") { confidence += 15 } // Check for asset.json with Inspur-specific structure if strings.HasSuffix(path, "asset.json") { if containsInspurMarkers(f.Content) { confidence += 20 } } // Cap at 100 if confidence >= 100 { return 100 } } return confidence } // containsInspurMarkers checks if content has Inspur-specific markers func containsInspurMarkers(content []byte) bool { s := string(content) // Check for typical Inspur asset.json structure return strings.Contains(s, "VersionInfo") && strings.Contains(s, "CpuInfo") && strings.Contains(s, "MemInfo") } // Parse parses Inspur/Kaytus archive 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), } // Parse asset.json first (base hardware info) if f := parser.FindFileByName(files, "asset.json"); f != nil { if hw, err := ParseAssetJSON(f.Content); err == nil { result.Hardware = hw } } // Extract BoardInfo from FRU data if result.Hardware == nil { result.Hardware = &models.HardwareConfig{} } // Parse devicefrusdr.log (contains SDR, FRU, PCIe and additional data) if f := parser.FindFileByName(files, "devicefrusdr.log"); f != nil { p.parseDeviceFruSDR(f.Content, result) } extractBoardInfo(result.FRU, result.Hardware) // Extract PlatformId (server model) from ThermalConfig if f := parser.FindFileByName(files, "ThermalConfig_Cur.conf"); f != nil { extractPlatformId(f.Content, result.Hardware) } // Parse component.log for additional data (PSU, etc.) if f := parser.FindFileByName(files, "component.log"); f != nil { ParseComponentLog(f.Content, result.Hardware) // Extract events from component.log (memory errors, etc.) componentEvents := ParseComponentLogEvents(f.Content) result.Events = append(result.Events, componentEvents...) } // Parse IDL log (BMC alarms/diagnose events) if f := parser.FindFileByName(files, "idl.log"); f != nil { idlEvents := ParseIDLLog(f.Content) result.Events = append(result.Events, idlEvents...) } // Parse SEL list (selelist.csv) if f := parser.FindFileByName(files, "selelist.csv"); f != nil { selEvents := ParseSELList(f.Content) result.Events = append(result.Events, selEvents...) } // Parse syslog files syslogFiles := parser.FindFileByPattern(files, "syslog/alert", "syslog/warning", "syslog/notice", "syslog/info") for _, f := range syslogFiles { events := ParseSyslog(f.Content, f.Path) result.Events = append(result.Events, events...) } return result, nil } func (p *Parser) parseDeviceFruSDR(content []byte, result *models.AnalysisResult) { lines := string(content) // Find SDR section sdrStart := strings.Index(lines, "BMC sdr Info:") fruStart := strings.Index(lines, "BMC fru Info:") if sdrStart != -1 { var sdrContent string if fruStart != -1 && fruStart > sdrStart { sdrContent = lines[sdrStart:fruStart] } else { sdrContent = lines[sdrStart:] } result.Sensors = ParseSDR([]byte(sdrContent)) } // Find FRU section if fruStart != -1 { fruContent := lines[fruStart:] result.FRU = ParseFRU([]byte(fruContent)) } // Parse PCIe devices from RESTful PCIE Device info // This supplements data from asset.json with serial numbers, firmware, etc. pcieDevicesFromREST := ParsePCIeDevices(content) // Merge PCIe data: keep asset.json data but add RESTful data if available if result.Hardware != nil { // If asset.json didn't have PCIe devices, use RESTful data if len(result.Hardware.PCIeDevices) == 0 && len(pcieDevicesFromREST) > 0 { result.Hardware.PCIeDevices = pcieDevicesFromREST } // If we have both, merge them (RESTful data takes precedence for detailed info) // For now, we keep asset.json data which has more details } // Parse GPU devices and add temperature data from sensors if len(result.Sensors) > 0 && result.Hardware != nil { // Use existing GPU data from asset.json and enrich with sensor data for i := range result.Hardware.GPUs { gpu := &result.Hardware.GPUs[i] // Extract GPU number from slot name slotNum := extractSlotNumberFromGPU(gpu.Slot) // Find temperature sensors for this GPU for _, sensor := range result.Sensors { sensorName := strings.ToUpper(sensor.Name) // Match GPU temperature sensor if strings.Contains(sensorName, fmt.Sprintf("GPU%d_TEMP", slotNum)) && !strings.Contains(sensorName, "MEM") { if sensor.RawValue != "" { fmt.Sscanf(sensor.RawValue, "%d", &gpu.Temperature) } } // Match GPU memory temperature if strings.Contains(sensorName, fmt.Sprintf("GPU%d_MEM_TEMP", slotNum)) { if sensor.RawValue != "" { fmt.Sscanf(sensor.RawValue, "%d", &gpu.MemTemperature) } } // Match PCIe slot temperature as fallback if strings.Contains(sensorName, fmt.Sprintf("PCIE%d_GPU_TLM_T", slotNum)) && gpu.Temperature == 0 { if sensor.RawValue != "" { fmt.Sscanf(sensor.RawValue, "%d", &gpu.Temperature) } } } } } } // extractSlotNumberFromGPU extracts slot number from GPU slot string func extractSlotNumberFromGPU(slot string) int { parts := strings.Split(slot, "_") for _, part := range parts { if strings.HasPrefix(part, "PCIE") { var num int fmt.Sscanf(part, "PCIE%d", &num) if num > 0 { return num } } } return 0 }