Files
taiji-AI-PAD/services/mcp-server/app/routes/resource_monitoring.py
T
2026-01-16 10:38:17 +00:00

417 lines
16 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.
"""
资源监控 API 路由
提供平台资源概览、用户资源使用汇总、资源使用趋势和Agent资源统计
"""
from typing import Optional
from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, Query, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_, or_, case
from decimal import Decimal
from database import get_db
from app.auth import require_role
from models import (
User, Agent, ResourceUsage, BillingRecord,
AgentTrace, AgentBillingRecord, ModelBillingRecord
)
router = APIRouter(
prefix="/api/billing-admin/resources",
tags=["resource-monitoring"]
)
# =====================================================
# 1. 获取平台资源概览
# =====================================================
@router.get("/overview")
async def get_platform_overview(
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_role(["super_admin", "billing_admin", "operations_admin"]))
):
"""
获取平台整体资源使用概览(管理员视图)
返回数据:
- todayCalls: 今日调用次数
- monthCalls: 本月调用次数
- activeUsersToday: 今日活跃用户数
- activeAgents: 活跃Agent数(平台端 + 自定义)
- monthTotalEu: 本月总EU消费
- platformAgents: 平台端 Agent 统计
- customAgents: 自定义 Agent 统计
**权限要求**: super_admin, billing_admin, operations_admin
"""
try:
now = datetime.utcnow()
today_start = datetime(now.year, now.month, now.day)
month_start = datetime(now.year, now.month, 1)
# 1. 今日调用次数(从resource_usage统计)
today_calls_result = await db.execute(
select(func.count(ResourceUsage.id))
.where(ResourceUsage.created_at >= today_start)
)
today_calls = today_calls_result.scalar() or 0
# 2. 本月调用次数
month_calls_result = await db.execute(
select(func.count(ResourceUsage.id))
.where(ResourceUsage.created_at >= month_start)
)
month_calls = month_calls_result.scalar() or 0
# 3. 今日活跃用户数(DISTINCT user_id)
active_users_result = await db.execute(
select(func.count(func.distinct(ResourceUsage.user_id)))
.where(ResourceUsage.created_at >= today_start)
)
active_users_today = active_users_result.scalar() or 0
# 4. 活跃Agent数(平台端 + 自定义)
# 4.1 平台端 Agent 统计
platform_agents_result = await db.execute(
select(Agent)
.where(and_(Agent.type == "platform", Agent.status == "active"))
)
platform_agents = platform_agents_result.scalars().all()
platform_agents_count = len(platform_agents)
platform_cpu = sum(float(a.cpu or 0) for a in platform_agents)
platform_memory = sum(float(a.memory or 0) for a in platform_agents)
platform_healthy = len([a for a in platform_agents if a.health_status == "healthy"])
# 4.2 自定义 Agent 统计
custom_agents_result = await db.execute(
select(Agent)
.where(and_(Agent.type == "custom", Agent.status == "active"))
)
custom_agents = custom_agents_result.scalars().all()
custom_agents_count = len(custom_agents)
custom_cpu = sum(float(a.cpu or 0) for a in custom_agents)
custom_memory = sum(float(a.memory or 0) for a in custom_agents)
custom_healthy = len([a for a in custom_agents if a.health_status == "healthy"])
# 总活跃 Agent 数
active_agents = platform_agents_count + custom_agents_count
# 5. 本月总EU消费(合并 Agent运行时间 + 模型Token 两种计费)
# 5.1 Agent 运行时间计费的 EU(从 agent_billing_records 表)
agent_eu_result = await db.execute(
select(func.sum(AgentBillingRecord.eu_consumed))
.where(AgentBillingRecord.period_start >= month_start)
)
agent_eu = agent_eu_result.scalar() or 0
# 5.2 模型 Token 计费的 EU(从 model_billing_records 表)
model_eu_result = await db.execute(
select(func.sum(ModelBillingRecord.eu_consumed))
.where(ModelBillingRecord.created_at >= month_start)
)
model_eu = model_eu_result.scalar() or 0
# 5.3 合并两种 EU 消耗
month_total_eu = float(agent_eu or 0) + float(model_eu or 0)
return {
"success": True,
"data": {
"todayCalls": today_calls,
"monthCalls": month_calls,
"activeUsersToday": active_users_today,
"activeAgents": active_agents,
"monthTotalEu": round(float(month_total_eu), 2),
"timestamp": now.isoformat(),
# 平台端 Agent 统计
"platformAgents": {
"count": platform_agents_count,
"healthy": platform_healthy,
"cpu": round(platform_cpu, 2),
"memory": round(platform_memory, 2),
},
# 自定义 Agent 统计
"customAgents": {
"count": custom_agents_count,
"healthy": custom_healthy,
"cpu": round(custom_cpu, 2),
"memory": round(custom_memory, 2),
},
# 总资源使用
"totalResources": {
"cpu": round(platform_cpu + custom_cpu, 2),
"memory": round(platform_memory + custom_memory, 2),
},
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取平台资源概览失败: {str(e)}")
# =====================================================
# 2. 获取用户资源使用汇总
# =====================================================
@router.get("/user/{user_id}")
async def get_user_resource_summary(
user_id: str,
start_date: str = Query(..., description="开始日期 (ISO 8601格式)"),
end_date: str = Query(..., description="结束日期 (ISO 8601格式)"),
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_role(["super_admin", "billing_admin", "operations_admin"]))
):
"""
获取指定用户在时间范围内的资源使用汇总
返回数据:
- totalCpuSeconds: CPU总使用时间(秒)
- totalMemoryMbSeconds: 内存总使用量(MB·秒)
- totalNetworkBytes: 网络总流量(字节)
- totalStorageBytes: 存储总使用量(字节)
- totalApiCalls: API总调用次数
**权限要求**: super_admin, billing_admin, operations_admin
"""
try:
# 解析日期
start_dt = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
end_dt = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
# 验证用户存在
user_result = await db.execute(
select(User).where(User.id == user_id)
)
user = user_result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 聚合ResourceUsage表
usage_result = await db.execute(
select(
func.sum(ResourceUsage.cpu_seconds).label("total_cpu"),
func.sum(ResourceUsage.memory_mb_seconds).label("total_memory"),
func.sum(ResourceUsage.network_bytes).label("total_network"),
func.sum(ResourceUsage.storage_bytes).label("total_storage"),
func.count(ResourceUsage.id).label("total_calls")
)
.where(
and_(
ResourceUsage.user_id == user_id,
ResourceUsage.created_at >= start_dt,
ResourceUsage.created_at <= end_dt
)
)
)
usage = usage_result.first()
return {
"success": True,
"data": {
"totalCpuSeconds": round(float(usage.total_cpu or 0), 2),
"totalMemoryMbSeconds": round(float(usage.total_memory or 0), 2),
"totalNetworkBytes": int(usage.total_network or 0),
"totalStorageBytes": int(usage.total_storage or 0),
"totalApiCalls": int(usage.total_calls or 0),
"startDate": start_dt.isoformat(),
"endDate": end_dt.isoformat()
}
}
except ValueError as e:
raise HTTPException(status_code=400, detail=f"日期格式错误: {str(e)}")
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取用户资源使用汇总失败: {str(e)}")
# =====================================================
# 3. 获取资源使用趋势
# =====================================================
@router.get("/trends")
async def get_resource_trends(
user_id: str = Query(..., description="用户ID"),
period: str = Query("7d", regex="^(7d|30d|90d)$", description="时间范围: 7d, 30d, 90d"),
granularity: str = Query("daily", regex="^(hourly|daily)$", description="粒度: hourly, daily"),
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_role(["super_admin", "billing_admin", "operations_admin"]))
):
"""
获取用户资源使用趋势
**查询参数**:
- user_id: 用户ID
- period: 时间范围 (7d, 30d, 90d)
- granularity: 粒度 (hourly, daily)
**权限要求**: super_admin, billing_admin, operations_admin
"""
try:
# 计算时间范围
now = datetime.utcnow()
period_days = {
"7d": 7,
"30d": 30,
"90d": 90
}
start_date = now - timedelta(days=period_days[period])
# 验证用户存在
user_result = await db.execute(
select(User).where(User.id == user_id)
)
user = user_result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 根据粒度查询
if granularity == "hourly":
# PostgreSQL: DATE_TRUNC('hour', created_at)
time_bucket = func.date_trunc('hour', ResourceUsage.created_at)
else: # daily
# PostgreSQL: DATE(created_at)
time_bucket = func.date(ResourceUsage.created_at)
# 聚合查询
trends_result = await db.execute(
select(
time_bucket.label("period_start"),
func.sum(ResourceUsage.cpu_seconds).label("cpu_seconds"),
func.sum(ResourceUsage.memory_mb_seconds).label("memory_mb_seconds"),
func.sum(ResourceUsage.network_bytes).label("network_bytes"),
func.count(ResourceUsage.id).label("api_calls")
)
.where(
and_(
ResourceUsage.user_id == user_id,
ResourceUsage.created_at >= start_date
)
)
.group_by(time_bucket)
.order_by(time_bucket)
)
trends = trends_result.all()
# 构造响应
trends_data = []
for trend in trends:
period_start = trend.period_start
# 计算period_end
if granularity == "hourly":
period_end = period_start + timedelta(hours=1) - timedelta(seconds=1)
else:
period_end = period_start + timedelta(days=1) - timedelta(seconds=1)
trends_data.append({
"periodStart": period_start.isoformat() if hasattr(period_start, 'isoformat') else str(period_start),
"periodEnd": period_end.isoformat(),
"cpuSeconds": round(float(trend.cpu_seconds or 0), 2),
"memoryMbSeconds": round(float(trend.memory_mb_seconds or 0), 2),
"networkBytes": int(trend.network_bytes or 0),
"apiCalls": int(trend.api_calls or 0)
})
return {
"success": True,
"data": {
"trends": trends_data
}
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取资源使用趋势失败: {str(e)}")
# =====================================================
# 4. 获取Agent资源统计
# =====================================================
@router.get("/agent/{agent_id}")
async def get_agent_resource_stats(
agent_id: str,
start_date: str = Query(..., description="开始日期 (ISO 8601格式)"),
end_date: str = Query(..., description="结束日期 (ISO 8601格式)"),
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_role(["super_admin", "billing_admin", "operations_admin"]))
):
"""
获取指定Agent在时间范围内的资源统计
返回数据:
- agentId: Agent ID
- totalExecutions: 总执行次数
- avgExecutionTime: 平均执行时间(毫秒)
- totalEuConsumed: 总EU消耗
- successRate: 成功率(百分比)
**权限要求**: super_admin, billing_admin, operations_admin
"""
try:
# 解析日期
start_dt = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
end_dt = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
# 验证Agent存在
agent_result = await db.execute(
select(Agent).where(Agent.id == agent_id)
)
agent = agent_result.scalar_one_or_none()
if not agent:
raise HTTPException(status_code=404, detail="Agent不存在")
# 从AgentTrace表统计
stats_result = await db.execute(
select(
func.count(AgentTrace.id).label("total_executions"),
func.avg(AgentTrace.duration_ms).label("avg_duration"),
func.sum(AgentTrace.eu_consumed).label("total_eu"), # 修复:使用正确的字段名 eu_consumed
func.sum(
func.case(
(AgentTrace.status == "success", 1),
else_=0
)
).label("success_count")
)
.where(
and_(
AgentTrace.agent_id == agent_id,
AgentTrace.created_at >= start_dt,
AgentTrace.created_at <= end_dt
)
)
)
stats = stats_result.first()
total_executions = int(stats.total_executions or 0)
success_count = int(stats.success_count or 0)
success_rate = (success_count / total_executions * 100) if total_executions > 0 else 0
return {
"success": True,
"data": {
"agentId": agent_id,
"totalExecutions": total_executions,
"avgExecutionTime": round(float(stats.avg_duration or 0), 2),
"totalEuConsumed": round(float(stats.total_eu or 0), 2),
"successRate": round(success_rate, 2),
"startDate": start_dt.isoformat(),
"endDate": end_dt.isoformat()
}
}
except ValueError as e:
raise HTTPException(status_code=400, detail=f"日期格式错误: {str(e)}")
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取Agent资源统计失败: {str(e)}")