118 lines
4.4 KiB
Python
118 lines
4.4 KiB
Python
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)
|