fix(metrics): strip units from GPU legend names; fix fan SDR parsing for new IPMI format

Legend names were "GPU 0 %" — remove unit suffix since chart title already
conveys it. Fan parsing now handles the 5-field IPMI SDR format where the
value+unit ("4340 RPM") are combined in the last column rather than split
across separate fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 11:14:27 +03:00
parent a714c45f10
commit a3ed9473a3
2 changed files with 37 additions and 21 deletions

View File

@@ -322,7 +322,9 @@ func sampleFanSpeeds() ([]FanReading, error) {
} }
// parseFanSpeeds parses "ipmitool sdr type Fan" output. // parseFanSpeeds parses "ipmitool sdr type Fan" output.
// Line format: "FAN1 | 2400.000 | RPM | ok" // Handles two formats:
// Old: "FAN1 | 2400.000 | RPM | ok" (value in col[1], unit in col[2])
// New: "FAN1 | 41h | ok | 29.1 | 4340 RPM" (value+unit combined in last col)
func parseFanSpeeds(raw string) []FanReading { func parseFanSpeeds(raw string) []FanReading {
var fans []FanReading var fans []FanReading
for _, line := range strings.Split(strings.TrimSpace(raw), "\n") { for _, line := range strings.Split(strings.TrimSpace(raw), "\n") {
@@ -330,25 +332,39 @@ func parseFanSpeeds(raw string) []FanReading {
if len(parts) < 2 { if len(parts) < 2 {
continue continue
} }
unit := "" name := strings.TrimSpace(parts[0])
if len(parts) >= 3 { // Find the first field that contains "RPM" (either as a standalone unit or inline)
unit = strings.TrimSpace(parts[2]) rpmVal := 0.0
found := false
for _, p := range parts[1:] {
p = strings.TrimSpace(p)
if !strings.Contains(strings.ToUpper(p), "RPM") {
continue
}
if strings.EqualFold(p, "RPM") {
continue // unit-only column in old format; value is in previous field
}
val, err := parseFanRPMValue(p)
if err == nil {
rpmVal = val
found = true
break
}
} }
valStr := strings.TrimSpace(parts[1]) // Old format: unit "RPM" is in col[2], value is in col[1]
if !strings.EqualFold(unit, "RPM") && !strings.Contains(strings.ToUpper(valStr), "RPM") { if !found && len(parts) >= 3 && strings.EqualFold(strings.TrimSpace(parts[2]), "RPM") {
valStr := strings.TrimSpace(parts[1])
if !strings.EqualFold(valStr, "na") && !strings.EqualFold(valStr, "disabled") && valStr != "" {
if val, err := parseFanRPMValue(valStr); err == nil {
rpmVal = val
found = true
}
}
}
if !found {
continue continue
} }
if strings.EqualFold(valStr, "na") || strings.EqualFold(valStr, "disabled") || valStr == "" { fans = append(fans, FanReading{Name: name, RPM: rpmVal})
continue
}
val, err := parseFanRPMValue(valStr)
if err != nil {
continue
}
fans = append(fans, FanReading{
Name: strings.TrimSpace(parts[0]),
RPM: val,
})
} }
return fans return fans
} }

View File

@@ -459,7 +459,7 @@ func (h *handler) handleMetricsChartSVG(w http.ResponseWriter, r *http.Request)
} }
vUtil, l := gr.Util.snapshot() vUtil, l := gr.Util.snapshot()
datasets = append(datasets, vUtil) datasets = append(datasets, vUtil)
names = append(names, fmt.Sprintf("GPU %d %%", idx)) names = append(names, fmt.Sprintf("GPU %d", idx))
if len(labels) == 0 { if len(labels) == 0 {
labels = l labels = l
} }
@@ -477,7 +477,7 @@ func (h *handler) handleMetricsChartSVG(w http.ResponseWriter, r *http.Request)
} }
vMem, l := gr.MemUtil.snapshot() vMem, l := gr.MemUtil.snapshot()
datasets = append(datasets, vMem) datasets = append(datasets, vMem)
names = append(names, fmt.Sprintf("GPU %d %%", idx)) names = append(names, fmt.Sprintf("GPU %d", idx))
if len(labels) == 0 { if len(labels) == 0 {
labels = l labels = l
} }
@@ -495,7 +495,7 @@ func (h *handler) handleMetricsChartSVG(w http.ResponseWriter, r *http.Request)
} }
vPow, l := gr.Power.snapshot() vPow, l := gr.Power.snapshot()
datasets = append(datasets, vPow) datasets = append(datasets, vPow)
names = append(names, fmt.Sprintf("GPU %d W", idx)) names = append(names, fmt.Sprintf("GPU %d", idx))
if len(labels) == 0 { if len(labels) == 0 {
labels = l labels = l
} }
@@ -513,7 +513,7 @@ func (h *handler) handleMetricsChartSVG(w http.ResponseWriter, r *http.Request)
} }
vTemp, l := gr.Temp.snapshot() vTemp, l := gr.Temp.snapshot()
datasets = append(datasets, vTemp) datasets = append(datasets, vTemp)
names = append(names, fmt.Sprintf("GPU %d °C", idx)) names = append(names, fmt.Sprintf("GPU %d", idx))
if len(labels) == 0 { if len(labels) == 0 {
labels = l labels = l
} }