Plot live drive activity every three minutes (#91)

- Change default collection cadence from 5 minutes to 3 minutes
  (CADENCE_DEFAULT_S=180, systemd OnUnitInactiveSec=3min,
  runit CADENCE=180)
- Update freshness threshold to 450s (2×180 + AccuracySec + 60)
- Add _query_live_graph_data(): queries raw samples from the last
  3 hours and computes interval byte deltas with actual timestamps
- Add LiveActivityGraph widget: vertical bar chart of interval
  volumes with read/write toggle (w key), arrow key inspection,
  and click support
- Wire live graph into TUI layout (full-width row between daily
  graph and drive health), refresh cycle, and CSS grid
- Replace t theme binding with t today/live binding; theme
  selection via preferences file
- Add w binding for read/write toggle on live graph
- Update action legend, help screen, and grid layout for new
  live-activity row
- Add 20 tests covering cadence constants, live query, widget
  rendering, toggle, and TUI integration
- Update all cadence documentation (README, ADR 0003, acceptance
  criteria LC-2, fenris-redesign constants table, CHANGELOG)
This commit is contained in:
xavierk
2026-09-18 13:17:18 +05:30
parent 4f884b4b73
commit e12f4a574c
14 changed files with 735 additions and 70 deletions
+8 -8
View File
@@ -870,7 +870,7 @@ class TestRefreshTiming:
_insert_segment(conn)
_open_period(conn)
now = _clock()
ts = (now - timedelta(minutes=10)).isoformat()
ts = (now - timedelta(minutes=4)).isoformat()
_insert_sample(conn, ts)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
@@ -878,27 +878,27 @@ class TestRefreshTiming:
svc = {
"boot_enabled": True, "timer_active": True,
"last_collect_ok": True, "last_collect_age_s": 600,
"last_collect_ok": True, "last_collect_age_s": 240,
"last_collect_reason": None,
}
# Sample is 10 min old (600s) → fresh (within 690s threshold)
# Sample is 4 min old (240s) → fresh (within 450s threshold)
comp1 = compose_status(conn, svc, now, store_fault=None, newer_schema=None)
assert comp1.freshness == "fresh"
assert comp1.state == StatusState.MONITORING
# At now + 1 min → sample is 11 min old (660s) → still fresh
now2 = now + timedelta(minutes=1)
# At now + 3 min → sample is 7 min old (420s) → still fresh
now2 = now + timedelta(minutes=3)
comp2 = compose_status(conn, svc, now2, store_fault=None, newer_schema=None)
assert comp2.freshness == "fresh"
# At now + 2 min → sample is 12 min old (720s) → missed (past 690s)
now3 = now + timedelta(minutes=2)
# At now + 5 min → sample is 9 min old (540s) → missed (past 450s)
now3 = now + timedelta(minutes=5)
comp3 = compose_status(conn, svc, now3, store_fault=None, newer_schema=None)
assert comp3.freshness == "missed"
assert comp3.state == StatusState.MONITORING
# At now + 49 hours → sample is 49h 10min old → stale
# At now + 49 hours → sample is 49h 4min old → stale
now4 = now + timedelta(hours=49)
comp4 = compose_status(conn, svc, now4, store_fault=None, newer_schema=None)
assert comp4.freshness == "stale"