Files
taiji-AI-PAD/services/mcp-server/app/resource_monitor.py
T
2026-03-10 06:40:38 +00:00

392 lines
12 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.
"""
资源使用监控模块
采集和统计用户资源使用情况
注意:计费数据已迁移到新表:
- AgentBillingRecord: Agent 运行时计费
- ModelBillingRecord: 模型调用计费(LiteLLM)
旧的 BillingRecord 表已废弃,不再使用。
"""
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Dict, List, Optional, Tuple
from sqlalchemy import select, func, and_
from sqlalchemy.ext.asyncio import AsyncSession
from models import ResourceUsage, User, Agent, Execution, AgentBillingRecord, ModelBillingRecord
async def record_resource_usage(
user_id: str,
agent_id: Optional[str],
cpu_seconds: float,
memory_mb_seconds: float,
network_bytes: int,
storage_bytes: int,
api_calls: int,
period_start: datetime,
period_end: datetime,
granularity: str,
db: AsyncSession
) -> ResourceUsage:
"""
记录资源使用情况
Args:
user_id: 用户ID
agent_id: Agent ID
cpu_seconds: CPU使用秒数
memory_mb_seconds: 内存使用 (MB*秒)
network_bytes: 网络流量字节
storage_bytes: 存储使用字节
api_calls: API调用次数
period_start: 周期开始时间
period_end: 周期结束时间
granularity: 粒度 (hourly, daily, monthly)
db: 数据库会话
Returns:
ResourceUsage记录
"""
# 检查是否已有该周期的记录
result = await db.execute(
select(ResourceUsage).where(
and_(
ResourceUsage.user_id == user_id,
ResourceUsage.agent_id == agent_id if agent_id else True,
ResourceUsage.period_start == period_start,
ResourceUsage.granularity == granularity,
)
)
)
existing = result.scalar_one_or_none()
if existing:
# 更新现有记录
existing.cpu_seconds += cpu_seconds
existing.memory_mb_seconds += memory_mb_seconds
existing.network_bytes += network_bytes
existing.storage_bytes += storage_bytes
existing.api_calls += api_calls
usage = existing
else:
# 创建新记录
usage = ResourceUsage(
user_id=user_id,
agent_id=agent_id,
cpu_seconds=cpu_seconds,
memory_mb_seconds=memory_mb_seconds,
network_bytes=network_bytes,
storage_bytes=storage_bytes,
api_calls=api_calls,
period_start=period_start,
period_end=period_end,
granularity=granularity,
)
db.add(usage)
await db.commit()
await db.refresh(usage)
return usage
async def get_user_resource_summary(
user_id: str,
start_date: datetime,
end_date: datetime,
db: AsyncSession
) -> Dict:
"""
获取用户资源使用汇总
Args:
user_id: 用户ID
start_date: 开始日期
end_date: 结束日期
db: 数据库会话
Returns:
资源使用汇总
"""
result = await db.execute(
select(
func.sum(ResourceUsage.cpu_seconds).label("total_cpu_seconds"),
func.sum(ResourceUsage.memory_mb_seconds).label("total_memory_mb_seconds"),
func.sum(ResourceUsage.network_bytes).label("total_network_bytes"),
func.sum(ResourceUsage.storage_bytes).label("total_storage_bytes"),
func.sum(ResourceUsage.api_calls).label("total_api_calls"),
)
.where(
and_(
ResourceUsage.user_id == user_id,
ResourceUsage.period_start >= start_date,
ResourceUsage.period_end <= end_date,
)
)
)
row = result.first()
return {
"totalCpuSeconds": float(row.total_cpu_seconds or 0),
"totalMemoryMbSeconds": float(row.total_memory_mb_seconds or 0),
"totalNetworkBytes": int(row.total_network_bytes or 0),
"totalStorageBytes": int(row.total_storage_bytes or 0),
"totalApiCalls": int(row.total_api_calls or 0),
"startDate": start_date.isoformat(),
"endDate": end_date.isoformat(),
}
async def get_resource_trends(
user_id: str,
period: str,
granularity: str,
db: AsyncSession
) -> List[Dict]:
"""
获取资源使用趋势
Args:
user_id: 用户ID
period: 时间范围 (7d, 30d, 90d)
granularity: 粒度 (hourly, daily)
db: 数据库会话
Returns:
资源使用趋势列表
"""
days_map = {"7d": 7, "30d": 30, "90d": 90}
days = days_map.get(period, 7)
start_date = datetime.utcnow() - timedelta(days=days)
result = await db.execute(
select(ResourceUsage)
.where(
and_(
ResourceUsage.user_id == user_id,
ResourceUsage.period_start >= start_date,
ResourceUsage.granularity == granularity,
)
)
.order_by(ResourceUsage.period_start)
)
usages = result.scalars().all()
return [
{
"periodStart": usage.period_start.isoformat(),
"periodEnd": usage.period_end.isoformat(),
"cpuSeconds": float(usage.cpu_seconds),
"memoryMbSeconds": float(usage.memory_mb_seconds),
"networkBytes": int(usage.network_bytes),
"apiCalls": int(usage.api_calls),
}
for usage in usages
]
async def get_agent_resource_stats(
agent_id: str,
start_date: datetime,
end_date: datetime,
db: AsyncSession
) -> Dict:
"""
获取Agent资源统计
Args:
agent_id: Agent ID
start_date: 开始日期
end_date: 结束日期
db: 数据库会话
Returns:
Agent资源统计
"""
# 执行次数和时间统计
exec_result = await db.execute(
select(
func.count(Execution.id).label("total_executions"),
func.avg(Execution.execution_time).label("avg_execution_time"),
func.sum(Execution.eu_consumed).label("total_eu"),
)
.where(
and_(
Execution.agent_id == agent_id,
Execution.started_at >= start_date,
Execution.started_at <= end_date,
)
)
)
exec_row = exec_result.first()
# 成功率
success_result = await db.execute(
select(func.count(Execution.id))
.where(
and_(
Execution.agent_id == agent_id,
Execution.started_at >= start_date,
Execution.status == "completed",
)
)
)
success_count = success_result.scalar() or 0
total_count = exec_row.total_executions or 1
return {
"agentId": agent_id,
"totalExecutions": int(exec_row.total_executions or 0),
"avgExecutionTime": float(exec_row.avg_execution_time or 0),
"totalEuConsumed": float(exec_row.total_eu or 0),
"successRate": round(success_count / total_count * 100, 2) if total_count > 0 else 0,
"startDate": start_date.isoformat(),
"endDate": end_date.isoformat(),
}
async def get_platform_resource_overview(db: AsyncSession) -> Dict:
"""
获取平台资源概览(管理员视图)
数据来源:
- AgentBillingRecord: Agent 运行时计费
- ModelBillingRecord: 模型调用计费(LiteLLM)
Args:
db: 数据库会话
Returns:
平台资源概览
"""
now = datetime.utcnow()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
# 今日调用次数(Agent + Model)
today_agent_calls = await db.execute(
select(func.count(AgentBillingRecord.id))
.where(AgentBillingRecord.start_time >= today_start)
)
today_model_calls = await db.execute(
select(func.count(ModelBillingRecord.id))
.where(ModelBillingRecord.created_at >= today_start)
)
today_calls_total = (today_agent_calls.scalar() or 0) + (today_model_calls.scalar() or 0)
# 本月调用次数(Agent + Model)
month_agent_calls = await db.execute(
select(func.count(AgentBillingRecord.id))
.where(AgentBillingRecord.start_time >= month_start)
)
month_model_calls = await db.execute(
select(func.count(ModelBillingRecord.id))
.where(ModelBillingRecord.created_at >= month_start)
)
month_calls_total = (month_agent_calls.scalar() or 0) + (month_model_calls.scalar() or 0)
# 活跃用户数(今日有 Agent 或 Model 调用的用户)
active_agent_users = await db.execute(
select(func.count(func.distinct(AgentBillingRecord.user_id)))
.where(AgentBillingRecord.start_time >= today_start)
)
active_model_users = await db.execute(
select(func.count(func.distinct(ModelBillingRecord.tenant_id)))
.where(ModelBillingRecord.created_at >= today_start)
)
# 注意:这里简单相加可能有重复,但作为概览统计可以接受
active_users_total = max(active_agent_users.scalar() or 0, active_model_users.scalar() or 0)
# 活跃Agent数
active_agents = await db.execute(
select(func.count(Agent.id))
.where(Agent.status == "active")
)
# 总EU消耗(Agent + Model)
agent_eu = await db.execute(
select(func.sum(AgentBillingRecord.eu_consumed))
.where(AgentBillingRecord.start_time >= month_start)
)
model_eu = await db.execute(
select(func.sum(ModelBillingRecord.eu_consumed))
.where(ModelBillingRecord.created_at >= month_start)
)
total_eu = (agent_eu.scalar() or 0) + (model_eu.scalar() or 0)
return {
"todayCalls": today_calls_total,
"monthCalls": month_calls_total,
"activeUsersToday": active_users_total,
"activeAgents": active_agents.scalar() or 0,
"monthTotalEu": int(total_eu),
"timestamp": now.isoformat(),
}
async def aggregate_hourly_usage(db: AsyncSession) -> int:
"""
聚合小时级资源使用数据(定时任务调用)
Args:
db: 数据库会话
Returns:
聚合的记录数
"""
# 获取上一小时的时间范围
now = datetime.utcnow()
period_end = now.replace(minute=0, second=0, microsecond=0)
period_start = period_end - timedelta(hours=1)
# 从执行记录聚合
result = await db.execute(
select(
Execution.agent_id,
func.count(Execution.id).label("api_calls"),
func.sum(Execution.execution_time).label("total_time"),
func.sum(Execution.cpu_usage).label("cpu_seconds"),
func.sum(Execution.memory_usage).label("memory_usage"),
)
.where(
and_(
Execution.started_at >= period_start,
Execution.started_at < period_end,
)
)
.group_by(Execution.agent_id)
)
count = 0
for row in result.all():
if row.agent_id:
# 获取Agent所有者
agent_result = await db.execute(
select(Agent.owner_id).where(Agent.id == row.agent_id)
)
owner = agent_result.scalar()
if owner:
await record_resource_usage(
user_id=str(owner),
agent_id=str(row.agent_id),
cpu_seconds=float(row.cpu_seconds or 0),
memory_mb_seconds=float(row.memory_usage or 0) * (float(row.total_time or 0) / 1000),
network_bytes=0,
storage_bytes=0,
api_calls=int(row.api_calls or 0),
period_start=period_start,
period_end=period_end,
granularity="hourly",
db=db,
)
count += 1
return count