monitor.db¶
MetricDB — SQLite persistence for monitor metrics and events.
Owns the aiosqlite connection, the schema, the WAL-vs-DELETE journal choice (network filesystems can’t WAL), and the flock guard that stops two live collectors writing one database. Extracted from MetricCollector; behavior is identical.
Schema v2 is session-scoped: one physical database file can hold many
sessions (one per live run, appended over time), and every row is stamped
with the session it belongs to. There is no migration path from the
pre-session (v1) schema — opening or reading anything that is not v2 fails
loud (UnsupportedDBError); see read_sessions().
- exception otto.monitor.db.UnsupportedDBError¶
Bases:
RuntimeErrorRaised for any monitor database that is not schema v2.
Legacy pre-session databases are deliberately unsupported (spec 2026-07-12: no migration path); the message must say so.
- class otto.monitor.db.SessionRow(id: str, label: str | None, note: str | None, start: str, end: str | None, lab_json: str, meta_json: str, chart_map_json: str, metrics: list[tuple[str, str, str, float, str | None]], events: list[tuple[int, str, str | None, str, str, str, str]], log_events: list[tuple[str, str, str, str]])¶
Bases:
objectOne session read back from a v2 archive, with all of its rows.
Row tuples deliberately mirror the SQL column order rather than being modeled — this is the review-path reader, consumed by the producer (Task 3), not a public API surface.
- class otto.monitor.db.MetricDB(path: str, frame: SessionFrame, lab_json: str, meta_json: str)¶
Bases:
objectPersistent async SQLite store, bound to ONE live session frame.
The frame is a
SessionFrame, and every write is stamped with its session id. A single database file accumulates one row insessionsper live run over time — reopen a freshMetricDB(with a fresh frame) for the next run.- async write_chart_map(chart_map_json: str) None¶
Overwrite this session’s series-label → chart-key map. No-op if not open.
Called by the collector whenever a tick introduces a label the map did not already carry, NOT at finalize: a crashed session (
endleft NULL) must still carry its grouping, or every one of its series renders as its own ungrouped, unit-less chart on replay (seeotto.monitor.export).
- async write_point(ts: datetime, host: str, label: str, value: float) None¶
Insert one metric point. No-op if the connection is not open.
- async write_log_event(ts: datetime, host: str, tab: str, fields: dict[str, str]) None¶
Insert one log-event row (fields JSON-encoded). No-op if not open.
- async write_event(event: MonitorEvent) int¶
Insert event into the DB and return the rowid (0 if no DB configured).
- async delete_event(event_id: int) None¶
Delete the event with the given id. No-op if the connection is not open.
- async update_event(event: MonitorEvent) None¶
Update an existing event’s label, color, dash, and end_ts. No-op if not open.
- otto.monitor.db.read_sessions(path: str) list[SessionRow]¶
Read every session (ordered by start) from a v2 archive. Fail-loud otherwise.
Synchronous sqlite3, opened read-only — review mode (the CLI’s historical viewer) runs before any event loop matters, so there is no need to route this through aiosqlite.