- agent/session_stats.py: SessionDB context/compression metrics - agent/skill_stats.py: curator usage.json reader + prune history - agent/system_health.py: gateway uptime, version, cron activity - agent/stats_dashboard.py: Telegram-friendly bullet renderer - cli.py: /stats dispatch + _handle_stats_command method - gateway/run.py: /stats dispatch + _handle_stats_command for messaging platforms - hermes_cli/commands.py: /stats CommandDef registration
77 lines
3.0 KiB
Python
77 lines
3.0 KiB
Python
"""Renderer for the Telegram-friendly /stats dashboard."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from agent.session_stats import collect_context_stats, collect_semantic_rle_stats
|
|
from agent.skill_stats import collect_curator_prunes, collect_skill_stats
|
|
from agent.system_health import collect_system_health
|
|
|
|
|
|
def _fmt_int(value: Any) -> str:
|
|
try:
|
|
return f"{int(value):,}"
|
|
except (TypeError, ValueError):
|
|
return "0"
|
|
|
|
|
|
def _fmt_pct(value: Any) -> str:
|
|
if value is None:
|
|
return "unknown"
|
|
try:
|
|
return f"{float(value):.1f}%"
|
|
except (TypeError, ValueError):
|
|
return "unknown"
|
|
|
|
|
|
def _fallback_text(chain: list[dict]) -> str:
|
|
if not chain:
|
|
return "none"
|
|
return " → ".join(
|
|
f"{item.get('model') or '?'} ({item.get('provider') or '?'})"
|
|
for item in chain
|
|
)
|
|
|
|
|
|
def format_stats_dashboard(*, agent: Any = None, session_db: Any = None, session_id: str | None = None, started_at: Any = None, start_monotonic: float | None = None) -> str:
|
|
context = collect_context_stats(agent=agent, session_db=session_db, session_id=session_id)
|
|
rle = collect_semantic_rle_stats(session_db=session_db)
|
|
skills = collect_skill_stats(limit=5)
|
|
prunes = collect_curator_prunes(days=7, limit=3)
|
|
health = collect_system_health(started_at=started_at, start_monotonic=start_monotonic)
|
|
cron = health.get("cron") or {}
|
|
|
|
lines = [
|
|
"📊 Hermes stats",
|
|
"",
|
|
f"• Model: {context['model']} ({context['provider']})",
|
|
f"• Fallback: {_fallback_text(context.get('fallback_chain') or [])}",
|
|
f"• Context: {_fmt_int(context.get('total_tokens'))}/{_fmt_int(context.get('context_length'))} tokens ({_fmt_pct(context.get('usage_percent'))})",
|
|
f"• Semantic RLE: {rle.get('sessions_compressed', 0)} sessions · ratio {_fmt_pct((rle.get('compression_ratio') or 0) * 100 if rle.get('compression_ratio') is not None else None)} · avg saved {_fmt_int(rle.get('avg_tokens_saved'))} tokens",
|
|
"",
|
|
"• Top skills:",
|
|
]
|
|
|
|
top = skills.get("top_skills") or []
|
|
if top:
|
|
for row in top[:5]:
|
|
lines.append(f" - {row['name']}: {row['activity_count']} activity ({row['use_count']} use / {row['view_count']} view / {row['patch_count']} patch)")
|
|
else:
|
|
lines.append(" - no skill usage telemetry yet")
|
|
|
|
lines.append("• Gardener prunes (7d):")
|
|
recent_prunes = prunes.get("recent_prunes") or []
|
|
if recent_prunes:
|
|
for row in recent_prunes[:3]:
|
|
stamp = str(row.get("archived_at") or "unknown").split("T", 1)[0]
|
|
lines.append(f" - {row.get('name')}: {stamp}")
|
|
else:
|
|
lines.append(" - none")
|
|
|
|
lines.extend([
|
|
f"• Nightly/cron 24h: {cron.get('runs', 0)} runs · {cron.get('ok', 0)} ok · {cron.get('error', 0)} errors · {cron.get('health_checks', 0)} health checks",
|
|
f"• Uptime/version: {health.get('uptime')} · v{health.get('version')} · pid {health.get('pid')}",
|
|
])
|
|
return "\n".join(lines)
|