89 lines
2.9 KiB
Python
89 lines
2.9 KiB
Python
"""Session telemetry — lightweight event/metric log for post-run analysis.
|
|
|
|
Writes a JSON-lines file (one event per line) to the session directory.
|
|
Each event has a timestamp, type, and payload. Designed to be grep-friendly.
|
|
|
|
Usage:
|
|
tel = Telemetry(session_dir)
|
|
tel.event("scene_threshold_changed", {"from": 0.10, "to": 0.15})
|
|
tel.metric("scene_detection", {"start": 120.0, "end": 135.0, "frames_found": 3, "elapsed_ms": 1200})
|
|
tel.close()
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import time
|
|
from pathlib import Path
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class Telemetry:
|
|
def __init__(self, session_dir: Path):
|
|
self._path = session_dir / "telemetry.jsonl"
|
|
self._start = time.monotonic()
|
|
self._wall_start = time.time()
|
|
self._file = None
|
|
self._log_handler = None
|
|
try:
|
|
session_dir.mkdir(parents=True, exist_ok=True)
|
|
self._file = open(self._path, "a")
|
|
except Exception as e:
|
|
log.warning("Telemetry init failed: %s", e)
|
|
|
|
# Also save full logs to session directory
|
|
try:
|
|
log_path = session_dir / "session.log"
|
|
handler = logging.FileHandler(str(log_path), mode="a")
|
|
handler.setLevel(logging.DEBUG)
|
|
handler.setFormatter(logging.Formatter(
|
|
"%(asctime)s %(levelname)-7s %(name)s: %(message)s",
|
|
datefmt="%H:%M:%S",
|
|
))
|
|
logging.getLogger().addHandler(handler)
|
|
self._log_handler = handler
|
|
except Exception as e:
|
|
log.warning("Log file handler failed: %s", e)
|
|
|
|
self.event("session_start", {"session_dir": str(session_dir)})
|
|
|
|
def event(self, name: str, data: dict | None = None) -> None:
|
|
"""Log a discrete event (setting change, mode switch, user action)."""
|
|
self._write("event", name, data or {})
|
|
|
|
def metric(self, name: str, data: dict) -> None:
|
|
"""Log a measurement (processing time, frame count, etc)."""
|
|
self._write("metric", name, data)
|
|
|
|
def _write(self, kind: str, name: str, data: dict) -> None:
|
|
if not self._file:
|
|
return
|
|
entry = {
|
|
"t": round(time.monotonic() - self._start, 3),
|
|
"wall": round(time.time(), 3),
|
|
"kind": kind,
|
|
"name": name,
|
|
**data,
|
|
}
|
|
try:
|
|
self._file.write(json.dumps(entry) + "\n")
|
|
self._file.flush()
|
|
except Exception:
|
|
pass
|
|
|
|
def close(self) -> None:
|
|
self.event("session_end", {})
|
|
if self._log_handler:
|
|
logging.getLogger().removeHandler(self._log_handler)
|
|
try:
|
|
self._log_handler.close()
|
|
except Exception:
|
|
pass
|
|
self._log_handler = None
|
|
if self._file:
|
|
try:
|
|
self._file.close()
|
|
except Exception:
|
|
pass
|
|
self._file = None
|