Add LOGPile BMC diagnostic log analyzer
Features: - Modular parser architecture for vendor-specific formats - Inspur/Kaytus parser supporting asset.json, devicefrusdr.log, component.log, idl.log, and syslog files - PCI Vendor/Device ID lookup for hardware identification - Web interface with tabs: Events, Sensors, Config, Serials, Firmware - Server specification summary with component grouping - Export to CSV, JSON, TXT formats - BMC alarm parsing from IDL logs (memory errors, PSU events, etc.) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
352
internal/parser/vendors/inspur/asset.go
vendored
Normal file
352
internal/parser/vendors/inspur/asset.go
vendored
Normal file
@@ -0,0 +1,352 @@
|
||||
package inspur
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
|
||||
)
|
||||
|
||||
// AssetJSON represents the structure of Inspur asset.json file
|
||||
type AssetJSON struct {
|
||||
VersionInfo []struct {
|
||||
DeviceID int `json:"DeviceId"`
|
||||
DeviceName string `json:"DeviceName"`
|
||||
DeviceRevision string `json:"DeviceRevision"`
|
||||
BuildTime string `json:"BuildTime"`
|
||||
} `json:"VersionInfo"`
|
||||
|
||||
CpuInfo []struct {
|
||||
ProcessorName string `json:"ProcessorName"`
|
||||
ProcessorID string `json:"ProcessorId"`
|
||||
MicroCodeVer string `json:"MicroCodeVer"`
|
||||
CurrentSpeed int `json:"CurrentSpeed"`
|
||||
Core int `json:"Core"`
|
||||
ThreadCount int `json:"ThreadCount"`
|
||||
L1Cache int `json:"L1Cache"`
|
||||
L2Cache int `json:"L2Cache"`
|
||||
L3Cache int `json:"L3Cache"`
|
||||
CpuTdp int `json:"CpuTdp"`
|
||||
PPIN string `json:"PPIN"`
|
||||
TurboEnableMaxSpeed int `json:"TurboEnableMaxSpeed"`
|
||||
TurboCloseMaxSpeed int `json:"TurboCloseMaxSpeed"`
|
||||
UPIBandwidth string `json:"UPIBandwidth"`
|
||||
} `json:"CpuInfo"`
|
||||
|
||||
MemInfo struct {
|
||||
MemCommonInfo []struct {
|
||||
Manufacturer string `json:"Manufacturer"`
|
||||
MaxSpeed int `json:"MaxSpeed"`
|
||||
CurrentSpeed int `json:"CurrentSpeed"`
|
||||
MemoryType int `json:"MemoryType"`
|
||||
Rank int `json:"Rank"`
|
||||
DataWidth int `json:"DataWidth"`
|
||||
ConfiguredVoltage int `json:"ConfiguredVoltage"`
|
||||
PhysicalSize int `json:"PhysicalSize"`
|
||||
} `json:"MemCommonInfo"`
|
||||
|
||||
DimmInfo []struct {
|
||||
SerialNumber string `json:"SerialNumber"`
|
||||
PartNumber string `json:"PartNumber"`
|
||||
AssetTag string `json:"AssetTag"`
|
||||
} `json:"DimmInfo"`
|
||||
} `json:"MemInfo"`
|
||||
|
||||
HddInfo []struct {
|
||||
SerialNumber string `json:"SerialNumber"`
|
||||
Manufacturer string `json:"Manufacturer"`
|
||||
ModelName string `json:"ModelName"`
|
||||
FirmwareVersion string `json:"FirmwareVersion"`
|
||||
Capacity int `json:"Capacity"`
|
||||
Location int `json:"Location"`
|
||||
DiskInterfaceType int `json:"DiskInterfaceType"`
|
||||
MediaType int `json:"MediaType"`
|
||||
LocationString string `json:"LocationString"`
|
||||
BlockSizeBytes int `json:"BlockSizeBytes"`
|
||||
CapableSpeedGbs string `json:"CapableSpeedGbs"`
|
||||
NegotiatedSpeedGbs string `json:"NegotiatedSpeedGbs"`
|
||||
PcieSlot int `json:"PcieSlot"`
|
||||
} `json:"HddInfo"`
|
||||
|
||||
PcieInfo []struct {
|
||||
VendorId int `json:"VendorId"`
|
||||
DeviceId int `json:"DeviceId"`
|
||||
BusNumber int `json:"BusNumber"`
|
||||
DeviceNumber int `json:"DeviceNumber"`
|
||||
FunctionNumber int `json:"FunctionNumber"`
|
||||
MaxLinkWidth int `json:"MaxLinkWidth"`
|
||||
MaxLinkSpeed int `json:"MaxLinkSpeed"`
|
||||
NegotiatedLinkWidth int `json:"NegotiatedLinkWidth"`
|
||||
CurrentLinkSpeed int `json:"CurrentLinkSpeed"`
|
||||
ClassCode int `json:"ClassCode"`
|
||||
SubClassCode int `json:"SubClassCode"`
|
||||
PcieSlot int `json:"PcieSlot"`
|
||||
LocString string `json:"LocString"`
|
||||
PartNumber *string `json:"PartNumber"`
|
||||
SerialNumber *string `json:"SerialNumber"`
|
||||
Mac []string `json:"Mac"`
|
||||
} `json:"PcieInfo"`
|
||||
}
|
||||
|
||||
// ParseAssetJSON parses Inspur asset.json content
|
||||
func ParseAssetJSON(content []byte) (*models.HardwareConfig, error) {
|
||||
var asset AssetJSON
|
||||
if err := json.Unmarshal(content, &asset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &models.HardwareConfig{}
|
||||
|
||||
// Parse version info
|
||||
for _, v := range asset.VersionInfo {
|
||||
config.Firmware = append(config.Firmware, models.FirmwareInfo{
|
||||
DeviceName: v.DeviceName,
|
||||
Version: v.DeviceRevision,
|
||||
BuildTime: v.BuildTime,
|
||||
})
|
||||
}
|
||||
|
||||
// Parse CPU info
|
||||
for i, cpu := range asset.CpuInfo {
|
||||
config.CPUs = append(config.CPUs, models.CPU{
|
||||
Socket: i,
|
||||
Model: strings.TrimSpace(cpu.ProcessorName),
|
||||
Cores: cpu.Core,
|
||||
Threads: cpu.ThreadCount,
|
||||
FrequencyMHz: cpu.CurrentSpeed,
|
||||
MaxFreqMHz: cpu.TurboEnableMaxSpeed,
|
||||
L1CacheKB: cpu.L1Cache,
|
||||
L2CacheKB: cpu.L2Cache,
|
||||
L3CacheKB: cpu.L3Cache,
|
||||
TDP: cpu.CpuTdp,
|
||||
PPIN: cpu.PPIN,
|
||||
})
|
||||
}
|
||||
|
||||
// Parse memory info
|
||||
if len(asset.MemInfo.MemCommonInfo) > 0 {
|
||||
common := asset.MemInfo.MemCommonInfo[0]
|
||||
for i, dimm := range asset.MemInfo.DimmInfo {
|
||||
config.Memory = append(config.Memory, models.MemoryDIMM{
|
||||
Slot: i,
|
||||
SizeMB: common.PhysicalSize * 1024,
|
||||
Type: memoryTypeToString(common.MemoryType),
|
||||
SpeedMHz: common.CurrentSpeed,
|
||||
Manufacturer: common.Manufacturer,
|
||||
SerialNumber: dimm.SerialNumber,
|
||||
PartNumber: strings.TrimSpace(dimm.PartNumber),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Parse storage info
|
||||
for _, hdd := range asset.HddInfo {
|
||||
storageType := "HDD"
|
||||
if hdd.DiskInterfaceType == 5 {
|
||||
storageType = "NVMe"
|
||||
} else if hdd.MediaType == 1 {
|
||||
storageType = "SSD"
|
||||
}
|
||||
|
||||
// Resolve manufacturer: try vendor ID first, then model name extraction
|
||||
modelName := strings.TrimSpace(hdd.ModelName)
|
||||
manufacturer := resolveManufacturer(hdd.Manufacturer, modelName)
|
||||
|
||||
config.Storage = append(config.Storage, models.Storage{
|
||||
Slot: hdd.LocationString,
|
||||
Type: storageType,
|
||||
Model: modelName,
|
||||
SizeGB: hdd.Capacity,
|
||||
SerialNumber: hdd.SerialNumber,
|
||||
Manufacturer: manufacturer,
|
||||
Firmware: hdd.FirmwareVersion,
|
||||
Interface: diskInterfaceToString(hdd.DiskInterfaceType),
|
||||
})
|
||||
}
|
||||
|
||||
// Parse PCIe info
|
||||
for _, pcie := range asset.PcieInfo {
|
||||
vendor, deviceName := pciids.DeviceInfo(pcie.VendorId, pcie.DeviceId)
|
||||
device := models.PCIeDevice{
|
||||
Slot: pcie.LocString,
|
||||
VendorID: pcie.VendorId,
|
||||
DeviceID: pcie.DeviceId,
|
||||
BDF: formatBDF(pcie.BusNumber, pcie.DeviceNumber, pcie.FunctionNumber),
|
||||
LinkWidth: pcie.NegotiatedLinkWidth,
|
||||
LinkSpeed: pcieLinkSpeedToString(pcie.CurrentLinkSpeed),
|
||||
MaxLinkWidth: pcie.MaxLinkWidth,
|
||||
MaxLinkSpeed: pcieLinkSpeedToString(pcie.MaxLinkSpeed),
|
||||
DeviceClass: pcieClassToString(pcie.ClassCode, pcie.SubClassCode),
|
||||
Manufacturer: vendor,
|
||||
}
|
||||
if pcie.PartNumber != nil {
|
||||
device.PartNumber = strings.TrimSpace(*pcie.PartNumber)
|
||||
}
|
||||
if pcie.SerialNumber != nil {
|
||||
device.SerialNumber = strings.TrimSpace(*pcie.SerialNumber)
|
||||
}
|
||||
if len(pcie.Mac) > 0 {
|
||||
device.MACAddresses = pcie.Mac
|
||||
}
|
||||
// Use device name from PCI IDs database if available
|
||||
if deviceName != "" {
|
||||
device.DeviceClass = deviceName
|
||||
}
|
||||
config.PCIeDevices = append(config.PCIeDevices, device)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func memoryTypeToString(memType int) string {
|
||||
switch memType {
|
||||
case 26:
|
||||
return "DDR4"
|
||||
case 34:
|
||||
return "DDR5"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func diskInterfaceToString(ifType int) string {
|
||||
switch ifType {
|
||||
case 4:
|
||||
return "SATA"
|
||||
case 5:
|
||||
return "NVMe"
|
||||
case 6:
|
||||
return "SAS"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func pcieLinkSpeedToString(speed int) string {
|
||||
switch speed {
|
||||
case 1:
|
||||
return "2.5 GT/s"
|
||||
case 2:
|
||||
return "5.0 GT/s"
|
||||
case 3:
|
||||
return "8.0 GT/s"
|
||||
case 4:
|
||||
return "16.0 GT/s"
|
||||
case 5:
|
||||
return "32.0 GT/s"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func pcieClassToString(classCode, subClass int) string {
|
||||
switch classCode {
|
||||
case 1:
|
||||
switch subClass {
|
||||
case 0:
|
||||
return "SCSI"
|
||||
case 1:
|
||||
return "IDE"
|
||||
case 4:
|
||||
return "RAID"
|
||||
case 6:
|
||||
return "SATA"
|
||||
case 7:
|
||||
return "SAS"
|
||||
case 8:
|
||||
return "NVMe"
|
||||
default:
|
||||
return "Storage"
|
||||
}
|
||||
case 2:
|
||||
return "Network"
|
||||
case 3:
|
||||
switch subClass {
|
||||
case 0:
|
||||
return "VGA"
|
||||
case 2:
|
||||
return "3D Controller"
|
||||
default:
|
||||
return "Display"
|
||||
}
|
||||
case 4:
|
||||
return "Multimedia"
|
||||
case 6:
|
||||
return "Bridge"
|
||||
case 12:
|
||||
return "Serial Bus"
|
||||
default:
|
||||
return "Other"
|
||||
}
|
||||
}
|
||||
|
||||
func formatBDF(bus, dev, fun int) string {
|
||||
return fmt.Sprintf("%02x:%02x.%x", bus, dev, fun)
|
||||
}
|
||||
|
||||
// resolveManufacturer resolves manufacturer name from various sources
|
||||
func resolveManufacturer(rawManufacturer, modelName string) string {
|
||||
raw := strings.TrimSpace(rawManufacturer)
|
||||
|
||||
// If it looks like a vendor ID (hex), try to resolve it
|
||||
if raw != "" {
|
||||
if name := pciids.VendorNameFromString(raw); name != "" {
|
||||
return name
|
||||
}
|
||||
// If not a vendor ID but looks like a real name (has letters), use it
|
||||
hasLetter := false
|
||||
for _, c := range raw {
|
||||
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') {
|
||||
hasLetter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasLetter && len(raw) > 2 {
|
||||
return raw
|
||||
}
|
||||
}
|
||||
|
||||
// Try to extract from model name
|
||||
return extractStorageManufacturer(modelName)
|
||||
}
|
||||
|
||||
// extractStorageManufacturer tries to extract manufacturer from model name
|
||||
func extractStorageManufacturer(model string) string {
|
||||
modelUpper := strings.ToUpper(model)
|
||||
|
||||
knownVendors := []struct {
|
||||
prefix string
|
||||
name string
|
||||
}{
|
||||
{"SAMSUNG", "Samsung"},
|
||||
{"KIOXIA", "KIOXIA"},
|
||||
{"TOSHIBA", "Toshiba"},
|
||||
{"WDC", "Western Digital"},
|
||||
{"WD", "Western Digital"},
|
||||
{"SEAGATE", "Seagate"},
|
||||
{"HGST", "HGST"},
|
||||
{"INTEL", "Intel"},
|
||||
{"MICRON", "Micron"},
|
||||
{"KINGSTON", "Kingston"},
|
||||
{"CRUCIAL", "Crucial"},
|
||||
{"SK HYNIX", "SK Hynix"},
|
||||
{"SKHYNIX", "SK Hynix"},
|
||||
{"SANDISK", "SanDisk"},
|
||||
{"LITEON", "Lite-On"},
|
||||
{"PLEXTOR", "Plextor"},
|
||||
{"ADATA", "ADATA"},
|
||||
{"TRANSCEND", "Transcend"},
|
||||
{"CORSAIR", "Corsair"},
|
||||
{"SOLIDIGM", "Solidigm"},
|
||||
}
|
||||
|
||||
for _, v := range knownVendors {
|
||||
if strings.HasPrefix(modelUpper, v.prefix) {
|
||||
return v.name
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user