Files
shy3130 7bd290289a perf: 优化扩展查询、概览并发与 ETF 历史读取
按需裁剪 Parquet 列,合并概览并发构建并防止旧结果回填。为 ETF 历史输入增加有界缓存,按容器内存预算进行任务准入及批次控制,保留原计算口径。补充回归测试与可复现性能基准。

Signed-off-by: shy3130 <shy3130@qq.com>
2026-11-02 00:23:06 +08:00

103 lines
3.3 KiB
Python

"""API 路由 — Phase 0 仅 /health 与 /api/capabilities。"""
from __future__ import annotations
from pathlib import Path
from fastapi import APIRouter, Request
from app import __version__
from app.tickflow import client as tf_client
from app.tickflow.policy import detect_capabilities, tier_label
router = APIRouter()
def _read_cgroup_int(path: Path) -> int | None:
try:
text = path.read_text(encoding="utf-8").strip()
except OSError:
return None
if not text or text == "max":
return None
try:
return int(text)
except ValueError:
return None
def _memory_snapshot() -> dict:
"""内存可观测性 (issue #516): 主进程 RSS + 系统可用 + cgroup v2 节流态。
让外部看门狗在「服务活着但被 cgroup MemoryHigh 节流到假死」时有数可判,
而不是只有 status=ok。
"""
import psutil
vm = psutil.virtual_memory()
from app.services.memory_budget import memory_budget
budget = memory_budget(vm)
snap: dict[str, object] = {
"rss_bytes": psutil.Process().memory_info().rss,
"system_total_bytes": vm.total,
"system_available_bytes": vm.available,
"system_used_pct": vm.percent,
"effective_total_bytes": budget.total_bytes,
"effective_available_bytes": budget.available_bytes,
}
cg = Path("/sys/fs/cgroup")
if _read_cgroup_int(cg / "memory.current") is not None:
snap["cgroup_current_bytes"] = _read_cgroup_int(cg / "memory.current")
snap["cgroup_high_bytes"] = _read_cgroup_int(cg / "memory.high")
snap["cgroup_max_bytes"] = _read_cgroup_int(cg / "memory.max")
events: dict[str, int] = {}
try:
for line in (cg / "memory.events").read_text(encoding="utf-8").splitlines():
parts = line.split()
if len(parts) == 2:
try:
events[parts[0]] = int(parts[1])
except ValueError:
continue
except OSError:
pass
if events:
snap["cgroup_events"] = events
return snap
@router.get("/health")
def health() -> dict:
return {
"status": "ok",
"version": __version__,
# 三态: none(无key/无效) / free(免费key) / api_key(付费档)
"mode": tf_client.current_mode(),
"memory": _memory_snapshot(),
}
@router.get("/api/capabilities")
def capabilities() -> dict:
"""前端用来决定哪些功能可用、哪些灰显。"""
capset = detect_capabilities()
return {
"label": tier_label(),
"capabilities": capset.to_dict(),
}
@router.post("/api/capabilities/redetect")
def redetect(request: Request) -> dict:
"""用户在设置页"重新检测"按钮。"""
capset = detect_capabilities(force=True)
# 同步刷新 app.state 快照 (minute_refresh 等服务的门控读这里) 与财务调度器,
# 与 settings.py 各探测路径一致 — 否则重检测后服务侧仍读旧 capset 被错误门控
request.app.state.capabilities = capset
from app.api.settings import _sync_financial_scheduler_caps
_sync_financial_scheduler_caps(request.app.state, capset)
return {
"label": tier_label(),
"capabilities": capset.to_dict(),
}