Files
openswarm/backend/OLDapps/analytics/analytics.py

150 lines
5.3 KiB
Python

"""Analytics SubApp: PostHog for product analytics + local usage summary from session data."""
import asyncio
import logging
import platform
from contextlib import asynccontextmanager
from datetime import datetime
from typing import Optional
from pydantic import InstanceOf
from backend.config.Apps import SubApp
from backend.apps.analytics.collector import init as init_collector, shutdown as shutdown_collector, record, identify
from backend.apps.analytics.usage_summary import load_all_sessions, compute_session_stats, enrich_with_nine_router
from backend.apps.agents.manager.agent_manager import agent_manager
from backend.apps.settings.settings import load_settings, _save_settings
from backend.apps.agents.manager.agent_manager import agent_manager
from backend.apps.nine_router import get_usage_stats, is_running as _9r_running
logger = logging.getLogger(__name__)
APP_VERSION = "1.0.20"
_heartbeat_task: Optional[InstanceOf[asyncio.Task]] = None
async def _heartbeat_loop():
"""Send a heartbeat event every 60 seconds for usage-time tracking and cost snapshots."""
while True:
await asyncio.sleep(60)
try:
props = {
"active_session_count": len(agent_manager.sessions),
}
try:
if _9r_running():
stats = await get_usage_stats()
if stats:
props["nine_router_total_cost"] = stats.get("totalCost", 0)
props["nine_router_total_prompt_tokens"] = stats.get("totalPromptTokens", 0)
props["nine_router_total_completion_tokens"] = stats.get("totalCompletionTokens", 0)
props["nine_router_total_requests"] = stats.get("totalRequests", 0)
for model_name, model_data in (stats.get("byModel") or {}).items():
safe_name = model_name.replace(".", "_").replace("-", "_")[:40]
props[f"cost_model_{safe_name}"] = model_data.get("cost", 0)
props[f"tokens_model_{safe_name}"] = model_data.get("promptTokens", 0) + model_data.get("completionTokens", 0)
except Exception:
pass
record("app.heartbeat", props)
if "nine_router_total_cost" in props:
record("cost.snapshot", {
"total_cost_usd": props["nine_router_total_cost"],
"total_prompt_tokens": props.get("nine_router_total_prompt_tokens", 0),
"total_completion_tokens": props.get("nine_router_total_completion_tokens", 0),
"total_requests": props.get("nine_router_total_requests", 0),
})
except Exception:
pass
@asynccontextmanager
async def analytics_lifespan():
global _heartbeat_task
init_collector()
logger.info("PostHog analytics initialised")
try:
settings = load_settings()
is_first_open = settings.first_opened_at is None
if is_first_open:
settings.first_opened_at = datetime.now().isoformat()
_save_settings(settings)
days_since_install = 0
if settings.first_opened_at:
try:
first = datetime.fromisoformat(settings.first_opened_at[:19])
days_since_install = (datetime.now() - first).days
except Exception:
pass
providers = []
if getattr(settings, "anthropic_api_key", None):
providers.append("anthropic")
if getattr(settings, "openai_api_key", None):
providers.append("openai")
if getattr(settings, "google_api_key", None):
providers.append("gemini")
if getattr(settings, "openrouter_api_key", None):
providers.append("openrouter")
for cp in getattr(settings, "custom_providers", []):
providers.append(cp.name)
record("app.opened", {
"os": platform.system(),
"platform": platform.platform(),
"provider_count": len(providers),
"providers": providers,
"is_first_open": is_first_open,
"days_since_install": days_since_install,
"app_version": APP_VERSION,
})
identify({
"providers_configured": providers,
"provider_count": len(providers),
"app_version": APP_VERSION,
})
except Exception as e:
logger.debug(f"Analytics startup event failed (non-critical): {e}")
_heartbeat_task = asyncio.create_task(_heartbeat_loop())
yield
if _heartbeat_task:
_heartbeat_task.cancel()
try:
await _heartbeat_task
except asyncio.CancelledError:
pass
_heartbeat_task = None
shutdown_collector()
logger.info("PostHog analytics shut down")
analytics = SubApp("analytics", analytics_lifespan)
@analytics.router.get("/usage-summary")
async def usage_summary():
"""Compute usage stats from persisted sessions for the Settings page."""
sessions = load_all_sessions()
for s in agent_manager.get_all_sessions():
sessions.append(s.model_dump(mode="json"))
stats = compute_session_stats(sessions)
nine_router_stats = await get_usage_stats() if _9r_running() else None
return enrich_with_nine_router(stats, nine_router_stats)