Files
logpile/internal/parser/vendors/inspur/sdr.go
Mikhail Chusavitin 21f4e5a67e v1.2.0: Enhanced Inspur/Kaytus parser with GPU, PCIe, and storage support
Major improvements:
- Add CSV SEL event parser for Kaytus firmware format
- Add PCIe device parser with link speed/width detection
- Add GPU temperature and PCIe link monitoring
- Add disk backplane parser for storage bay information
- Fix memory module detection (only show installed DIMMs)

Parser enhancements:
- Parse RESTful PCIe Device info (max/current link width/speed)
- Parse GPU sensor data (core and memory temperatures)
- Parse diskbackplane info (slot count, installed drives)
- Parse SEL events from CSV format (selelist.csv)
- Fix memory Present status logic (check mem_mod_status)

Web interface improvements:
- Add PCIe link degradation highlighting (red when current < max)
- Add storage table with Present status and location
- Update memory specification to show only installed modules with frequency
- Sort events from newest to oldest
- Filter out N/A serial numbers from display

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-30 12:30:18 +03:00

91 lines
2.6 KiB
Go

package inspur
import (
"bufio"
"regexp"
"strconv"
"strings"
"git.mchus.pro/mchus/logpile/internal/models"
)
// SDR sensor reading patterns
var (
sdrLineRegex = regexp.MustCompile(`^(\S+)\s+\|\s+(.+?)\s+\|\s+(\w+)$`)
valueRegex = regexp.MustCompile(`^([\d.]+)\s+(.+)$`)
)
// ParseSDR parses BMC SDR (Sensor Data Record) output
func ParseSDR(content []byte) []models.SensorReading {
var readings []models.SensorReading
scanner := bufio.NewScanner(strings.NewReader(string(content)))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "BMC sdr Info:") {
continue
}
matches := sdrLineRegex.FindStringSubmatch(line)
if matches == nil {
continue
}
name := strings.TrimSpace(matches[1])
valueStr := strings.TrimSpace(matches[2])
status := strings.TrimSpace(matches[3])
reading := models.SensorReading{
Name: name,
Status: status,
}
// Parse value and unit
if valueStr != "disabled" && valueStr != "no reading" && !strings.HasPrefix(valueStr, "0x") {
if vm := valueRegex.FindStringSubmatch(valueStr); vm != nil {
if v, err := strconv.ParseFloat(vm[1], 64); err == nil {
reading.Value = v
reading.Unit = strings.TrimSpace(vm[2])
reading.RawValue = valueStr // Keep original string for reference
}
}
} else if strings.HasPrefix(valueStr, "0x") {
reading.RawValue = valueStr
}
// Determine sensor type
reading.Type = determineSensorType(name)
readings = append(readings, reading)
}
return readings
}
func determineSensorType(name string) string {
nameLower := strings.ToLower(name)
switch {
case strings.Contains(nameLower, "temp"):
return "temperature"
case strings.Contains(nameLower, "fan") && strings.Contains(nameLower, "speed"):
return "fan_speed"
case strings.Contains(nameLower, "fan") && strings.Contains(nameLower, "status"):
return "fan_status"
case strings.HasSuffix(nameLower, "_vin") || strings.HasSuffix(nameLower, "_vout") ||
strings.HasSuffix(nameLower, "_v") || strings.Contains(nameLower, "volt"):
return "voltage"
case strings.Contains(nameLower, "power") || strings.HasSuffix(nameLower, "_pin") ||
strings.HasSuffix(nameLower, "_pout") || strings.HasSuffix(nameLower, "_pwr"):
return "power"
case strings.Contains(nameLower, "psu") && strings.Contains(nameLower, "status"):
return "psu_status"
case strings.Contains(nameLower, "cpu") && strings.Contains(nameLower, "status"):
return "cpu_status"
case strings.Contains(nameLower, "hdd") || strings.Contains(nameLower, "nvme"):
return "storage_status"
default:
return "other"
}
}