feat(collector): capture additional smartctl diagnostics

Record host read and write commands, controller busy time, error log
entries, warning and critical temperature time, and thermal management
transitions from the existing smartctl -a -j acquisition. Schema 7 adds
nullable columns to samples so legacy rows read as unknown, and the
Diagnostics panel shows each counter with its change.
This commit is contained in:
xavierk
2026-10-05 21:12:25 +05:30
parent 948e097952
commit ab1d894adc
7 changed files with 332 additions and 10 deletions
+23 -1
View File
@@ -51,8 +51,25 @@ DIAGNOSTIC_COUNTERS = (
("unsafe_shutdowns", "Unsafe shutdowns", ""),
("media_errors", "Media errors", ""),
("available_spare", "Available spare", "%"),
("host_reads", "Host reads", ""),
("host_writes", "Host writes", ""),
("controller_busy_time", "Controller busy time", " min"),
("num_err_log_entries", "Error log entries", ""),
("warning_temp_time", "Warning temperature time", " min"),
("critical_comp_time", "Critical temperature time", " min"),
("thm_temp1_trans_count", "Thermal management 1 transitions", ""),
("thm_temp1_total_time", "Thermal management 1 time", " s"),
("thm_temp2_trans_count", "Thermal management 2 transitions", ""),
("thm_temp2_total_time", "Thermal management 2 time", " s"),
)
# Reported only by drives that implement thermal management; with no value the
# panel omits them instead of listing four permanent n/a rows.
_OPTIONAL_DIAGNOSTICS = frozenset((
"thm_temp1_trans_count", "thm_temp1_total_time",
"thm_temp2_trans_count", "thm_temp2_total_time",
))
LOCAL_ACTIVITY_STATE_LABELS = {
"so_far": "totals so far",
"incomplete": "incomplete",
@@ -689,7 +706,10 @@ def _counter_deltas(
inside the window; with fewer than two distinct samples, or across a
controller-segment change, there is no delta.
"""
columns = ", ".join(key for key, _, _ in DIAGNOSTIC_COUNTERS)
# A store not yet migrated lacks the newer counter columns: read them as unknown.
present = {row[1] for row in conn.execute("PRAGMA table_info(samples)")}
columns = ", ".join(
key if key in present else "NULL" for key, _, _ in DIAGNOSTIC_COUNTERS)
columns += ", segment_id"
last = conn.execute(
"SELECT id, %s FROM samples WHERE ts <= ? ORDER BY ts DESC, id DESC "
@@ -1016,6 +1036,8 @@ def diagnostics_text(activity: ActivityReadout) -> str:
for key, label, unit in DIAGNOSTIC_COUNTERS:
counter = diag["counters"][key]
if counter["value"] is None:
if key in _OPTIONAL_DIAGNOSTICS:
continue
lines.append("%s n/a" % label)
continue
delta = "Δ n/a" if counter["delta"] is None else "Δ " + _signed(counter["delta"])