storage SAT: fix NVMe SMART counters showing 0 for power-on hours/read/write
nvme-cli emits large 64-bit counters as JSON-quoted strings on some
versions; the disk-report text generator only handled bare numbers and
{lo,hi} objects, so power_on_hours/data_units_read/data_units_written
etc. silently parsed as 0 while the structured collector path already
handled this correctly. Unify both paths on a single exported
JSONInt64/NVMeSmartLog/NVMeIDCtrl type in collector/storage.go instead
of keeping two independent nvme-cli JSON parsers in sync.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"bee/audit/internal/collector"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
@@ -39,65 +40,22 @@ func GenerateDiskReportText(index int, devPath string, outputs map[string][]byte
|
||||
|
||||
// ── NVMe ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
type nvmeIdCtrl struct {
|
||||
ModelNumber string `json:"mn"`
|
||||
SerialNumber string `json:"sn"`
|
||||
Firmware string `json:"fr"`
|
||||
TotalCap uint64 `json:"tnvmcap"`
|
||||
NVMCap uint64 `json:"nvmcap"`
|
||||
}
|
||||
|
||||
// nvmeU64 handles both plain JSON numbers and {"lo":n,"hi":n} objects that
|
||||
// some nvme-cli versions emit for 128-bit counters.
|
||||
func nvmeU64(raw json.RawMessage) uint64 {
|
||||
if len(raw) == 0 {
|
||||
return 0
|
||||
}
|
||||
var n uint64
|
||||
if json.Unmarshal(raw, &n) == nil {
|
||||
return n
|
||||
}
|
||||
var obj struct {
|
||||
Lo uint64 `json:"lo"`
|
||||
Hi uint64 `json:"hi"`
|
||||
}
|
||||
if json.Unmarshal(raw, &obj) == nil {
|
||||
return obj.Lo
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type nvmeSmartLogRaw struct {
|
||||
CriticalWarning uint64 `json:"critical_warning"`
|
||||
Temperature json.RawMessage `json:"temperature"`
|
||||
AvailSpare uint64 `json:"avail_spare"`
|
||||
SpareThresh uint64 `json:"spare_thresh"`
|
||||
PercentUsed uint64 `json:"percent_used"`
|
||||
DataUnitsRead json.RawMessage `json:"data_units_read"`
|
||||
DataUnitsWritten json.RawMessage `json:"data_units_written"`
|
||||
PowerCycles json.RawMessage `json:"power_cycles"`
|
||||
PowerOnHours json.RawMessage `json:"power_on_hours"`
|
||||
UnsafeShutdowns json.RawMessage `json:"unsafe_shutdowns"`
|
||||
MediaErrors json.RawMessage `json:"media_errors"`
|
||||
NumErrLogEntries json.RawMessage `json:"num_err_log_entries"`
|
||||
}
|
||||
|
||||
func writeNVMeReport(b *strings.Builder, outputs map[string][]byte) {
|
||||
// id-ctrl
|
||||
var ctrl nvmeIdCtrl
|
||||
var ctrl collector.NVMeIDCtrl
|
||||
if data := outputs["nvme-id-ctrl"]; len(data) > 0 {
|
||||
_ = json.Unmarshal(data, &ctrl)
|
||||
}
|
||||
|
||||
model := strings.TrimSpace(ctrl.ModelNumber)
|
||||
serial := strings.TrimSpace(ctrl.SerialNumber)
|
||||
firmware := strings.TrimSpace(ctrl.Firmware)
|
||||
firmware := strings.TrimSpace(ctrl.FirmwareRev)
|
||||
|
||||
capacityGB := ""
|
||||
if ctrl.TotalCap > 0 {
|
||||
capacityGB = formatCapacityGB(ctrl.TotalCap)
|
||||
} else if ctrl.NVMCap > 0 {
|
||||
capacityGB = formatCapacityGB(ctrl.NVMCap)
|
||||
if ctrl.TotalCapacity > 0 {
|
||||
capacityGB = formatCapacityGB(uint64(ctrl.TotalCapacity))
|
||||
} else if ctrl.NVMCapacity > 0 {
|
||||
capacityGB = formatCapacityGB(uint64(ctrl.NVMCapacity))
|
||||
}
|
||||
|
||||
writeField(b, "Model", model)
|
||||
@@ -113,39 +71,37 @@ func writeNVMeReport(b *strings.Builder, outputs map[string][]byte) {
|
||||
b.WriteString("\n(no SMART data)\n")
|
||||
return
|
||||
}
|
||||
var sl nvmeSmartLogRaw
|
||||
var sl collector.NVMeSmartLog
|
||||
if err := json.Unmarshal(data, &sl); err != nil {
|
||||
fmt.Fprintf(b, "\n(SMART parse error: %v)\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
tempK := nvmeU64(sl.Temperature)
|
||||
tempC := int(tempK) - 273
|
||||
tempC := int(sl.Temperature) - 273
|
||||
if tempC < 0 {
|
||||
tempC = 0
|
||||
}
|
||||
|
||||
critWarn := sl.CriticalWarning
|
||||
critWarnStr := "OK"
|
||||
if critWarn != 0 {
|
||||
critWarnStr = fmt.Sprintf("0x%02X", critWarn)
|
||||
if sl.CriticalWarning != 0 {
|
||||
critWarnStr = fmt.Sprintf("0x%02X", sl.CriticalWarning)
|
||||
}
|
||||
|
||||
poh := nvmeU64(sl.PowerOnHours)
|
||||
pc := nvmeU64(sl.PowerCycles)
|
||||
us := nvmeU64(sl.UnsafeShutdowns)
|
||||
me := nvmeU64(sl.MediaErrors)
|
||||
nel := nvmeU64(sl.NumErrLogEntries)
|
||||
poh := uint64(sl.PowerOnHours)
|
||||
pc := uint64(sl.PowerCycles)
|
||||
us := uint64(sl.UnsafeShutdowns)
|
||||
me := uint64(sl.MediaErrors)
|
||||
nel := uint64(sl.NumErrLogEntries)
|
||||
|
||||
// data_units are in 1000 × 512-byte sectors = 512,000 bytes each
|
||||
dataRead := float64(nvmeU64(sl.DataUnitsRead)) * 512000 / 1e9
|
||||
dataWritten := float64(nvmeU64(sl.DataUnitsWritten)) * 512000 / 1e9
|
||||
dataRead := float64(sl.DataUnitsRead) * 512000 / 1e9
|
||||
dataWritten := float64(sl.DataUnitsWritten) * 512000 / 1e9
|
||||
|
||||
writeSectionHeader(b, "Health")
|
||||
writeField(b, "Temperature", fmt.Sprintf("%d °C", tempC))
|
||||
writeField(b, "Critical Warning", critWarnStr)
|
||||
writeField(b, "Percentage Used", fmt.Sprintf("%d %%", sl.PercentUsed))
|
||||
writeField(b, "Available Spare", fmt.Sprintf("%d %% (threshold: %d %%)", sl.AvailSpare, sl.SpareThresh))
|
||||
writeField(b, "Percentage Used", fmt.Sprintf("%d %%", sl.PercentageUsed))
|
||||
writeField(b, "Available Spare", fmt.Sprintf("%d %% (threshold: %d %%)", sl.AvailableSpare, sl.SpareThreshold))
|
||||
|
||||
writeSectionHeader(b, "Usage")
|
||||
writeField(b, "Power On Hours", fmt.Sprintf("%s h", formatUint(poh)))
|
||||
@@ -158,14 +114,14 @@ func writeNVMeReport(b *strings.Builder, outputs map[string][]byte) {
|
||||
writeField(b, "Media Errors", formatUint(me))
|
||||
writeField(b, "Error Log Entries", formatUint(nel))
|
||||
|
||||
capacityBytes := ctrl.TotalCap
|
||||
capacityBytes := uint64(ctrl.TotalCapacity)
|
||||
if capacityBytes == 0 {
|
||||
capacityBytes = ctrl.NVMCap
|
||||
capacityBytes = uint64(ctrl.NVMCapacity)
|
||||
}
|
||||
writeResourceSection(b, resourceInfo{
|
||||
powerOnHours: poh,
|
||||
writtenBytes: uint64(nvmeU64(sl.DataUnitsWritten)) * 512000,
|
||||
readBytes: uint64(nvmeU64(sl.DataUnitsRead)) * 512000,
|
||||
writtenBytes: uint64(sl.DataUnitsWritten) * 512000,
|
||||
readBytes: uint64(sl.DataUnitsRead) * 512000,
|
||||
capacityBytes: capacityBytes,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user