monitor.export

The format:1 producer (spec 2026-07-12).

Pure reshaping; validation is the pydantic models’ job — the schema drift guards police this module for free.

otto.monitor.export.session_meta(collector: MetricCollector, *, interval: float | None = None) SessionMeta

Reshape get_meta_model() into an archival session meta.

The result is a lenient SessionMeta.

The single source of this reshape — never hand-roll it. The two models do NOT share a field name for the chart list: MonitorMeta calls it metrics, SessionMeta calls it charts. Because SessionMeta is a lenient RowModel (extra='ignore'), feeding it a raw MonitorMeta dump validates silently and drops every chart spec — tabs survives (same name), so the damage looks partial and is easy to miss. A session persisted that way renders with no chart grouping and no units, exactly as an empty chart_map does.

charts/tabs come from the parser catalog rather than from collected data, so they are fully known even before the first tick — which is what lets the --db paths persist meta_json at construction time.

Parameters:
  • collector – The collector whose parser catalog describes this session.

  • interval – The run’s collection interval in seconds. Pass this whenever the meta is built before :meth:`MetricCollector.run` has started — i.e. from the construction-time --db call sites in otto.cli.monitor (its --interval option) and otto.suite.plugin (its --monitor-interval). The collector only records its own interval once run() begins, so reading it off the model at construction yields None permanently for a DB archive: nothing repairs it later (unlike chart_map, which the collector rewrites incrementally). A null interval is not cosmetic — the frontend resolves cadence as chart.interval ?? session.meta.interval (web/src/data/health.ts), so with both null cadenceMs() returns null and derived health/staleness is unresolvable for the whole session. Omit it only when the collector has already run and its own recorded value is authoritative (build_live_export()).

otto.monitor.export.build_session_metric_db(path: str, frame: SessionFrame, lab: LabSnapshot, meta_collector: MetricCollector, *, interval: float) MetricDB

Construct an unopened, archive-real v2 MetricDB.

The shared construction for every ``–db``-backed session-monitor call site — never hand-roll ``MetricDB(…, lab_json=”{}”, meta_json=”{}”)``. Used by otto.suite.suite.OttoSuite.start_monitor(), otto.suite.plugin.OttoPlugin’s --monitor --monitor-output *.db session fixture, and otto.cli.monitor’s --live --db path — closing a long-standing triplication: all three call sites used to build their own MetricDB. One of them (OttoSuite.start_monitor) had drifted and kept persisting lab_json="{}"/meta_json="{}" — the degraded-archive shape this producer phase spent three fix waves eliminating elsewhere (no chart specs, no units, null interval, no lab topology on replay); the CLI’s copy was already correct, just duplicated.

Parameters:
  • path – Filesystem path for the SQLite archive.

  • frame – This run’s session identity (see otto.monitor.session.new_frame()).

  • lab – The frozen lab config for this session (see otto.monitor.session.snapshot_lab()).

  • meta_collector – A throwaway collector, built the same way (same hosts/targets/parsers) as the real collector that will go on to own this MetricDB — never run, never handed this database. Needed because MetricDB’s constructor needs meta_json up front, but the real collector can’t be constructed until the MetricDB object it will own already exists.

  • interval – The run’s collection interval in seconds. Must be passed explicitly — see session_meta()’s docstring for why a null interval here would persist forever and leave the replayed session’s derived health unresolvable.

Returns:

An unopened MetricDB — call sites open it themselves, directly or by handing it to a MetricCollector as db=.

otto.monitor.export.build_live_export(frame: SessionFrame, collector: MetricCollector, lab: LabSnapshot) MonitorExport

Wrap one live collector’s in-memory state into a format:1 document.

Always exactly one session, taking its identity straight from frame: end mirrors frame.end verbatim, so a still-open frame (end is None) produces a still-open session — a live export is never “crashed”, so no last-sample fallback applies here (that’s build_db_export()’s job, for archives a process actually walked away from). Nothing here reads the wall clock: frame fully determines the session’s bounds.

Parameters:
Returns:

The single-session MonitorExport.

otto.monitor.export.build_db_export(path: str) MonitorExport

Read every session from a v2 archive into one format:1 document.

Parameters:

path – Filesystem path to the v2 SQLite archive (see otto.monitor.db.read_sessions(); fails loud on anything that is not schema v2).

Returns:

A MonitorExport with one SessionRecord per archived session, in the archive’s own (start-ordered) order.

otto.monitor.export.document_json(export: MonitorExport) str

Serialize export exactly as scripts/gen_monitor_fixtures.py does.

The generator’s dumps() calls doc.model_dump(mode="json", exclude_none=True) then json.dumps(...); model_dump_json is pydantic’s direct equivalent of that same mode="json" shape, so passing the identical exclude_none=True argument here guarantees the frontend’s normalizer (web/src/data/exportDoc.ts) sees identical field presence/absence for a live/DB export as it does for the committed fixtures, byte-for-byte whitespace aside.