zhuyuan-ice-gl-zy001/STELLAR-GAUGE-20260807/collector.py
2026-08-20 00:01:53 +08:00

91 lines
3.2 KiB
Python
Executable file

#!/usr/bin/env python3
"""collector.py · Qoder CN 用量采集器
扫会话日志聚合每模型调用量,输出 ~/.stellar-gauge/usage.json。
只读宿主日志,不写宿主任何东西。
"""
import json, os, re, glob, time
from datetime import datetime, timedelta
LOGS_ROOT = os.path.expanduser(
"~/Library/Application Support/QoderCN/logs")
STATE_FILE = os.path.expanduser("~/.stellar-gauge/usage.json")
OFFSETS_FILE = os.path.expanduser("~/.stellar-gauge/offsets.json")
RE_CTX = re.compile(
r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+).*Context usage update: "
r"\{.*?\"usedTokens\":(\d+)")
RE_MODEL = re.compile(r"applyModelRuntimeMeta: model=([A-Za-z0-9_.-]+)")
# Qoder 内部别名 → 展示名
ALIAS = {
"qmodel_38max": "Qwen3.8-Max",
"qmodel_37max": "Qwen3.7-Max",
"dsmodel_v4pro": "DeepSeek-v4-Pro",
}
def load(path, default):
try:
with open(path) as f:
return json.load(f)
except Exception:
return default
def scan():
offsets = load(OFFSETS_FILE, {})
usage = load(STATE_FILE, {"requests": {}, "tokens": {},
"daily": {}, "meta": {}})
now = datetime.now()
for f in glob.glob(os.path.join(LOGS_ROOT, "*", "questWindow", "agent.log")):
try:
size = os.path.getsize(f)
off = offsets.get(f, 0)
if size < off: # 日志轮转
off = 0
if size == off:
continue
with open(f, errors="ignore") as fh:
fh.seek(off)
chunk = fh.read()
offsets[f] = size
except Exception:
continue
cur_model = "unknown"
prev_used = None
for line in chunk.splitlines():
m = RE_MODEL.search(line)
if m:
cur_model = ALIAS.get(m.group(1), m.group(1))
continue
m = RE_CTX.search(line)
if m:
ts, used = m.group(1), int(m.group(2))
# 增量估算:两次上报差值近似为本轮新增 token
delta = used - prev_used if prev_used else min(used, 8000)
prev_used = used
day = ts[:10]
usage["requests"][cur_model] = \
usage["requests"].get(cur_model, 0) + 1
usage["tokens"][cur_model] = \
usage["tokens"].get(cur_model, 0) + max(delta, 0)
d = usage["daily"].setdefault(day, {})
d[cur_model] = d.get(cur_model, 0) + max(delta, 0)
# 只留 14 天
keep = (now - timedelta(days=14)).strftime("%Y-%m-%d")
usage["daily"] = {k: v for k, v in usage["daily"].items() if k >= keep}
usage["meta"] = {"updated_at": now.isoformat(timespec="seconds"),
"note": "token 为本地增量估算,非计费 Credits"}
tmp = STATE_FILE + ".tmp"
with open(tmp, "w") as f:
json.dump(usage, f, ensure_ascii=False, indent=1)
os.replace(tmp, STATE_FILE)
with open(OFFSETS_FILE, "w") as f:
json.dump(offsets, f)
return usage
if __name__ == "__main__":
u = scan()
print(json.dumps({"requests": u["requests"],
"meta": u["meta"]}, ensure_ascii=False, indent=1))