Build lightweight AI agent admin

This commit is contained in:
Codex
2026-06-08 18:14:59 +08:00
commit e164840f43
2530 changed files with 435693 additions and 0 deletions
@@ -0,0 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Redis监控模块
"""
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Redis监控API
"""
from fastapi import APIRouter
from app.config import settings
from core.redis_monitor.redis_collector import AsyncRedisInfoCollector
from core.redis_monitor.schema import (
RedisMonitorOverviewSchema,
RedisRealtimeStatsSchema,
RedisConnectionTestSchema,
RedisConfigSchema,
)
router = APIRouter(prefix="/redis_monitor", tags=["Redis监控"])
def get_redis_config():
"""获取项目Redis配置"""
redis_host = settings.REDIS_HOST
redis_port = settings.REDIS_PORT
redis_password = settings.REDIS_PASSWORD
redis_db = settings.REDIS_DB
# 如果密码为空字符串,设置为None
if redis_password == '':
redis_password = None
return redis_host, redis_port, redis_password, redis_db
@router.get("/overview", response_model=RedisMonitorOverviewSchema, summary="获取Redis监控概览")
async def get_redis_monitor_overview():
"""获取Redis监控概览信息"""
redis_host, redis_port, redis_password, redis_db = get_redis_config()
collector = AsyncRedisInfoCollector(
host=redis_host,
port=redis_port,
password=redis_password,
db=redis_db
)
data = await collector.get_all_info('project_redis', '项目Redis')
return RedisMonitorOverviewSchema(**data)
@router.get("/realtime", response_model=RedisRealtimeStatsSchema, summary="获取Redis实时统计")
async def get_redis_realtime_stats():
"""获取Redis实时统计信息"""
redis_host, redis_port, redis_password, redis_db = get_redis_config()
collector = AsyncRedisInfoCollector(
host=redis_host,
port=redis_port,
password=redis_password,
db=redis_db
)
data = await collector.get_realtime_stats('project_redis')
return RedisRealtimeStatsSchema(**data)
@router.post("/test", response_model=RedisConnectionTestSchema, summary="测试Redis连接")
async def test_redis_connection():
"""测试Redis连接"""
redis_host, redis_port, redis_password, redis_db = get_redis_config()
collector = AsyncRedisInfoCollector(
host=redis_host,
port=redis_port,
password=redis_password,
db=redis_db
)
result = await collector.test_connection()
return RedisConnectionTestSchema(**result)
@router.get("/config", response_model=RedisConfigSchema, summary="获取Redis配置")
def get_redis_config_info():
"""获取Redis配置信息"""
redis_host, redis_port, redis_password, redis_db = get_redis_config()
return RedisConfigSchema(
host=redis_host,
port=redis_port,
database=redis_db,
has_password=bool(redis_password),
redis_url=settings.REDIS_URL or ''
)
@@ -0,0 +1,455 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Redis信息收集器(异步版本)
"""
import logging
import time
from datetime import datetime
from typing import Dict, List, Any, Optional
import redis.asyncio as aioredis
logger = logging.getLogger(__name__)
def serialize_redis_data(data: Any) -> Any:
"""递归地将Redis数据中的bytes转换为字符串"""
if isinstance(data, bytes):
try:
return data.decode('utf-8')
except UnicodeDecodeError:
return str(data)
elif isinstance(data, dict):
return {serialize_redis_data(k): serialize_redis_data(v) for k, v in data.items()}
elif isinstance(data, list):
return [serialize_redis_data(item) for item in data]
elif isinstance(data, tuple):
return tuple(serialize_redis_data(item) for item in data)
else:
return data
class AsyncRedisInfoCollector:
"""异步Redis信息收集器"""
def __init__(self, host: str = 'localhost', port: int = 6379,
password: Optional[str] = None, db: int = 0):
self.host = host
self.port = port
self.password = password
self.db = db
self.client = None
async def connect(self) -> bool:
"""连接Redis"""
try:
self.client = aioredis.Redis(
host=self.host,
port=self.port,
password=self.password,
db=self.db,
decode_responses=True,
socket_timeout=5,
socket_connect_timeout=5
)
await self.client.ping()
return True
except Exception as e:
logger.error(f"Failed to connect to Redis {self.host}:{self.port}: {e}")
return False
async def disconnect(self):
"""断开连接"""
if self.client:
try:
await self.client.aclose()
except Exception as e:
logger.error(f"Error disconnecting from Redis: {e}")
finally:
self.client = None
async def test_connection(self) -> Dict[str, Any]:
"""测试Redis连接"""
start_time = time.time()
try:
if await self.connect():
response_time = (time.time() - start_time) * 1000
info = await self.client.info('server')
redis_version = info.get('redis_version', 'unknown')
return serialize_redis_data({
'success': True,
'message': '连接成功',
'response_time': round(response_time, 2),
'redis_version': redis_version
})
else:
return {
'success': False,
'message': '连接失败',
'response_time': None,
'redis_version': None
}
except Exception as e:
return {
'success': False,
'message': f'连接错误: {str(e)}',
'response_time': None,
'redis_version': None
}
finally:
await self.disconnect()
async def get_server_info(self) -> Dict[str, Any]:
"""获取Redis服务器信息"""
if not self.client:
if not await self.connect():
return {}
try:
info = await self.client.info('server')
clients_info = await self.client.info('clients')
return {
'redis_version': info.get('redis_version', ''),
'redis_mode': info.get('redis_mode', 'standalone'),
'role': info.get('role', 'master'),
'os': info.get('os', ''),
'arch_bits': info.get('arch_bits', 64),
'uptime_in_seconds': info.get('uptime_in_seconds', 0),
'uptime_in_days': info.get('uptime_in_days', 0),
'tcp_port': info.get('tcp_port', self.port),
'connected_clients': clients_info.get('connected_clients', 0),
'blocked_clients': clients_info.get('blocked_clients', 0)
}
except Exception as e:
logger.error(f"Error getting Redis server info: {e}")
return {}
async def get_memory_info(self) -> Dict[str, Any]:
"""获取Redis内存信息"""
if not self.client:
if not await self.connect():
return {}
try:
info = await self.client.info('memory')
return {
'used_memory': info.get('used_memory', 0),
'used_memory_human': info.get('used_memory_human', '0B'),
'used_memory_rss': info.get('used_memory_rss', 0),
'used_memory_peak': info.get('used_memory_peak', 0),
'used_memory_peak_human': info.get('used_memory_peak_human', '0B'),
'total_system_memory': info.get('total_system_memory', 0),
'total_system_memory_human': info.get('total_system_memory_human', '0B'),
'used_memory_dataset': info.get('used_memory_dataset', 0),
'used_memory_dataset_perc': info.get('used_memory_dataset_perc', '0%'),
'allocator_allocated': info.get('allocator_allocated', 0),
'allocator_active': info.get('allocator_active', 0),
'maxmemory': info.get('maxmemory', 0),
'maxmemory_human': info.get('maxmemory_human', '0B'),
'maxmemory_policy': info.get('maxmemory_policy', 'noeviction'),
'mem_fragmentation_ratio': info.get('mem_fragmentation_ratio', 1.0)
}
except Exception as e:
logger.error(f"Error getting Redis memory info: {e}")
return {}
async def get_stats_info(self) -> Dict[str, Any]:
"""获取Redis统计信息"""
if not self.client:
if not await self.connect():
return {}
try:
info = await self.client.info('stats')
return {
'total_connections_received': info.get('total_connections_received', 0),
'total_commands_processed': info.get('total_commands_processed', 0),
'instantaneous_ops_per_sec': info.get('instantaneous_ops_per_sec', 0),
'total_net_input_bytes': info.get('total_net_input_bytes', 0),
'total_net_output_bytes': info.get('total_net_output_bytes', 0),
'instantaneous_input_kbps': info.get('instantaneous_input_kbps', 0.0),
'instantaneous_output_kbps': info.get('instantaneous_output_kbps', 0.0),
'rejected_connections': info.get('rejected_connections', 0),
'sync_full': info.get('sync_full', 0),
'sync_partial_ok': info.get('sync_partial_ok', 0),
'sync_partial_err': info.get('sync_partial_err', 0),
'expired_keys': info.get('expired_keys', 0),
'evicted_keys': info.get('evicted_keys', 0),
'keyspace_hits': info.get('keyspace_hits', 0),
'keyspace_misses': info.get('keyspace_misses', 0),
'pubsub_channels': info.get('pubsub_channels', 0),
'pubsub_patterns': info.get('pubsub_patterns', 0),
'latest_fork_usec': info.get('latest_fork_usec', 0),
'migrate_cached_sockets': info.get('migrate_cached_sockets', 0)
}
except Exception as e:
logger.error(f"Error getting Redis stats info: {e}")
return {}
async def get_keyspace_info(self) -> List[Dict[str, Any]]:
"""获取Redis键空间信息"""
if not self.client:
if not await self.connect():
return []
try:
info = await self.client.info('keyspace')
keyspaces = []
for key, value in info.items():
if key.startswith('db'):
db_id = int(key[2:])
if isinstance(value, dict):
stats = value
elif isinstance(value, str):
stats = {}
for stat in value.split(','):
if '=' in stat:
stat_key, stat_value = stat.split('=', 1)
try:
stats[stat_key] = int(stat_value)
except ValueError:
stats[stat_key] = stat_value
else:
continue
keyspaces.append({
'db_id': db_id,
'keys': stats.get('keys', 0),
'expires': stats.get('expires', 0),
'avg_ttl': stats.get('avg_ttl', 0)
})
return keyspaces
except Exception as e:
logger.error(f"Error getting Redis keyspace info: {e}")
return []
async def get_clients_info(self, limit: int = 100) -> List[Dict[str, Any]]:
"""获取Redis客户端信息"""
if not self.client:
if not await self.connect():
return []
try:
clients_raw = await self.client.client_list()
clients = []
for client in clients_raw[:limit]:
clients.append({
'id': str(client.get('id', '')),
'addr': client.get('addr', ''),
'fd': client.get('fd', 0),
'name': client.get('name', ''),
'age': client.get('age', 0),
'idle': client.get('idle', 0),
'flags': client.get('flags', ''),
'db': client.get('db', 0),
'sub': client.get('sub', 0),
'psub': client.get('psub', 0),
'multi': client.get('multi', 0),
'qbuf': client.get('qbuf', 0),
'qbuf_free': client.get('qbuf-free', 0),
'obl': client.get('obl', 0),
'oll': client.get('oll', 0),
'omem': client.get('omem', 0),
'events': client.get('events', ''),
'cmd': client.get('cmd', '')
})
return clients
except Exception as e:
logger.error(f"Error getting Redis clients info: {e}")
return []
async def get_slowlog(self, limit: int = 10) -> List[Dict[str, Any]]:
"""获取Redis慢日志"""
if not self.client:
if not await self.connect():
return []
try:
slowlog_raw = await self.client.slowlog_get(limit)
slowlog = []
for entry in slowlog_raw:
slowlog.append({
'id': entry['id'],
'timestamp': entry['start_time'],
'duration': entry['duration'],
'command': ' '.join(str(arg) for arg in entry['command']),
'client_ip': entry.get('client_address', 'unknown'),
'client_name': entry.get('client_name', '')
})
return slowlog
except Exception as e:
logger.error(f"Error getting Redis slowlog: {e}")
return []
def _get_default_info(self) -> Dict[str, Any]:
"""获取默认的info数据"""
return {
'redis_version': '',
'redis_mode': 'standalone',
'role': 'master',
'os': '',
'arch_bits': 64,
'uptime_in_seconds': 0,
'uptime_in_days': 0,
'tcp_port': self.port,
'connected_clients': 0,
'blocked_clients': 0
}
def _get_default_memory(self) -> Dict[str, Any]:
"""获取默认的memory数据"""
return {
'used_memory': 0,
'used_memory_human': '0B',
'used_memory_rss': 0,
'used_memory_peak': 0,
'used_memory_peak_human': '0B',
'total_system_memory': 0,
'total_system_memory_human': '0B',
'used_memory_dataset': 0,
'used_memory_dataset_perc': '0%',
'allocator_allocated': 0,
'allocator_active': 0,
'maxmemory': 0,
'maxmemory_human': '0B',
'maxmemory_policy': 'noeviction',
'mem_fragmentation_ratio': 1.0
}
def _get_default_stats(self) -> Dict[str, Any]:
"""获取默认的stats数据"""
return {
'total_connections_received': 0,
'total_commands_processed': 0,
'instantaneous_ops_per_sec': 0,
'total_net_input_bytes': 0,
'total_net_output_bytes': 0,
'instantaneous_input_kbps': 0.0,
'instantaneous_output_kbps': 0.0,
'rejected_connections': 0,
'sync_full': 0,
'sync_partial_ok': 0,
'sync_partial_err': 0,
'expired_keys': 0,
'evicted_keys': 0,
'keyspace_hits': 0,
'keyspace_misses': 0,
'pubsub_channels': 0,
'pubsub_patterns': 0,
'latest_fork_usec': 0,
'migrate_cached_sockets': 0
}
async def get_all_info(self, connection_id: str, connection_name: str) -> Dict[str, Any]:
"""获取所有Redis监控信息"""
timestamp = datetime.now().isoformat()
try:
if not await self.connect():
return {
'connection_id': connection_id,
'connection_name': connection_name,
'status': 'disconnected',
'info': self._get_default_info(),
'memory': self._get_default_memory(),
'stats': self._get_default_stats(),
'keyspace': [],
'clients': [],
'slow_log': [],
'timestamp': timestamp
}
data = {
'connection_id': connection_id,
'connection_name': connection_name,
'status': 'connected',
'info': await self.get_server_info(),
'memory': await self.get_memory_info(),
'stats': await self.get_stats_info(),
'keyspace': await self.get_keyspace_info(),
'clients': await self.get_clients_info(),
'slow_log': await self.get_slowlog(),
'timestamp': timestamp
}
return serialize_redis_data(data)
except Exception as e:
logger.error(f"Error getting Redis all info: {e}")
return {
'connection_id': connection_id,
'connection_name': connection_name,
'status': 'error',
'info': self._get_default_info(),
'memory': self._get_default_memory(),
'stats': self._get_default_stats(),
'keyspace': [],
'clients': [],
'slow_log': [],
'timestamp': timestamp
}
finally:
await self.disconnect()
async def get_realtime_stats(self, connection_id: str) -> Dict[str, Any]:
"""获取实时统计信息"""
timestamp = datetime.now().isoformat()
try:
if not await self.connect():
return {
'connection_id': connection_id,
'used_memory': 0,
'memory_usage_percent': 0.0,
'connected_clients': 0,
'ops_per_sec': 0,
'hit_rate': 0.0,
'keyspace_hits': 0,
'keyspace_misses': 0,
'timestamp': timestamp
}
memory_info = await self.get_memory_info()
stats_info = await self.get_stats_info()
server_info = await self.get_server_info()
used_memory = memory_info.get('used_memory', 0)
total_memory = memory_info.get('total_system_memory', 0)
memory_usage_percent = (used_memory / total_memory * 100) if total_memory > 0 else 0.0
hits = stats_info.get('keyspace_hits', 0)
misses = stats_info.get('keyspace_misses', 0)
hit_rate = (hits / (hits + misses) * 100) if (hits + misses) > 0 else 0.0
data = {
'connection_id': connection_id,
'used_memory': used_memory,
'memory_usage_percent': round(memory_usage_percent, 2),
'connected_clients': server_info.get('connected_clients', 0),
'ops_per_sec': stats_info.get('instantaneous_ops_per_sec', 0),
'hit_rate': round(hit_rate, 2),
'keyspace_hits': hits,
'keyspace_misses': misses,
'timestamp': timestamp
}
return serialize_redis_data(data)
except Exception as e:
logger.error(f"Error getting Redis realtime stats: {e}")
return {
'connection_id': connection_id,
'used_memory': 0,
'memory_usage_percent': 0.0,
'connected_clients': 0,
'ops_per_sec': 0,
'hit_rate': 0.0,
'keyspace_hits': 0,
'keyspace_misses': 0,
'timestamp': timestamp
}
finally:
await self.disconnect()
@@ -0,0 +1,148 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Redis监控Schema
"""
from typing import List, Optional
from pydantic import BaseModel, Field
class RedisInfoSchema(BaseModel):
"""Redis基础信息Schema"""
redis_version: str = Field(..., description="Redis版本")
redis_mode: str = Field(..., description="Redis模式")
role: str = Field(..., description="角色")
os: str = Field(..., description="操作系统")
arch_bits: int = Field(..., description="架构位数")
uptime_in_seconds: int = Field(..., description="运行时间(秒)")
uptime_in_days: int = Field(..., description="运行时间(天)")
tcp_port: int = Field(..., description="TCP端口")
connected_clients: int = Field(..., description="连接的客户端数量")
blocked_clients: int = Field(..., description="阻塞的客户端数量")
class RedisMemorySchema(BaseModel):
"""Redis内存信息Schema"""
used_memory: int = Field(..., description="已使用内存(字节)")
used_memory_human: str = Field(..., description="已使用内存(人类可读)")
used_memory_rss: int = Field(..., description="RSS内存使用(字节)")
used_memory_peak: int = Field(..., description="内存使用峰值(字节)")
used_memory_peak_human: str = Field(..., description="内存使用峰值(人类可读)")
total_system_memory: int = Field(..., description="系统总内存(字节)")
total_system_memory_human: str = Field(..., description="系统总内存(人类可读)")
used_memory_dataset: int = Field(..., description="数据集内存使用(字节)")
used_memory_dataset_perc: str = Field(..., description="数据集内存使用百分比")
allocator_allocated: int = Field(..., description="分配器已分配内存")
allocator_active: int = Field(..., description="分配器活跃内存")
maxmemory: int = Field(..., description="最大内存限制")
maxmemory_human: str = Field(..., description="最大内存限制(人类可读)")
maxmemory_policy: str = Field(..., description="内存策略")
mem_fragmentation_ratio: float = Field(..., description="内存碎片率")
class RedisStatsSchema(BaseModel):
"""Redis统计信息Schema"""
total_connections_received: int = Field(..., description="总连接数")
total_commands_processed: int = Field(..., description="总命令处理数")
instantaneous_ops_per_sec: int = Field(..., description="每秒操作数")
total_net_input_bytes: int = Field(..., description="总输入字节数")
total_net_output_bytes: int = Field(..., description="总输出字节数")
instantaneous_input_kbps: float = Field(..., description="每秒输入KB")
instantaneous_output_kbps: float = Field(..., description="每秒输出KB")
rejected_connections: int = Field(..., description="拒绝的连接数")
sync_full: int = Field(..., description="完整同步次数")
sync_partial_ok: int = Field(..., description="部分同步成功次数")
sync_partial_err: int = Field(..., description="部分同步失败次数")
expired_keys: int = Field(..., description="过期键数量")
evicted_keys: int = Field(..., description="被驱逐键数量")
keyspace_hits: int = Field(..., description="键空间命中数")
keyspace_misses: int = Field(..., description="键空间未命中数")
pubsub_channels: int = Field(..., description="发布订阅频道数")
pubsub_patterns: int = Field(..., description="发布订阅模式数")
latest_fork_usec: int = Field(..., description="最近fork时间(微秒)")
migrate_cached_sockets: int = Field(..., description="迁移缓存套接字数")
class RedisKeyspaceSchema(BaseModel):
"""Redis键空间信息Schema"""
db_id: int = Field(..., description="数据库ID")
keys: int = Field(..., description="键数量")
expires: int = Field(..., description="过期键数量")
avg_ttl: int = Field(..., description="平均TTL")
class RedisSlowLogSchema(BaseModel):
"""Redis慢日志Schema"""
id: int = Field(..., description="日志ID")
timestamp: int = Field(..., description="时间戳")
duration: int = Field(..., description="执行时间(微秒)")
command: str = Field(..., description="命令")
client_ip: str = Field(..., description="客户端IP")
client_name: str = Field(..., description="客户端名称")
class RedisClientSchema(BaseModel):
"""Redis客户端信息Schema"""
id: str = Field(..., description="客户端ID")
addr: str = Field(..., description="客户端地址")
fd: int = Field(..., description="文件描述符")
name: str = Field(..., description="客户端名称")
age: int = Field(..., description="连接时长(秒)")
idle: int = Field(..., description="空闲时间(秒)")
flags: str = Field(..., description="客户端标志")
db: int = Field(..., description="数据库")
sub: int = Field(..., description="订阅频道数")
psub: int = Field(..., description="订阅模式数")
multi: int = Field(..., description="事务命令数")
qbuf: int = Field(..., description="查询缓冲区长度")
qbuf_free: int = Field(..., description="查询缓冲区剩余空间")
obl: int = Field(..., description="输出缓冲区长度")
oll: int = Field(..., description="输出列表长度")
omem: int = Field(..., description="输出内存使用")
events: str = Field(..., description="文件描述符事件")
cmd: str = Field(..., description="最后执行的命令")
class RedisMonitorOverviewSchema(BaseModel):
"""Redis监控概览Schema"""
connection_id: str = Field(..., description="连接ID")
connection_name: str = Field(..., description="连接名称")
status: str = Field(..., description="连接状态")
info: RedisInfoSchema = Field(..., description="基础信息")
memory: RedisMemorySchema = Field(..., description="内存信息")
stats: RedisStatsSchema = Field(..., description="统计信息")
keyspace: List[RedisKeyspaceSchema] = Field(default=[], description="键空间信息")
clients: List[RedisClientSchema] = Field(default=[], description="客户端信息")
slow_log: List[RedisSlowLogSchema] = Field(default=[], description="慢日志")
timestamp: str = Field(..., description="时间戳")
class RedisRealtimeStatsSchema(BaseModel):
"""Redis实时统计Schema"""
connection_id: str = Field(..., description="连接ID")
used_memory: int = Field(..., description="已使用内存")
memory_usage_percent: float = Field(..., description="内存使用率")
connected_clients: int = Field(..., description="连接客户端数")
ops_per_sec: int = Field(..., description="每秒操作数")
hit_rate: float = Field(..., description="命中率")
keyspace_hits: int = Field(..., description="键空间命中数")
keyspace_misses: int = Field(..., description="键空间未命中数")
timestamp: str = Field(..., description="时间戳")
class RedisConnectionTestSchema(BaseModel):
"""Redis连接测试Schema"""
success: bool = Field(..., description="连接是否成功")
message: str = Field(..., description="测试结果消息")
response_time: Optional[float] = Field(None, description="响应时间(毫秒)")
redis_version: Optional[str] = Field(None, description="Redis版本")
class RedisConfigSchema(BaseModel):
"""Redis配置信息Schema"""
host: str = Field(..., description="主机地址")
port: int = Field(..., description="端口")
database: int = Field(..., description="数据库编号")
has_password: bool = Field(..., description="是否有密码")
redis_url: str = Field(default='', description="Redis URL")