z / app /admin /stats.py
bg5's picture
Upload 85 files
712a3c7 verified
Raw History Blame Contribute Delete
6.73 kB
"""管理后台统计聚合辅助函数。"""
from __future__ import annotations
import os
import time
from typing import Any, Dict, Optional
try:
import psutil
except ImportError:
psutil = None
from app.services.request_log_dao import RequestLogDAO, get_request_log_dao
from app.services.token_dao import TokenDAO, get_token_dao
from app.utils.format import format_compact_number
from app.utils.token_pool import TokenPool, get_token_pool
_TOKEN_POOL_SENTINEL = object()
DEFAULT_TREND_WINDOW = "7d"
TREND_WINDOW_OPTIONS = (
{"key": "24h", "label": "24 小时"},
{"key": "7d", "label": "7 天"},
{"key": "30d", "label": "30 天"},
)
def _coerce_int(value: Any) -> int:
"""将数据库聚合结果安全转换为整数。"""
return int(value or 0)
def calculate_success_rate(
successful_requests: int,
total_requests: int,
) -> float:
"""计算成功率百分比。"""
if total_requests <= 0:
return 0.0
return round(successful_requests / total_requests * 100, 1)
def normalize_trend_window(value: Any) -> str:
"""规范化趋势窗口参数,非法值回退到默认值。"""
normalized = str(value or "").strip().lower()
if normalized in {"24h", "7d", "30d"}:
return normalized
if normalized == "1d":
return "24h"
return DEFAULT_TREND_WINDOW
def format_uptime(total_seconds: int) -> str:
"""格式化运行时长。"""
total_seconds = max(0, int(total_seconds))
days, remainder = divmod(total_seconds, 86400)
hours, remainder = divmod(remainder, 3600)
minutes, seconds = divmod(remainder, 60)
parts = []
if days:
parts.append(f"{days}天")
if days or hours:
parts.append(f"{hours}小时")
if days or hours or minutes:
parts.append(f"{minutes}分钟")
parts.append(f"{seconds}秒")
return " ".join(parts)
def get_process_uptime() -> str:
"""获取当前进程运行时长。"""
if psutil is None:
return "未知"
created_at = psutil.Process(os.getpid()).create_time()
return format_uptime(int(time.time() - created_at))
async def collect_admin_stats(
provider: str,
*,
token_dao: Optional[TokenDAO] = None,
request_log_dao: Optional[RequestLogDAO] = None,
token_pool: Any = _TOKEN_POOL_SENTINEL,
trend_window: str = DEFAULT_TREND_WINDOW,
) -> Dict[str, Any]:
"""聚合管理后台所需的 Token 与请求统计。"""
token_dao = token_dao or get_token_dao()
request_log_dao = request_log_dao or get_request_log_dao()
if token_pool is _TOKEN_POOL_SENTINEL:
token_pool = get_token_pool()
trend_window = normalize_trend_window(trend_window)
token_counts = await token_dao.get_provider_token_counts(provider)
request_stats = await request_log_dao.get_provider_request_stats(provider)
usage_trend = await request_log_dao.get_provider_usage_trend(
provider,
window=trend_window,
)
pool_status: Dict[str, Any] = {}
if isinstance(token_pool, TokenPool) or hasattr(token_pool, "get_pool_status"):
pool_status = await token_pool.get_pool_status() if token_pool else {}
total_tokens = _coerce_int(token_counts.get("total_tokens"))
enabled_tokens = _coerce_int(token_counts.get("enabled_tokens"))
user_tokens = _coerce_int(token_counts.get("user_tokens"))
guest_tokens = _coerce_int(token_counts.get("guest_tokens"))
unknown_tokens = _coerce_int(token_counts.get("unknown_tokens"))
pool_total_tokens = _coerce_int(pool_status.get("total_tokens"))
if pool_total_tokens == 0 and token_pool is None:
pool_total_tokens = max(0, enabled_tokens - guest_tokens)
available_tokens = _coerce_int(pool_status.get("available_tokens"))
healthy_tokens = _coerce_int(pool_status.get("healthy_tokens"))
unhealthy_tokens = _coerce_int(pool_status.get("unhealthy_tokens"))
total_requests = _coerce_int(request_stats.get("total_requests"))
successful_requests = _coerce_int(request_stats.get("successful_requests"))
failed_requests = _coerce_int(request_stats.get("failed_requests"))
input_tokens = _coerce_int(request_stats.get("input_tokens"))
output_tokens = _coerce_int(request_stats.get("output_tokens"))
total_consumed_tokens = _coerce_int(request_stats.get("total_tokens"))
cache_creation_tokens = _coerce_int(
request_stats.get("cache_creation_tokens")
)
cache_read_tokens = _coerce_int(request_stats.get("cache_read_tokens"))
cache_creation_requests = _coerce_int(
request_stats.get("cache_creation_requests")
)
cache_hit_requests = _coerce_int(request_stats.get("cache_hit_requests"))
average_latency = round(float(request_stats.get("avg_duration") or 0.0), 2)
average_first_token_latency = round(
float(request_stats.get("avg_first_token_time") or 0.0),
2,
)
total_cache_tokens = cache_creation_tokens + cache_read_tokens
return {
"total_tokens": total_tokens,
"enabled_tokens": enabled_tokens,
"user_tokens": user_tokens,
"guest_tokens": guest_tokens,
"unknown_tokens": unknown_tokens,
"pool_total_tokens": pool_total_tokens,
"available_tokens": available_tokens,
"healthy_tokens": healthy_tokens,
"unhealthy_tokens": unhealthy_tokens,
"total_requests": total_requests,
"successful_requests": successful_requests,
"failed_requests": failed_requests,
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"total_consumed_tokens": total_consumed_tokens,
"cache_creation_tokens": cache_creation_tokens,
"cache_read_tokens": cache_read_tokens,
"total_cache_tokens": total_cache_tokens,
"cache_creation_requests": cache_creation_requests,
"cache_hit_requests": cache_hit_requests,
"average_latency": average_latency,
"average_first_token_latency": average_first_token_latency,
"trend_window": trend_window,
"usage_trend": usage_trend,
"total_consumed_tokens_display": format_compact_number(
total_consumed_tokens
),
"total_cache_tokens_display": format_compact_number(
total_cache_tokens
),
"input_tokens_display": format_compact_number(input_tokens),
"output_tokens_display": format_compact_number(output_tokens),
"success_rate": calculate_success_rate(
successful_requests,
total_requests,
),
}