Files
taiji-AI-PAD/services/mcp-server/monitoring.py
T
2026-01-10 19:27:57 +00:00

940 lines
39 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
平台监控模块
提供系统健康检查、性能指标、资源使用等监控功能
"""
import asyncio
import json
import logging
import psutil
import time
import httpx
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text, func
from sqlalchemy.orm import selectinload
from models import Agent, Execution, User, Tool, Session
from database import AsyncSessionLocal
from config import settings
logger = logging.getLogger(__name__)
class SystemMonitor:
"""系统监控类"""
def __init__(self):
self.start_time = datetime.utcnow()
# 服务健康检查端点配置
self._service_endpoints = {
"mcp_server": "http://localhost:8000/health", # 本服务
"data_ingestion": "http://data-ingestion:8000/health", # Docker 内部网络
"model_gateway": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/health/liveliness", # LiteLLM 模型网关
"agent_manager": "http://10.0.0.4:8000", # Agent Manager 服务(本地)
}
# LiteLLM API Key - 从配置读取
self._litellm_api_key = settings.litellm_master_key
async def get_system_health(self) -> Dict[str, Any]:
"""获取系统健康状态,包括三个微服务的状态"""
health = {
"status": "healthy",
"score": 100,
"timestamp": datetime.utcnow().isoformat(),
"uptime_seconds": (datetime.utcnow() - self.start_time).total_seconds(),
"services": {}
}
# 检查数据库(内部使用,不对外暴露)
try:
async with AsyncSessionLocal() as session:
await session.execute(text("SELECT 1"))
# 数据库健康检查成功,但不添加到 services 中
except Exception as e:
# 数据库不健康会影响整体状态
health["status"] = "degraded"
health["score"] -= 25
# 检查三个微服务状态
service_health = await self._check_microservices_health()
health["services"].update(service_health)
# 计算整体健康分数
unhealthy_count = sum(1 for s in health["services"].values()
if s.get("status") not in ["healthy", "running"])
if unhealthy_count > 0:
health["score"] = max(0, 100 - (unhealthy_count * 20))
if unhealthy_count >= 2:
health["status"] = "degraded"
if unhealthy_count >= 3:
health["status"] = "unhealthy"
return health
async def _check_microservices_health(self) -> Dict[str, Dict[str, Any]]:
"""
检查四个微服务的健康状态
返回格式:
{
"mcp_server": { "status": "healthy", "latency": 45 },
"data_ingestion": { "status": "healthy", "latency": 32 },
"model_gateway": { "status": "healthy", "latency": 28 },
"agent_manager": { "status": "healthy", "latency": 15 }
}
状态值:
- healthy/running: 运行中
- maintenance: 维护中
- error: 错误
"""
results = {}
async def check_service(name: str, url: str, headers: Dict[str, str] = None) -> Dict[str, Any]:
"""检查单个服务的健康状态"""
start_time = time.time()
try:
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.get(url, headers=headers)
latency = int((time.time() - start_time) * 1000)
if response.status_code == 200:
return {"status": "healthy", "latency": latency}
elif response.status_code == 503:
return {"status": "maintenance", "latency": latency}
else:
return {"status": "error", "latency": latency, "code": response.status_code}
except httpx.TimeoutException:
return {"status": "error", "latency": 5000, "error": "timeout"}
except httpx.ConnectError:
# 服务不可达,可能是维护中或未启动
return {"status": "maintenance", "latency": 0, "error": "connection_refused"}
except Exception as e:
return {"status": "error", "latency": 0, "error": str(e)}
# mcp_server 本服务始终是健康的(因为能响应请求)
results["mcp_server"] = {"status": "healthy", "latency": 1}
# 检查 data_ingestion 服务
results["data_ingestion"] = await check_service(
"data_ingestion",
self._service_endpoints["data_ingestion"]
)
# 检查 model_gateway (LiteLLM) - 需要带上 API Key
results["model_gateway"] = await check_service(
"model_gateway",
self._service_endpoints["model_gateway"],
headers={"Authorization": f"Bearer {self._litellm_api_key}"}
)
# 检查 agent_manager 服务
results["agent_manager"] = await check_service(
"agent_manager",
self._service_endpoints["agent_manager"]
)
return results
async def get_system_metrics(self) -> Dict[str, Any]:
"""获取系统性能指标"""
try:
# 系统资源使用
cpu_percent = psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
# 数据库统计
async with AsyncSessionLocal() as session:
# Agent统计
agent_result = await session.execute(
text("SELECT COUNT(*) FROM agents WHERE status = 'active'")
)
active_agents = agent_result.scalar() or 0
# 执行统计
execution_result = await session.execute(
text("""
SELECT
COUNT(*) as total,
AVG(execution_time) as avg_time,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END)::float / NULLIF(COUNT(*), 0) * 100 as success_rate
FROM executions
WHERE started_at > NOW() - INTERVAL '24 hours'
""")
)
exec_stats = execution_result.fetchone()
total_executions = exec_stats[0] or 0 if exec_stats else 0
avg_execution_time = float(exec_stats[1] or 0) if exec_stats and exec_stats[1] else 0.0
success_rate = float(exec_stats[2] or 0) if exec_stats and exec_stats[2] else 0.0
# 用户统计
user_result = await session.execute(
text("""
SELECT COUNT(DISTINCT user_id)
FROM sessions
WHERE created_at > NOW() - INTERVAL '24 hours'
""")
)
daily_active_users = user_result.scalar() or 0
# EU消耗统计
eu_result = await session.execute(
text("""
SELECT
SUM(eu_consumed) as total_eu,
SUM(cost) as total_cost
FROM billing
WHERE created_at > NOW() - INTERVAL '24 hours'
""")
)
eu_stats = eu_result.fetchone()
total_eu = float(eu_stats[0] or 0) if eu_stats and eu_stats[0] else 0.0
total_cost = float(eu_stats[1] or 0) if eu_stats and eu_stats[1] else 0.0
return {
"timestamp": datetime.utcnow().isoformat(),
"system": {
"cpu_usage_percent": cpu_percent,
"memory_usage_percent": memory.percent,
"memory_used_mb": memory.used / 1024 / 1024,
"memory_total_mb": memory.total / 1024 / 1024,
"disk_usage_percent": disk.percent,
"disk_used_gb": disk.used / 1024 / 1024 / 1024,
"disk_total_gb": disk.total / 1024 / 1024 / 1024,
},
"services": {
"active_agents": active_agents,
"total_executions_24h": total_executions,
"success_rate_percent": round(success_rate, 2),
"avg_execution_time_ms": round(avg_execution_time, 2),
"daily_active_users": daily_active_users,
},
"billing": {
"total_eu_consumed_24h": round(total_eu, 4),
"total_cost_24h": round(total_cost, 4),
}
}
except Exception as e:
logger.error(f"获取系统指标失败: {e}")
raise
async def get_service_stats(self, service: str = "all") -> Dict[str, Any]:
"""获取服务统计信息"""
try:
async with AsyncSessionLocal() as session:
stats = {}
if service == "all" or service == "agents":
# Agent统计
agent_stats = await session.execute(
text("""
SELECT
COUNT(*) as total,
COUNT(CASE WHEN status = 'active' THEN 1 END) as active,
COUNT(CASE WHEN status = 'inactive' THEN 1 END) as inactive,
AVG(total_executions) as avg_executions,
AVG(success_rate) as avg_success_rate
FROM agents
""")
)
row = agent_stats.fetchone()
if row:
stats["agents"] = {
"total": row[0] or 0,
"active": row[1] or 0,
"inactive": row[2] or 0,
"avg_executions": float(row[3] or 0),
"avg_success_rate": float(row[4] or 0),
}
if service == "all" or service == "executions":
# 执行统计
exec_stats = await session.execute(
text("""
SELECT
COUNT(*) as total,
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed,
COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed,
COUNT(CASE WHEN status = 'running' THEN 1 END) as running,
AVG(execution_time) as avg_time,
SUM(eu_consumed) as total_eu
FROM executions
WHERE started_at > NOW() - INTERVAL '7 days'
""")
)
row = exec_stats.fetchone()
if row:
stats["executions"] = {
"total_7d": row[0] or 0,
"completed": row[1] or 0,
"failed": row[2] or 0,
"running": row[3] or 0,
"avg_time_ms": float(row[4] or 0),
"total_eu": float(row[5] or 0),
}
if service == "all" or service == "tools":
# 工具统计
tool_stats = await session.execute(
text("""
SELECT
COUNT(*) as total,
COUNT(CASE WHEN is_active = true THEN 1 END) as active,
SUM(total_calls) as total_calls,
AVG(success_rate) as avg_success_rate,
AVG(avg_response_time) as avg_response_time
FROM tools
""")
)
row = tool_stats.fetchone()
if row:
stats["tools"] = {
"total": row[0] or 0,
"active": row[1] or 0,
"total_calls": row[2] or 0,
"avg_success_rate": float(row[3] or 0),
"avg_response_time_ms": float(row[4] or 0),
}
if service == "all" or service == "users":
# 用户统计
user_stats = await session.execute(
text("""
SELECT
COUNT(*) as total,
COUNT(CASE WHEN is_active = true THEN 1 END) as active,
COUNT(CASE WHEN is_admin = true THEN 1 END) as admins
FROM users
""")
)
row = user_stats.fetchone()
if row:
stats["users"] = {
"total": row[0] or 0,
"active": row[1] or 0,
"admins": row[2] or 0,
}
return {
"timestamp": datetime.utcnow().isoformat(),
"stats": stats
}
except Exception as e:
logger.error(f"获取服务统计失败: {e}")
raise
async def get_performance_trends(
self,
metric: str = "executions",
period: str = "24h",
interval: str = "1h"
) -> Dict[str, Any]:
"""获取性能趋势数据"""
try:
async with AsyncSessionLocal() as session:
# 计算时间范围
if period == "24h":
hours = 24
elif period == "7d":
hours = 168
elif period == "30d":
hours = 720
else:
hours = 24
if interval == "1h":
interval_sql = "1 hour"
elif interval == "6h":
interval_sql = "6 hours"
elif interval == "1d":
interval_sql = "1 day"
else:
interval_sql = "1 hour"
if metric == "executions":
result = await session.execute(
text(f"""
SELECT
DATE_TRUNC('hour', started_at) as time_bucket,
COUNT(*) as count,
AVG(execution_time) as avg_time,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END)::float / NULLIF(COUNT(*), 0) * 100 as success_rate
FROM executions
WHERE started_at > NOW() - INTERVAL '{hours} hours'
GROUP BY time_bucket
ORDER BY time_bucket
""")
)
rows = result.fetchall()
return {
"metric": metric,
"period": period,
"interval": interval,
"data": [
{
"timestamp": row[0].isoformat() if row[0] else None,
"count": row[1] or 0,
"avg_time_ms": float(row[2] or 0),
"success_rate": float(row[3] or 0),
}
for row in rows
]
}
elif metric == "eu_consumption":
result = await session.execute(
text(f"""
SELECT
DATE_TRUNC('hour', created_at) as time_bucket,
SUM(eu_consumed) as total_eu,
SUM(cost) as total_cost
FROM billing
WHERE created_at > NOW() - INTERVAL '{hours} hours'
GROUP BY time_bucket
ORDER BY time_bucket
""")
)
rows = result.fetchall()
return {
"metric": metric,
"period": period,
"interval": interval,
"data": [
{
"timestamp": row[0].isoformat() if row[0] else None,
"eu_consumed": float(row[1] or 0),
"cost": float(row[2] or 0),
}
for row in rows
]
}
else:
return {
"metric": metric,
"period": period,
"interval": interval,
"data": []
}
except Exception as e:
logger.error(f"获取性能趋势失败: {e}")
raise
async def get_alerts(self, severity: Optional[str] = None) -> List[Dict[str, Any]]:
"""获取系统告警"""
alerts = []
try:
# 检查系统资源
cpu_percent = psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
if cpu_percent > 80:
alerts.append({
"severity": "warning",
"type": "high_cpu",
"message": f"CPU使用率过高: {cpu_percent:.1f}%",
"timestamp": datetime.utcnow().isoformat(),
})
if memory.percent > 85:
alerts.append({
"severity": "warning",
"type": "high_memory",
"message": f"内存使用率过高: {memory.percent:.1f}%",
"timestamp": datetime.utcnow().isoformat(),
})
if disk.percent > 90:
alerts.append({
"severity": "critical",
"type": "low_disk",
"message": f"磁盘空间不足: {disk.percent:.1f}%",
"timestamp": datetime.utcnow().isoformat(),
})
# 检查服务健康
try:
async with AsyncSessionLocal() as session:
# 检查失败的执行
failed_result = await session.execute(
text("""
SELECT COUNT(*)
FROM executions
WHERE status = 'failed'
AND started_at > NOW() - INTERVAL '1 hour'
""")
)
failed_count = failed_result.scalar() or 0
if failed_count > 10:
alerts.append({
"severity": "warning",
"type": "high_failure_rate",
"message": f"过去1小时内有 {failed_count} 次执行失败",
"timestamp": datetime.utcnow().isoformat(),
})
except Exception as db_error:
# 如果executions表不存在或其他数据库错误,仅记录日志不中断
logger.warning(f"检查执行失败率时出错: {db_error}")
# 过滤严重程度
if severity:
alerts = [a for a in alerts if a["severity"] == severity]
return alerts
except Exception as e:
logger.error(f"获取告警失败: {e}")
return []
async def get_global_api_calls(self) -> Dict[str, Any]:
"""
获取全局API调用统计
返回:
- totalApiCalls: 历史总调用次数
- todayApiCalls: 今日调用次数
- todaySuccess: 今日成功次数
- todayFailed: 今日失败次数
"""
try:
async with AsyncSessionLocal() as session:
# 今日开始时间(UTC 00:00:00)
today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
# 尝试从多个表统计API调用
total_api_calls = 0
today_api_calls = 0
today_success = 0
today_failed = 0
# 1. 从 model_billing_records 统计(模型API调用)
try:
# 总调用数
total_result = await session.execute(
text("SELECT COUNT(*) FROM model_billing_records")
)
total_api_calls += total_result.scalar() or 0
# 今日调用数
today_result = await session.execute(
text("""
SELECT
COUNT(*) as total,
COUNT(CASE WHEN status = 'success' THEN 1 END) as success,
COUNT(CASE WHEN status != 'success' THEN 1 END) as failed
FROM model_billing_records
WHERE created_at >= :today_start
"""),
{"today_start": today_start}
)
row = today_result.fetchone()
if row:
today_api_calls += int(row[0] or 0)
today_success += int(row[1] or 0)
today_failed += int(row[2] or 0)
except Exception as e:
logger.warning(f"统计model_billing_records失败: {e}")
await session.rollback()
# 2. 从 agent_billing_records 统计(Agent运行时调用)
try:
# 总调用数
agent_total_result = await session.execute(
text("SELECT COUNT(*) FROM agent_billing_records")
)
total_api_calls += agent_total_result.scalar() or 0
# 今日调用数
agent_today_result = await session.execute(
text("""
SELECT COUNT(*)
FROM agent_billing_records
WHERE start_time >= :today_start
"""),
{"today_start": today_start}
)
agent_today = agent_today_result.scalar() or 0
today_api_calls += agent_today
today_success += agent_today # Agent计费记录默认成功
except Exception as e:
logger.warning(f"统计agent_billing_records失败: {e}")
await session.rollback()
# 3. 从 executions 统计(执行记录)
try:
exec_total_result = await session.execute(
text("SELECT COUNT(*) FROM executions")
)
total_api_calls += exec_total_result.scalar() or 0
exec_today_result = await session.execute(
text("""
SELECT
COUNT(*) as total,
COUNT(CASE WHEN status = 'completed' THEN 1 END) as success,
COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed
FROM executions
WHERE started_at >= :today_start
"""),
{"today_start": today_start}
)
exec_row = exec_today_result.fetchone()
if exec_row:
today_api_calls += int(exec_row[0] or 0)
today_success += int(exec_row[1] or 0)
today_failed += int(exec_row[2] or 0)
except Exception as e:
logger.warning(f"统计executions失败: {e}")
await session.rollback()
return {
"totalApiCalls": total_api_calls,
"todayApiCalls": today_api_calls,
"todaySuccess": today_success,
"todayFailed": today_failed,
}
except Exception as e:
logger.error(f"获取全局API调用统计失败: {e}")
return {
"totalApiCalls": 0,
"todayApiCalls": 0,
"todaySuccess": 0,
"todayFailed": 0,
}
async def get_tenant_dashboard(self, tenant_id: str) -> Dict[str, Any]:
"""
获取租户级别的监控仪表盘数据
包含:
1. 当前租户EU消耗过去24小时的消耗情况
2. 使用的模型和模型的使用情况、当前调用次数
3. 每周API调用的次数,7天的每一天调用多少次,包括成功和失败
4. 三个微服务的状态
✅ 优化:使用单个数据库会话,避免连接池耗尽
"""
try:
# 使用单个数据库会话执行所有查询,减少连接数
async with AsyncSessionLocal() as session:
try:
# 1. 获取租户过去24小时的EU消耗
eu_consumption = await self._get_tenant_eu_consumption_24h(session, tenant_id)
# 2. 获取模型使用情况
model_usage = await self._get_tenant_model_usage(session, tenant_id)
# 3. 获取每周API调用统计(7天每天成功/失败)
weekly_api_calls = await self._get_tenant_weekly_api_calls(session, tenant_id)
# 提交所有查询
await session.commit()
except Exception as db_error:
# 数据库错误时回滚并返回默认值
logger.error(f"查询租户仪表盘数据失败: {db_error}")
await session.rollback()
eu_consumption = {"total": 0, "totalCost": 0, "totalCalls": 0, "hourlyData": []}
model_usage = {"models": [], "totalCalls": 0}
weekly_api_calls = {"dailyData": [], "totalSuccess": 0, "totalFailed": 0, "total": 0}
# 4. 获取微服务状态(不使用数据库连接)
services_health = await self._check_microservices_health()
return {
"timestamp": datetime.utcnow().isoformat(),
"tenantId": tenant_id,
"euConsumption24h": eu_consumption,
"modelUsage": model_usage,
"weeklyApiCalls": weekly_api_calls,
"health": {
"score": 100, # 将在下面计算
"services": services_health
}
}
except Exception as e:
logger.error(f"获取租户仪表盘数据失败: {e}")
# 返回默认值而不是抛出异常
return {
"timestamp": datetime.utcnow().isoformat(),
"tenantId": tenant_id,
"euConsumption24h": {"total": 0, "totalCost": 0, "totalCalls": 0, "hourlyData": []},
"modelUsage": {"models": [], "totalCalls": 0},
"weeklyApiCalls": {"dailyData": [], "totalSuccess": 0, "totalFailed": 0, "total": 0},
"health": {
"score": 100,
"services": {}
}
}
async def _get_tenant_eu_consumption_24h(
self,
session: AsyncSession,
tenant_id: str
) -> Dict[str, Any]:
"""
获取租户过去24小时的EU消耗情况
✅ 修复:改为查询 agent_billing_records 表
✅ 增强:合并 model_billing_records 表(LiteLLM Token计费)
返回:
- total: 总EU消耗
- hourlyData: 每小时的消耗数据
- agentEU: Agent运行时长计费的EU
- modelEU: 模型Token计费的EU
"""
# 初始化默认值
agent_row = None
model_row = None
hourly_rows = []
try:
# Agent运行时长计费 - 独立捕获异常
# 注意:agent_billing_records 表使用 cost 列,没有 eu_consumed 列
try:
agent_result = await session.execute(
text("""
SELECT
COALESCE(SUM(cost), 0) as total_cost,
COUNT(*) as total_calls
FROM agent_billing_records
WHERE user_id = :tenant_id
AND start_time > NOW() - INTERVAL '24 hours'
"""),
{"tenant_id": tenant_id}
)
agent_row = agent_result.fetchone()
except Exception as agent_error:
logger.warning(f"查询agent_billing_records失败: {agent_error}")
# 不要回滚,继续使用同一个session
# 模型Token计费 - 独立捕获异常
try:
model_result = await session.execute(
text("""
SELECT
COALESCE(SUM(eu_consumed), 0) as total_eu,
COALESCE(SUM(total_cost), 0) as total_cost,
COUNT(*) as total_calls
FROM model_billing_records
WHERE tenant_id = :tenant_id
AND created_at > NOW() - INTERVAL '24 hours'
"""),
{"tenant_id": tenant_id}
)
model_row = model_result.fetchone()
except Exception as model_error:
logger.warning(f"查询model_billing_records失败: {model_error}")
# 不要回滚,继续使用同一个session
# 每小时消耗 - 仅查询存在的表
try:
# 尝试查询model_billing_records的每小时数据
hourly_result = await session.execute(
text("""
SELECT
DATE_TRUNC('hour', created_at) as hour,
COALESCE(SUM(eu_consumed), 0) as eu,
COALESCE(SUM(total_cost), 0) as cost,
COUNT(*) as calls
FROM model_billing_records
WHERE tenant_id = :tenant_id
AND created_at > NOW() - INTERVAL '24 hours'
GROUP BY DATE_TRUNC('hour', created_at)
ORDER BY hour
"""),
{"tenant_id": tenant_id}
)
hourly_rows = hourly_result.fetchall()
except Exception as hourly_error:
logger.warning(f"查询每小时数据失败: {hourly_error}")
# 不要回滚,继续使用同一个session
# 合并数据
# agent_row: (total_cost, total_calls)
# model_row: (total_eu, total_cost, total_calls)
agent_cost = float(agent_row[0]) if agent_row else 0
agent_calls = int(agent_row[1]) if agent_row else 0
model_eu = float(model_row[0]) if model_row else 0
model_cost = float(model_row[1]) if model_row else 0
model_calls = int(model_row[2]) if model_row else 0
# Agent计费使用cost换算EU(假设1 EU = 1 cost单位)
agent_eu = agent_cost
total_eu = agent_eu + model_eu
total_cost = agent_cost + model_cost
total_calls = agent_calls + model_calls
return {
"total": total_eu,
"totalCost": total_cost,
"totalCalls": total_calls,
"agentEU": agent_eu,
"modelEU": model_eu,
"hourlyData": [
{
"timestamp": row[0].isoformat() if row[0] else None,
"value": float(row[1]),
"cost": float(row[2]),
"calls": int(row[3])
}
for row in hourly_rows
]
}
except Exception as e:
logger.warning(f"获取租户EU消耗失败: {e}")
return {"total": 0, "totalCost": 0, "totalCalls": 0, "agentEU": 0, "modelEU": 0, "hourlyData": []}
async def _get_tenant_model_usage(
self,
session: AsyncSession,
tenant_id: str
) -> Dict[str, Any]:
"""
获取租户的模型使用情况
✅ 修复:改为查询 model_billing_records 表(LiteLLM Token计费)
返回:
- models: 使用的模型列表及其调用次数
- totalCalls: 总调用次数
"""
try:
# 查询模型使用统计 - 使用model_billing_records表
result = await session.execute(
text("""
SELECT
COALESCE(model_name, 'unknown') as model,
COUNT(*) as calls,
COALESCE(SUM(eu_consumed), 0) as eu_consumed,
COALESCE(SUM(total_cost), 0) as cost,
COALESCE(SUM(total_tokens), 0) as total_tokens
FROM model_billing_records
WHERE tenant_id = :tenant_id
AND created_at > NOW() - INTERVAL '30 days'
GROUP BY model_name
ORDER BY calls DESC
"""),
{"tenant_id": tenant_id}
)
rows = result.fetchall()
await session.commit() # 确保提交查询
models = []
total_calls = 0
for row in rows:
calls = int(row[1])
total_calls += calls
models.append({
"name": row[0],
"calls": calls,
"euConsumed": float(row[2]),
"cost": float(row[3]),
"totalTokens": int(row[4])
})
return {
"models": models,
"totalCalls": total_calls
}
except Exception as e:
logger.warning(f"获取租户模型使用情况失败: {e}")
# 不要回滚,返回空数据
return {"models": [], "totalCalls": 0}
async def _get_tenant_weekly_api_calls(
self,
session: AsyncSession,
tenant_id: str
) -> Dict[str, Any]:
"""
获取租户每周API调用统计(7天每天成功/失败)
✅ 修复:改为查询 model_billing_records 表(模型调用统计)
返回:
- dailyData: 7天每天的调用数据
- totalSuccess: 总成功次数
- totalFailed: 总失败次数
"""
try:
# 查询7天每天的模型调用统计
result = await session.execute(
text("""
SELECT
DATE(created_at) as date,
COUNT(*) as total,
COUNT(CASE WHEN status = 'success' THEN 1 END) as success,
COUNT(CASE WHEN status != 'success' THEN 1 END) as failed
FROM model_billing_records
WHERE tenant_id = :tenant_id
AND created_at > NOW() - INTERVAL '7 days'
GROUP BY DATE(created_at)
ORDER BY date
"""),
{"tenant_id": tenant_id}
)
rows = result.fetchall()
await session.commit() # 确保提交查询
daily_data = []
total_success = 0
total_failed = 0
# 生成过去7天的日期列表
today = datetime.utcnow().date()
date_map = {}
for row in rows:
if row[0]:
date_map[row[0]] = {
"total": int(row[1]),
"success": int(row[2]),
"failed": int(row[3])
}
total_success += int(row[2])
total_failed += int(row[3])
# 填充7天数据(包括没有数据的日期)
for i in range(6, -1, -1):
date = today - timedelta(days=i)
if date in date_map:
daily_data.append({
"date": date.isoformat(),
"total": date_map[date]["total"],
"success": date_map[date]["success"],
"failed": date_map[date]["failed"]
})
else:
daily_data.append({
"date": date.isoformat(),
"total": 0,
"success": 0,
"failed": 0
})
return {
"dailyData": daily_data,
"totalSuccess": total_success,
"totalFailed": total_failed,
"total": total_success + total_failed
}
except Exception as e:
logger.warning(f"获取租户每周API调用统计失败: {e}")
# 不要回滚,返回空数据
# 返回空的7天数据
today = datetime.utcnow().date()
daily_data = [
{"date": (today - timedelta(days=i)).isoformat(), "total": 0, "success": 0, "failed": 0}
for i in range(6, -1, -1)
]
return {"dailyData": daily_data, "totalSuccess": 0, "totalFailed": 0, "total": 0}
# 全局监控实例
system_monitor = SystemMonitor()