64 lines
1.8 KiB
Python
64 lines
1.8 KiB
Python
|
|
"""Telemetry export sinks."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Protocol
|
||
|
|
|
||
|
|
import httpx
|
||
|
|
|
||
|
|
from sandboxer.lifecycle.state_hub import hub_url
|
||
|
|
from sandboxer.telemetry.models import IntrospectionReport
|
||
|
|
|
||
|
|
|
||
|
|
class TelemetrySink(Protocol):
|
||
|
|
"""Future export target (artifact-store, Prometheus, ClickHouse)."""
|
||
|
|
|
||
|
|
def publish(self, report: IntrospectionReport) -> None: ...
|
||
|
|
|
||
|
|
|
||
|
|
def telemetry_dir() -> Path:
|
||
|
|
base = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share"))
|
||
|
|
path = base / "sandboxer" / "telemetry"
|
||
|
|
path.mkdir(parents=True, exist_ok=True)
|
||
|
|
return path
|
||
|
|
|
||
|
|
|
||
|
|
def export_local_artifact(report: IntrospectionReport) -> Path:
|
||
|
|
path = telemetry_dir() / f"{report.sandbox_id}.json"
|
||
|
|
path.write_text(json.dumps(report.model_dump(mode="json"), indent=2, default=str))
|
||
|
|
return path
|
||
|
|
|
||
|
|
|
||
|
|
def export_state_hub(report: IntrospectionReport) -> dict | None:
|
||
|
|
if os.environ.get("SANDBOXER_NO_STATE_HUB", "").lower() in ("1", "true", "yes"):
|
||
|
|
return None
|
||
|
|
payload = {
|
||
|
|
"event_type": "note",
|
||
|
|
"summary": (
|
||
|
|
f"Telemetry {report.sandbox_id}: load Δ "
|
||
|
|
f"{report.provision_delta.load_1m_delta if report.provision_delta else 0}, "
|
||
|
|
f"stale={len(report.stale_candidates)}"
|
||
|
|
),
|
||
|
|
"author": "sandboxer",
|
||
|
|
"detail": report.model_dump(mode="json"),
|
||
|
|
}
|
||
|
|
try:
|
||
|
|
response = httpx.post(f"{hub_url()}/progress/", json=payload, timeout=10.0)
|
||
|
|
response.raise_for_status()
|
||
|
|
return response.json()
|
||
|
|
except httpx.HTTPError:
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def export_telemetry(report: IntrospectionReport) -> Path:
|
||
|
|
path = export_local_artifact(report)
|
||
|
|
export_state_hub(report)
|
||
|
|
return path
|
||
|
|
|
||
|
|
|
||
|
|
class NoopTelemetrySink:
|
||
|
|
def publish(self, report: IntrospectionReport) -> None:
|
||
|
|
export_telemetry(report)
|