File size: 6,733 Bytes
e0b68d2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
"""管理后台统计聚合辅助函数。"""

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,
        ),
    }