Initial OpenClaw usage exporter
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import PlainTextResponse
|
||||
|
||||
SESSIONS_DIR = Path(os.environ.get("OPENCLAW_SESSIONS_DIR", "/root/.openclaw/agents/main/sessions"))
|
||||
TIMEZONE = ZoneInfo(os.environ.get("OPENCLAW_EXPORTER_TZ", "Europe/Berlin"))
|
||||
|
||||
app = FastAPI(title="openclaw-daily-tokens-exporter")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Totals:
|
||||
total: int = 0
|
||||
input: int = 0
|
||||
output: int = 0
|
||||
cache_read: int = 0
|
||||
cache_write: int = 0
|
||||
reasoning: int = 0
|
||||
entries: int = 0
|
||||
|
||||
|
||||
def _as_int(value: object) -> int:
|
||||
if value is None:
|
||||
return 0
|
||||
if isinstance(value, bool):
|
||||
return int(value)
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value)
|
||||
return 0
|
||||
|
||||
|
||||
def _today_in_tz() -> datetime.date:
|
||||
return datetime.now(TIMEZONE).date()
|
||||
|
||||
|
||||
def collect_daily_totals() -> Totals:
|
||||
today = _today_in_tz()
|
||||
totals = Totals()
|
||||
|
||||
for path in sorted(SESSIONS_DIR.glob("*.jsonl*")):
|
||||
try:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
record = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
if record.get("type") != "message":
|
||||
continue
|
||||
|
||||
message = record.get("message") or {}
|
||||
usage = message.get("usage")
|
||||
if not isinstance(usage, dict):
|
||||
continue
|
||||
|
||||
timestamp_ms = message.get("timestamp")
|
||||
if not isinstance(timestamp_ms, (int, float)):
|
||||
continue
|
||||
|
||||
usage_date = datetime.fromtimestamp(timestamp_ms / 1000, TIMEZONE).date()
|
||||
if usage_date != today:
|
||||
continue
|
||||
|
||||
totals.entries += 1
|
||||
totals.total += _as_int(usage.get("totalTokens"))
|
||||
totals.input += _as_int(usage.get("input"))
|
||||
totals.output += _as_int(usage.get("output"))
|
||||
totals.cache_read += _as_int(usage.get("cacheRead"))
|
||||
totals.cache_write += _as_int(usage.get("cacheWrite"))
|
||||
totals.reasoning += _as_int(usage.get("reasoningTokens"))
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
return totals
|
||||
|
||||
|
||||
@app.get("/metrics", response_class=PlainTextResponse)
|
||||
def metrics() -> str:
|
||||
totals = collect_daily_totals()
|
||||
today = _today_in_tz().isoformat()
|
||||
|
||||
lines = [
|
||||
"# HELP openclaw_daily_total_tokens Total OpenClaw tokens recorded today.",
|
||||
"# TYPE openclaw_daily_total_tokens gauge",
|
||||
f'openclaw_daily_total_tokens{{date="{today}",tz="{TIMEZONE.key}"}} {totals.total}',
|
||||
"# HELP openclaw_daily_input_tokens OpenClaw input tokens recorded today.",
|
||||
"# TYPE openclaw_daily_input_tokens gauge",
|
||||
f'openclaw_daily_input_tokens{{date="{today}",tz="{TIMEZONE.key}"}} {totals.input}',
|
||||
"# HELP openclaw_daily_output_tokens OpenClaw output tokens recorded today.",
|
||||
"# TYPE openclaw_daily_output_tokens gauge",
|
||||
f'openclaw_daily_output_tokens{{date="{today}",tz="{TIMEZONE.key}"}} {totals.output}',
|
||||
"# HELP openclaw_daily_cache_read_tokens OpenClaw cache-read tokens recorded today.",
|
||||
"# TYPE openclaw_daily_cache_read_tokens gauge",
|
||||
f'openclaw_daily_cache_read_tokens{{date="{today}",tz="{TIMEZONE.key}"}} {totals.cache_read}',
|
||||
"# HELP openclaw_daily_cache_write_tokens OpenClaw cache-write tokens recorded today.",
|
||||
"# TYPE openclaw_daily_cache_write_tokens gauge",
|
||||
f'openclaw_daily_cache_write_tokens{{date="{today}",tz="{TIMEZONE.key}"}} {totals.cache_write}',
|
||||
"# HELP openclaw_daily_reasoning_tokens OpenClaw reasoning tokens recorded today.",
|
||||
"# TYPE openclaw_daily_reasoning_tokens gauge",
|
||||
f'openclaw_daily_reasoning_tokens{{date="{today}",tz="{TIMEZONE.key}"}} {totals.reasoning}',
|
||||
"# HELP openclaw_daily_usage_entries OpenClaw usage entries recorded today.",
|
||||
"# TYPE openclaw_daily_usage_entries gauge",
|
||||
f'openclaw_daily_usage_entries{{date="{today}",tz="{TIMEZONE.key}"}} {totals.entries}',
|
||||
"",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
Reference in New Issue
Block a user