|
|
|
@@ -84,16 +84,19 @@ func collectStorage() []schema.HardwareStorage {
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// jsonInt64 accepts both a bare JSON number and a JSON-quoted number string.
|
|
|
|
|
// lsblk -J emits LOG-SEC / PHY-SEC as integers on util-linux ≥ 2.37 (Debian 12)
|
|
|
|
|
// but older versions emit them as strings. This type handles both.
|
|
|
|
|
type jsonInt64 int64
|
|
|
|
|
// JSONInt64 accepts a bare JSON number (512), a JSON-quoted number string
|
|
|
|
|
// ("512" — lsblk -J on util-linux < 2.37, and nvme-cli for large 64-bit
|
|
|
|
|
// counters that would lose precision as JS numbers), or a {"lo":n,"hi":n}
|
|
|
|
|
// object (128-bit NVMe counters on some nvme-cli versions; hi is ignored as
|
|
|
|
|
// no real counter exceeds 64 bits). Shared by lsblk and nvme-cli JSON output
|
|
|
|
|
// across the collector and the human-readable disk report.
|
|
|
|
|
type JSONInt64 int64
|
|
|
|
|
|
|
|
|
|
func (j *jsonInt64) UnmarshalJSON(data []byte) error {
|
|
|
|
|
func (j *JSONInt64) UnmarshalJSON(data []byte) error {
|
|
|
|
|
// bare number: 512
|
|
|
|
|
var n int64
|
|
|
|
|
if err := json.Unmarshal(data, &n); err == nil {
|
|
|
|
|
*j = jsonInt64(n)
|
|
|
|
|
*j = JSONInt64(n)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
// quoted string: "512"
|
|
|
|
@@ -101,24 +104,32 @@ func (j *jsonInt64) UnmarshalJSON(data []byte) error {
|
|
|
|
|
if err := json.Unmarshal(data, &s); err == nil {
|
|
|
|
|
n, err := strconv.ParseInt(strings.TrimSpace(s), 10, 64)
|
|
|
|
|
if err == nil {
|
|
|
|
|
*j = jsonInt64(n)
|
|
|
|
|
*j = JSONInt64(n)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
// {"lo":n,"hi":n} 128-bit counter object
|
|
|
|
|
var obj struct {
|
|
|
|
|
Lo int64 `json:"lo"`
|
|
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal(data, &obj); err == nil {
|
|
|
|
|
*j = JSONInt64(obj.Lo)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return nil // null or unexpected type — leave zero
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// lsblkDevice is a minimal lsblk JSON record.
|
|
|
|
|
type lsblkDevice struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
Size string `json:"size"`
|
|
|
|
|
Serial string `json:"serial"`
|
|
|
|
|
Model string `json:"model"`
|
|
|
|
|
Tran string `json:"tran"`
|
|
|
|
|
Hctl string `json:"hctl"`
|
|
|
|
|
LogSec jsonInt64 `json:"log-sec"`
|
|
|
|
|
PhySec jsonInt64 `json:"phy-sec"`
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
Size string `json:"size"`
|
|
|
|
|
Serial string `json:"serial"`
|
|
|
|
|
Model string `json:"model"`
|
|
|
|
|
Tran string `json:"tran"`
|
|
|
|
|
Hctl string `json:"hctl"`
|
|
|
|
|
LogSec JSONInt64 `json:"log-sec"`
|
|
|
|
|
PhySec JSONInt64 `json:"phy-sec"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type lsblkRoot struct {
|
|
|
|
@@ -423,32 +434,36 @@ func enrichWithSmartctl(dev lsblkDevice) schema.HardwareStorage {
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// nvmeSmartLog is the subset of `nvme smart-log -o json` output we care about.
|
|
|
|
|
// nvme-cli emits most counters as JSON strings (e.g. "power_on_hours":"49"),
|
|
|
|
|
// so all numeric fields use jsonInt64 which accepts both bare numbers and
|
|
|
|
|
// quoted strings. Field names match nvme-cli JSON output, not NVMe spec prose.
|
|
|
|
|
type nvmeSmartLog struct {
|
|
|
|
|
CriticalWarning jsonInt64 `json:"critical_warning"`
|
|
|
|
|
PercentageUsed jsonInt64 `json:"percent_used"`
|
|
|
|
|
AvailableSpare jsonInt64 `json:"avail_spare"`
|
|
|
|
|
SpareThreshold jsonInt64 `json:"spare_thresh"`
|
|
|
|
|
Temperature jsonInt64 `json:"temperature"`
|
|
|
|
|
PowerOnHours jsonInt64 `json:"power_on_hours"`
|
|
|
|
|
PowerCycles jsonInt64 `json:"power_cycles"`
|
|
|
|
|
UnsafeShutdowns jsonInt64 `json:"unsafe_shutdowns"`
|
|
|
|
|
DataUnitsRead jsonInt64 `json:"data_units_read"`
|
|
|
|
|
DataUnitsWritten jsonInt64 `json:"data_units_written"`
|
|
|
|
|
ControllerBusy jsonInt64 `json:"controller_busy_time"`
|
|
|
|
|
MediaErrors jsonInt64 `json:"media_errors"`
|
|
|
|
|
NumErrLogEntries jsonInt64 `json:"num_err_log_entries"`
|
|
|
|
|
// NVMeSmartLog is the subset of `nvme smart-log -o json` output shared by the
|
|
|
|
|
// structured collector and the human-readable disk report. nvme-cli emits
|
|
|
|
|
// most counters as JSON strings (e.g. "power_on_hours":"49") or, on some
|
|
|
|
|
// versions, as {"lo":n,"hi":n} objects — all numeric fields use JSONInt64,
|
|
|
|
|
// which accepts bare numbers, quoted strings, and lo/hi objects. Field names
|
|
|
|
|
// match nvme-cli JSON output, not NVMe spec prose.
|
|
|
|
|
type NVMeSmartLog struct {
|
|
|
|
|
CriticalWarning JSONInt64 `json:"critical_warning"`
|
|
|
|
|
PercentageUsed JSONInt64 `json:"percent_used"`
|
|
|
|
|
AvailableSpare JSONInt64 `json:"avail_spare"`
|
|
|
|
|
SpareThreshold JSONInt64 `json:"spare_thresh"`
|
|
|
|
|
Temperature JSONInt64 `json:"temperature"`
|
|
|
|
|
PowerOnHours JSONInt64 `json:"power_on_hours"`
|
|
|
|
|
PowerCycles JSONInt64 `json:"power_cycles"`
|
|
|
|
|
UnsafeShutdowns JSONInt64 `json:"unsafe_shutdowns"`
|
|
|
|
|
DataUnitsRead JSONInt64 `json:"data_units_read"`
|
|
|
|
|
DataUnitsWritten JSONInt64 `json:"data_units_written"`
|
|
|
|
|
ControllerBusy JSONInt64 `json:"controller_busy_time"`
|
|
|
|
|
MediaErrors JSONInt64 `json:"media_errors"`
|
|
|
|
|
NumErrLogEntries JSONInt64 `json:"num_err_log_entries"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// nvmeIDCtrl is the subset of `nvme id-ctrl -o json` output.
|
|
|
|
|
type nvmeIDCtrl struct {
|
|
|
|
|
ModelNumber string `json:"mn"`
|
|
|
|
|
SerialNumber string `json:"sn"`
|
|
|
|
|
FirmwareRev string `json:"fr"`
|
|
|
|
|
TotalCapacity int64 `json:"tnvmcap"`
|
|
|
|
|
// NVMeIDCtrl is the subset of `nvme id-ctrl -o json` output shared by the
|
|
|
|
|
// structured collector and the human-readable disk report.
|
|
|
|
|
type NVMeIDCtrl struct {
|
|
|
|
|
ModelNumber string `json:"mn"`
|
|
|
|
|
SerialNumber string `json:"sn"`
|
|
|
|
|
FirmwareRev string `json:"fr"`
|
|
|
|
|
TotalCapacity JSONInt64 `json:"tnvmcap"`
|
|
|
|
|
NVMCapacity JSONInt64 `json:"nvmcap"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func enrichWithNVMe(dev lsblkDevice) schema.HardwareStorage {
|
|
|
|
@@ -481,7 +496,7 @@ func enrichWithNVMe(dev lsblkDevice) schema.HardwareStorage {
|
|
|
|
|
|
|
|
|
|
// id-ctrl: model, serial, firmware, capacity
|
|
|
|
|
if out, err := exec.Command("nvme", "id-ctrl", devPath, "-o", "json").Output(); err == nil {
|
|
|
|
|
var ctrl nvmeIDCtrl
|
|
|
|
|
var ctrl NVMeIDCtrl
|
|
|
|
|
if json.Unmarshal(out, &ctrl) == nil {
|
|
|
|
|
if v := cleanDMIValue(strings.TrimSpace(ctrl.ModelNumber)); v != "" {
|
|
|
|
|
s.Model = &v
|
|
|
|
@@ -502,7 +517,7 @@ func enrichWithNVMe(dev lsblkDevice) schema.HardwareStorage {
|
|
|
|
|
|
|
|
|
|
// smart-log: wear telemetry
|
|
|
|
|
if out, err := exec.Command("nvme", "smart-log", devPath, "-o", "json").Output(); err == nil {
|
|
|
|
|
var log nvmeSmartLog
|
|
|
|
|
var log NVMeSmartLog
|
|
|
|
|
if json.Unmarshal(out, &log) == nil {
|
|
|
|
|
if log.PowerOnHours > 0 {
|
|
|
|
|
v := int64(log.PowerOnHours)
|
|
|
|
|