Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""数据库监控模块"""
|
||||
|
||||
from core.database_monitor.api import get_database_configs
|
||||
from core.database_monitor.config_resolver import (
|
||||
create_monitor_collector,
|
||||
list_monitor_configs,
|
||||
resolve_monitor_target,
|
||||
)
|
||||
from core.database_monitor.database_collector import AsyncDatabaseCollector
|
||||
|
||||
DatabaseCollector = AsyncDatabaseCollector
|
||||
|
||||
__all__ = [
|
||||
"AsyncDatabaseCollector",
|
||||
"DatabaseCollector",
|
||||
"create_monitor_collector",
|
||||
"get_database_configs",
|
||||
"list_monitor_configs",
|
||||
"resolve_monitor_target",
|
||||
]
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
数据库监控 API
|
||||
|
||||
监控维度与「数据库连接管理」一致:每个启用连接(code)为一个监控目标,
|
||||
凭证通过 ConnectionResolver 解析,支持 PostgreSQL / MySQL / SQL Server / Oracle。
|
||||
"""
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from core.database_monitor.config_resolver import (
|
||||
create_monitor_collector,
|
||||
list_monitor_configs,
|
||||
resolve_monitor_target,
|
||||
)
|
||||
from core.database_monitor.schema import (
|
||||
DatabaseConfigSchema,
|
||||
DatabaseConnectionTestSchema,
|
||||
DatabaseOverviewSchema,
|
||||
DatabaseRealtimeStatsSchema,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/database_monitor", tags=["数据库监控"])
|
||||
|
||||
|
||||
async def get_database_configs(db: AsyncSession) -> List[dict]:
|
||||
"""获取全部可监控连接配置(供 WebSocket 等模块复用)。"""
|
||||
return await list_monitor_configs(db)
|
||||
|
||||
|
||||
@router.get("/configs", response_model=List[DatabaseConfigSchema], summary="获取数据库监控目标列表")
|
||||
async def get_database_monitor_configs(db: AsyncSession = Depends(get_db)):
|
||||
"""获取已注册的数据库连接列表,作为监控目标。"""
|
||||
configs = await list_monitor_configs(db)
|
||||
return [
|
||||
DatabaseConfigSchema(
|
||||
name=config["name"],
|
||||
db_name=config["db_name"],
|
||||
db_type=config["db_type"],
|
||||
host=config["host"],
|
||||
port=config["port"],
|
||||
database=config["database"],
|
||||
user=config["user"],
|
||||
has_password=config["has_password"],
|
||||
is_system=config.get("is_system", False),
|
||||
)
|
||||
for config in configs
|
||||
]
|
||||
|
||||
|
||||
@router.get("/{connection_code}/overview", response_model=DatabaseOverviewSchema, summary="获取数据库概览信息")
|
||||
async def get_database_overview(
|
||||
connection_code: str,
|
||||
database: Optional[str] = Query(None, description="覆盖连接默认库/服务名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取指定连接的监控概览。"""
|
||||
try:
|
||||
target = await resolve_monitor_target(db, connection_code, database)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
collector = create_monitor_collector(target)
|
||||
data = await collector.get_all_info(connection_code, target["name"])
|
||||
return DatabaseOverviewSchema(**data)
|
||||
|
||||
|
||||
@router.get("/{connection_code}/realtime", response_model=DatabaseRealtimeStatsSchema, summary="获取数据库实时统计")
|
||||
async def get_database_realtime_stats(
|
||||
connection_code: str,
|
||||
database: Optional[str] = Query(None, description="覆盖连接默认库/服务名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取指定连接的实时统计。"""
|
||||
try:
|
||||
target = await resolve_monitor_target(db, connection_code, database)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
collector = create_monitor_collector(target)
|
||||
data = await collector.get_realtime_stats(connection_code)
|
||||
return DatabaseRealtimeStatsSchema(**data)
|
||||
|
||||
|
||||
@router.post("/{connection_code}/test", response_model=DatabaseConnectionTestSchema, summary="测试数据库连接")
|
||||
async def test_database_connection(
|
||||
connection_code: str,
|
||||
database: Optional[str] = Query(None, description="覆盖连接默认库/服务名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""测试指定连接是否可用。"""
|
||||
try:
|
||||
target = await resolve_monitor_target(db, connection_code, database)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
collector = create_monitor_collector(target)
|
||||
result = await collector.test_connection()
|
||||
return DatabaseConnectionTestSchema(**result)
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""数据库监控配置解析:与数据库连接管理统一,按连接 code 监控目标库。"""
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.database_connection.resolver import ConnectionResolver
|
||||
from core.database_connection.service import DatabaseConnectionService
|
||||
from core.database_monitor.database_collector import AsyncDatabaseCollector
|
||||
|
||||
|
||||
def normalize_monitor_db_type(db_type: str) -> str:
|
||||
upper = (db_type or "").upper()
|
||||
if upper in ("MSSQL", "SQL SERVER"):
|
||||
return "SQLSERVER"
|
||||
return upper
|
||||
|
||||
|
||||
def default_database_for_type(db_type: str) -> str:
|
||||
db = (db_type or "").lower()
|
||||
if db == "postgresql":
|
||||
return "postgres"
|
||||
if db == "sqlserver":
|
||||
return "master"
|
||||
if db == "oracle":
|
||||
return "ORCL"
|
||||
return ""
|
||||
|
||||
|
||||
async def list_monitor_configs(db: AsyncSession) -> List[Dict[str, Any]]:
|
||||
"""列出可监控的数据库连接(不含密码)。"""
|
||||
configs = await DatabaseConnectionService.get_manager_configs(db)
|
||||
results: List[Dict[str, Any]] = []
|
||||
for item in configs:
|
||||
db_type = normalize_monitor_db_type(item["db_type"])
|
||||
database = item.get("database") or default_database_for_type(item["db_type"])
|
||||
results.append(
|
||||
{
|
||||
"name": item.get("display_name") or item.get("name") or item["db_name"],
|
||||
"db_name": item["db_name"],
|
||||
"db_type": db_type,
|
||||
"host": item["host"],
|
||||
"port": item["port"],
|
||||
"database": database,
|
||||
"user": item.get("user") or "",
|
||||
"has_password": bool(item.get("has_password")),
|
||||
"is_system": bool(item.get("is_system")),
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
async def resolve_monitor_target(
|
||||
db: AsyncSession,
|
||||
connection_code: str,
|
||||
database: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""解析监控目标完整配置(含密码与 extra_options)。"""
|
||||
info = await ConnectionResolver.resolve(connection_code, db)
|
||||
target_database = (
|
||||
database
|
||||
or info.database
|
||||
or default_database_for_type(info.db_type)
|
||||
)
|
||||
return {
|
||||
"name": info.display_name or connection_code,
|
||||
"db_name": connection_code,
|
||||
"db_type": normalize_monitor_db_type(info.db_type),
|
||||
"host": info.host,
|
||||
"port": info.port,
|
||||
"user": info.user,
|
||||
"password": info.password,
|
||||
"database": target_database,
|
||||
"extra_options": info.extra_options or {},
|
||||
"is_system": info.is_system,
|
||||
"has_password": bool(info.password),
|
||||
}
|
||||
|
||||
|
||||
def create_monitor_collector(config: Dict[str, Any]) -> AsyncDatabaseCollector:
|
||||
"""根据解析后的配置创建采集器。"""
|
||||
return AsyncDatabaseCollector(
|
||||
db_type=config["db_type"],
|
||||
host=config["host"],
|
||||
port=config["port"],
|
||||
user=config["user"],
|
||||
password=config["password"],
|
||||
database=config["database"],
|
||||
extra_options=config.get("extra_options"),
|
||||
)
|
||||
@@ -0,0 +1,986 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
数据库信息收集器(异步版本)
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
import asyncpg
|
||||
|
||||
from core.database_manager.service import aiomysql_connect, log_database_connect_failure
|
||||
from core.database_manager.handlers.pools import build_mssql_dsn, build_oracle_dsn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# MySQL异步驱动
|
||||
try:
|
||||
import aiomysql
|
||||
AIOMYSQL_AVAILABLE = True
|
||||
except ImportError:
|
||||
AIOMYSQL_AVAILABLE = False
|
||||
|
||||
try:
|
||||
import aioodbc
|
||||
AIOODBC_AVAILABLE = True
|
||||
except ImportError:
|
||||
AIOODBC_AVAILABLE = False
|
||||
|
||||
try:
|
||||
import oracledb
|
||||
ORACLEDB_AVAILABLE = True
|
||||
except ImportError:
|
||||
ORACLEDB_AVAILABLE = False
|
||||
|
||||
|
||||
def _normalize_db_type(db_type: str) -> str:
|
||||
upper = (db_type or "").upper()
|
||||
if upper in ("MSSQL", "SQL SERVER"):
|
||||
return "SQLSERVER"
|
||||
return upper
|
||||
|
||||
|
||||
def serialize_data(data: Any) -> Any:
|
||||
"""递归地序列化数据,处理datetime、decimal等类型"""
|
||||
import decimal
|
||||
|
||||
if isinstance(data, (datetime,)):
|
||||
return data.isoformat()
|
||||
elif isinstance(data, decimal.Decimal):
|
||||
return float(data)
|
||||
elif isinstance(data, (bytes,)):
|
||||
try:
|
||||
return data.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
return str(data)
|
||||
elif isinstance(data, dict):
|
||||
return {serialize_data(k): serialize_data(v) for k, v in data.items()}
|
||||
elif isinstance(data, list):
|
||||
return [serialize_data(item) for item in data]
|
||||
elif isinstance(data, tuple):
|
||||
return tuple(serialize_data(item) for item in data)
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
class AsyncDatabaseCollector:
|
||||
"""异步数据库信息收集器"""
|
||||
|
||||
def __init__(self, db_type: str, host: str, port: int,
|
||||
user: str, password: str, database: str, **kwargs):
|
||||
self.db_type = _normalize_db_type(db_type)
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.database = database
|
||||
self.kwargs = kwargs
|
||||
self.connection = None
|
||||
self._last_connect_error: Optional[str] = None
|
||||
|
||||
async def connect(self) -> bool:
|
||||
"""连接数据库"""
|
||||
try:
|
||||
if self.db_type == 'POSTGRESQL':
|
||||
result = await self._connect_postgresql()
|
||||
elif self.db_type == 'MYSQL':
|
||||
result = await self._connect_mysql()
|
||||
elif self.db_type == 'SQLSERVER':
|
||||
result = await self._connect_sqlserver()
|
||||
elif self.db_type == 'ORACLE':
|
||||
result = await self._connect_oracle()
|
||||
else:
|
||||
self._last_connect_error = f"Unsupported database type: {self.db_type}"
|
||||
logger.error(self._last_connect_error)
|
||||
return False
|
||||
if result:
|
||||
self._last_connect_error = None
|
||||
return result
|
||||
except Exception as e:
|
||||
self._last_connect_error = str(e).strip() or e.__class__.__name__
|
||||
log_database_connect_failure(
|
||||
db_type=self.db_type.lower(),
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
user=self.user,
|
||||
database=self.database,
|
||||
detail=self._last_connect_error,
|
||||
)
|
||||
return False
|
||||
|
||||
async def _connect_postgresql(self) -> bool:
|
||||
"""连接PostgreSQL"""
|
||||
self.connection = await asyncpg.connect(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
user=self.user,
|
||||
password=self.password,
|
||||
database=self.database,
|
||||
timeout=5
|
||||
)
|
||||
return True
|
||||
|
||||
async def _connect_mysql(self) -> bool:
|
||||
"""连接MySQL"""
|
||||
if not AIOMYSQL_AVAILABLE:
|
||||
self._last_connect_error = "aiomysql 未安装,无法连接 MySQL"
|
||||
logger.error(self._last_connect_error)
|
||||
return False
|
||||
|
||||
self.connection = await aiomysql_connect(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
user=self.user,
|
||||
password=self.password,
|
||||
db=self.database,
|
||||
charset='utf8mb4',
|
||||
connect_timeout=5,
|
||||
)
|
||||
return True
|
||||
|
||||
async def _connect_sqlserver(self) -> bool:
|
||||
"""连接 SQL Server"""
|
||||
if not AIOODBC_AVAILABLE:
|
||||
self._last_connect_error = "aioodbc 未安装,无法连接 SQL Server"
|
||||
logger.error(self._last_connect_error)
|
||||
return False
|
||||
|
||||
dsn = build_mssql_dsn(
|
||||
self.host,
|
||||
self.port,
|
||||
self.user,
|
||||
self.password,
|
||||
self.database or "master",
|
||||
self.kwargs.get("extra_options"),
|
||||
)
|
||||
self.connection = await aioodbc.connect(dsn=dsn, autocommit=True, timeout=5)
|
||||
return True
|
||||
|
||||
async def _connect_oracle(self) -> bool:
|
||||
"""连接 Oracle"""
|
||||
if not ORACLEDB_AVAILABLE:
|
||||
self._last_connect_error = "oracledb 未安装,无法连接 Oracle"
|
||||
logger.error(self._last_connect_error)
|
||||
return False
|
||||
|
||||
service = self.database or self.kwargs.get("service_name") or "ORCL"
|
||||
dsn = build_oracle_dsn(self.host, self.port, service)
|
||||
self.connection = await oracledb.connect_async(
|
||||
user=self.user,
|
||||
password=self.password,
|
||||
dsn=dsn,
|
||||
)
|
||||
return True
|
||||
|
||||
async def _execute_odbc_query(
|
||||
self, query: str, params: tuple = ()
|
||||
) -> List[Dict[str, Any]]:
|
||||
async with self.connection.cursor() as cursor:
|
||||
await cursor.execute(query, params)
|
||||
if not cursor.description:
|
||||
return []
|
||||
columns = [col[0] for col in cursor.description]
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(zip(columns, row)) for row in rows]
|
||||
|
||||
async def _fetch_odbc_value(self, query: str, params: tuple = ()) -> Any:
|
||||
rows = await self._execute_odbc_query(query, params)
|
||||
if not rows:
|
||||
return None
|
||||
return next(iter(rows[0].values()))
|
||||
|
||||
async def _execute_oracle_query(
|
||||
self, query: str, params: Optional[Dict[str, Any]] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
cursor = self.connection.cursor()
|
||||
try:
|
||||
await cursor.execute(query, params or {})
|
||||
if not cursor.description:
|
||||
return []
|
||||
columns = [col[0].lower() for col in cursor.description]
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(zip(columns, row)) for row in rows]
|
||||
finally:
|
||||
await cursor.close()
|
||||
|
||||
async def _fetch_oracle_value(
|
||||
self, query: str, params: Optional[Dict[str, Any]] = None
|
||||
) -> Any:
|
||||
rows = await self._execute_oracle_query(query, params)
|
||||
if not rows:
|
||||
return None
|
||||
return next(iter(rows[0].values()))
|
||||
|
||||
async def disconnect(self):
|
||||
"""断开连接"""
|
||||
if self.connection:
|
||||
try:
|
||||
await self.connection.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Error disconnecting from database: {e}")
|
||||
finally:
|
||||
self.connection = None
|
||||
|
||||
async def test_connection(self) -> Dict[str, Any]:
|
||||
"""测试数据库连接"""
|
||||
start_time = time.time()
|
||||
try:
|
||||
if await self.connect():
|
||||
response_time = (time.time() - start_time) * 1000
|
||||
version = await self._get_version()
|
||||
return {
|
||||
'success': True,
|
||||
'message': '连接成功',
|
||||
'response_time': round(response_time, 2),
|
||||
'version': version,
|
||||
'db_type': self.db_type
|
||||
}
|
||||
else:
|
||||
target = f"{self.host}:{self.port}, db={self.database}"
|
||||
detail = self._last_connect_error or "未知错误"
|
||||
message = f'连接失败 ({target}): {detail}'
|
||||
log_database_connect_failure(
|
||||
db_type=self.db_type.lower(),
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
user=self.user,
|
||||
database=self.database,
|
||||
detail=detail,
|
||||
action="monitor_test",
|
||||
)
|
||||
return {
|
||||
'success': False,
|
||||
'message': message,
|
||||
'response_time': None,
|
||||
'version': None,
|
||||
'db_type': self.db_type
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'连接错误: {str(e)}',
|
||||
'response_time': None,
|
||||
'version': None,
|
||||
'db_type': self.db_type
|
||||
}
|
||||
finally:
|
||||
await self.disconnect()
|
||||
|
||||
async def _get_version(self) -> str:
|
||||
"""获取数据库版本"""
|
||||
try:
|
||||
if self.db_type == 'POSTGRESQL':
|
||||
result = await self.connection.fetchval("SELECT version()")
|
||||
return result
|
||||
elif self.db_type == 'MYSQL':
|
||||
async with self.connection.cursor() as cursor:
|
||||
await cursor.execute("SELECT VERSION()")
|
||||
result = await cursor.fetchone()
|
||||
return result[0]
|
||||
elif self.db_type == 'SQLSERVER':
|
||||
return await self._fetch_odbc_value("SELECT @@VERSION")
|
||||
elif self.db_type == 'ORACLE':
|
||||
return await self._fetch_oracle_value(
|
||||
"SELECT banner FROM v$version WHERE ROWNUM = 1"
|
||||
)
|
||||
return 'Unknown'
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting database version: {e}")
|
||||
return 'Unknown'
|
||||
|
||||
async def get_basic_info(self) -> Dict[str, Any]:
|
||||
"""获取数据库基本信息"""
|
||||
if not self.connection:
|
||||
if not await self.connect():
|
||||
return {}
|
||||
|
||||
try:
|
||||
info = {
|
||||
'db_type': self.db_type,
|
||||
'host': self.host,
|
||||
'port': self.port,
|
||||
'database': self.database,
|
||||
'version': await self._get_version(),
|
||||
'uptime': await self._get_uptime(),
|
||||
'timezone': await self._get_timezone(),
|
||||
'charset': await self._get_charset(),
|
||||
}
|
||||
return serialize_data(info)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting basic info: {e}")
|
||||
return {}
|
||||
|
||||
async def _get_uptime(self) -> str:
|
||||
"""获取数据库运行时间"""
|
||||
try:
|
||||
if self.db_type == 'POSTGRESQL':
|
||||
start_time = await self.connection.fetchval("SELECT pg_postmaster_start_time()")
|
||||
current_time = datetime.now(timezone.utc)
|
||||
if start_time.tzinfo is None:
|
||||
start_time = start_time.replace(tzinfo=timezone.utc)
|
||||
uptime = current_time - start_time
|
||||
return str(uptime)
|
||||
elif self.db_type == 'MYSQL':
|
||||
async with self.connection.cursor() as cursor:
|
||||
await cursor.execute("SHOW GLOBAL STATUS LIKE 'Uptime'")
|
||||
result = await cursor.fetchone()
|
||||
if result:
|
||||
uptime_seconds = int(result[1])
|
||||
uptime = timedelta(seconds=uptime_seconds)
|
||||
return str(uptime)
|
||||
elif self.db_type == 'SQLSERVER':
|
||||
start_time = await self._fetch_odbc_value(
|
||||
"SELECT sqlserver_start_time FROM sys.dm_os_sys_info"
|
||||
)
|
||||
if start_time:
|
||||
current_time = datetime.now(timezone.utc)
|
||||
if getattr(start_time, "tzinfo", None) is None:
|
||||
start_time = start_time.replace(tzinfo=timezone.utc)
|
||||
return str(current_time - start_time)
|
||||
elif self.db_type == 'ORACLE':
|
||||
start_time = await self._fetch_oracle_value(
|
||||
"SELECT startup_time FROM v$instance"
|
||||
)
|
||||
if start_time:
|
||||
current_time = datetime.now(timezone.utc)
|
||||
if getattr(start_time, "tzinfo", None) is None:
|
||||
start_time = start_time.replace(tzinfo=timezone.utc)
|
||||
return str(current_time - start_time)
|
||||
return 'Unknown'
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting uptime: {e}")
|
||||
return 'Unknown'
|
||||
|
||||
async def _get_timezone(self) -> str:
|
||||
"""获取数据库时区"""
|
||||
try:
|
||||
if self.db_type == 'POSTGRESQL':
|
||||
result = await self.connection.fetchval("SHOW timezone")
|
||||
return result
|
||||
elif self.db_type == 'MYSQL':
|
||||
async with self.connection.cursor() as cursor:
|
||||
await cursor.execute("SELECT @@global.time_zone")
|
||||
result = await cursor.fetchone()
|
||||
return result[0]
|
||||
elif self.db_type == 'SQLSERVER':
|
||||
return await self._fetch_odbc_value(
|
||||
"SELECT CAST(SYSDATETIMEOFFSET() AS NVARCHAR(50))"
|
||||
) or "Unknown"
|
||||
elif self.db_type == 'ORACLE':
|
||||
return await self._fetch_oracle_value(
|
||||
"SELECT DBTIMEZONE FROM dual"
|
||||
) or "Unknown"
|
||||
return 'Unknown'
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting timezone: {e}")
|
||||
return 'Unknown'
|
||||
|
||||
async def _get_charset(self) -> str:
|
||||
"""获取数据库字符集"""
|
||||
try:
|
||||
if self.db_type == 'POSTGRESQL':
|
||||
result = await self.connection.fetchval(
|
||||
"SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = $1",
|
||||
self.database
|
||||
)
|
||||
return result
|
||||
elif self.db_type == 'MYSQL':
|
||||
async with self.connection.cursor() as cursor:
|
||||
await cursor.execute("SELECT @@character_set_database")
|
||||
result = await cursor.fetchone()
|
||||
return result[0]
|
||||
elif self.db_type == 'SQLSERVER':
|
||||
return await self._fetch_odbc_value(
|
||||
"SELECT DATABASEPROPERTYEX(DB_NAME(), 'Collation')"
|
||||
) or "Unknown"
|
||||
elif self.db_type == 'ORACLE':
|
||||
return await self._fetch_oracle_value(
|
||||
"""
|
||||
SELECT value
|
||||
FROM nls_database_parameters
|
||||
WHERE parameter = 'NLS_CHARACTERSET'
|
||||
"""
|
||||
) or "Unknown"
|
||||
return 'Unknown'
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting charset: {e}")
|
||||
return 'Unknown'
|
||||
|
||||
async def get_connection_info(self) -> Dict[str, Any]:
|
||||
"""获取连接信息"""
|
||||
if not self.connection:
|
||||
if not await self.connect():
|
||||
return {}
|
||||
|
||||
try:
|
||||
if self.db_type == 'POSTGRESQL':
|
||||
return await self._get_postgresql_connections()
|
||||
elif self.db_type == 'MYSQL':
|
||||
return await self._get_mysql_connections()
|
||||
elif self.db_type == 'SQLSERVER':
|
||||
return await self._get_sqlserver_connections()
|
||||
elif self.db_type == 'ORACLE':
|
||||
return await self._get_oracle_connections()
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting connection info: {e}")
|
||||
return {}
|
||||
|
||||
async def _get_postgresql_connections(self) -> Dict[str, Any]:
|
||||
"""获取PostgreSQL连接信息"""
|
||||
total_connections = await self.connection.fetchval(
|
||||
"SELECT COUNT(*) FROM pg_stat_activity"
|
||||
)
|
||||
|
||||
max_connections = await self.connection.fetchval("SHOW max_connections")
|
||||
max_connections = int(max_connections)
|
||||
|
||||
active_connections = await self.connection.fetchval(
|
||||
"SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'active'"
|
||||
)
|
||||
|
||||
idle_connections = await self.connection.fetchval(
|
||||
"SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'idle'"
|
||||
)
|
||||
|
||||
return {
|
||||
'total_connections': total_connections,
|
||||
'max_connections': max_connections,
|
||||
'active_connections': active_connections,
|
||||
'idle_connections': idle_connections,
|
||||
'connection_usage_percent': round((total_connections / max_connections) * 100, 2) if max_connections > 0 else 0.0
|
||||
}
|
||||
|
||||
async def _get_mysql_connections(self) -> Dict[str, Any]:
|
||||
"""获取MySQL连接信息"""
|
||||
async with self.connection.cursor() as cursor:
|
||||
await cursor.execute("SHOW STATUS LIKE 'Threads_connected'")
|
||||
result = await cursor.fetchone()
|
||||
total_connections = int(result[1])
|
||||
|
||||
await cursor.execute("SHOW VARIABLES LIKE 'max_connections'")
|
||||
result = await cursor.fetchone()
|
||||
max_connections = int(result[1])
|
||||
|
||||
await cursor.execute("SHOW STATUS LIKE 'Threads_running'")
|
||||
result = await cursor.fetchone()
|
||||
active_connections = int(result[1])
|
||||
|
||||
idle_connections = total_connections - active_connections
|
||||
|
||||
return {
|
||||
'total_connections': total_connections,
|
||||
'max_connections': max_connections,
|
||||
'active_connections': active_connections,
|
||||
'idle_connections': idle_connections,
|
||||
'connection_usage_percent': round((total_connections / max_connections) * 100, 2) if max_connections > 0 else 0.0
|
||||
}
|
||||
|
||||
async def get_database_size(self) -> Dict[str, Any]:
|
||||
"""获取数据库大小信息"""
|
||||
if not self.connection:
|
||||
if not await self.connect():
|
||||
return {}
|
||||
|
||||
try:
|
||||
if self.db_type == 'POSTGRESQL':
|
||||
return await self._get_postgresql_size()
|
||||
elif self.db_type == 'MYSQL':
|
||||
return await self._get_mysql_size()
|
||||
elif self.db_type == 'SQLSERVER':
|
||||
return await self._get_sqlserver_size()
|
||||
elif self.db_type == 'ORACLE':
|
||||
return await self._get_oracle_size()
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting database size: {e}")
|
||||
return {}
|
||||
|
||||
async def _get_postgresql_size(self) -> Dict[str, Any]:
|
||||
"""获取PostgreSQL数据库大小"""
|
||||
size_bytes = await self.connection.fetchval(
|
||||
"SELECT pg_database_size($1)", self.database
|
||||
)
|
||||
|
||||
return {
|
||||
'database_size_bytes': size_bytes,
|
||||
'database_size_mb': round(size_bytes / 1024 / 1024, 2),
|
||||
'database_size_gb': round(size_bytes / 1024 / 1024 / 1024, 2)
|
||||
}
|
||||
|
||||
async def _get_mysql_size(self) -> Dict[str, Any]:
|
||||
"""获取MySQL数据库大小"""
|
||||
async with self.connection.cursor() as cursor:
|
||||
await cursor.execute("""
|
||||
SELECT SUM(data_length + index_length) AS size_bytes
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = %s
|
||||
""", (self.database,))
|
||||
result = await cursor.fetchone()
|
||||
size_bytes = result[0] if result[0] else 0
|
||||
|
||||
return {
|
||||
'database_size_bytes': size_bytes,
|
||||
'database_size_mb': round(size_bytes / 1024 / 1024, 2),
|
||||
'database_size_gb': round(size_bytes / 1024 / 1024 / 1024, 2)
|
||||
}
|
||||
|
||||
async def get_performance_stats(self) -> Dict[str, Any]:
|
||||
"""获取性能统计信息"""
|
||||
if not self.connection:
|
||||
if not await self.connect():
|
||||
return {'cache_hit_ratio': 0.0}
|
||||
|
||||
try:
|
||||
if self.db_type == 'POSTGRESQL':
|
||||
return await self._get_postgresql_performance()
|
||||
elif self.db_type == 'MYSQL':
|
||||
return await self._get_mysql_performance()
|
||||
elif self.db_type == 'SQLSERVER':
|
||||
return await self._get_sqlserver_performance()
|
||||
elif self.db_type == 'ORACLE':
|
||||
return await self._get_oracle_performance()
|
||||
return {'cache_hit_ratio': 0.0}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting performance stats: {e}")
|
||||
return {'cache_hit_ratio': 0.0}
|
||||
|
||||
async def _get_postgresql_performance(self) -> Dict[str, Any]:
|
||||
"""获取PostgreSQL性能统计"""
|
||||
stats = await self.connection.fetchrow("""
|
||||
SELECT SUM(numbackends) AS total_backends,
|
||||
SUM(xact_commit) AS transactions_commit,
|
||||
SUM(xact_rollback) AS transactions_rollback,
|
||||
SUM(blks_read) AS blocks_read,
|
||||
SUM(blks_hit) AS blocks_hit,
|
||||
SUM(tup_returned) AS tuples_returned,
|
||||
SUM(tup_fetched) AS tuples_fetched,
|
||||
SUM(tup_inserted) AS tuples_inserted,
|
||||
SUM(tup_updated) AS tuples_updated,
|
||||
SUM(tup_deleted) AS tuples_deleted,
|
||||
SUM(temp_files) AS temp_files,
|
||||
SUM(temp_bytes) AS temp_bytes,
|
||||
SUM(deadlocks) AS deadlocks
|
||||
FROM pg_stat_database
|
||||
WHERE datname = $1
|
||||
""", self.database)
|
||||
|
||||
blocks_read = stats['blocks_read'] or 0
|
||||
blocks_hit = stats['blocks_hit'] or 0
|
||||
total_reads = blocks_read + blocks_hit
|
||||
cache_hit_ratio = (blocks_hit / total_reads * 100) if total_reads > 0 else 0
|
||||
|
||||
return {
|
||||
'total_backends': stats['total_backends'] or 0,
|
||||
'transactions_commit': stats['transactions_commit'] or 0,
|
||||
'transactions_rollback': stats['transactions_rollback'] or 0,
|
||||
'blocks_read': blocks_read,
|
||||
'blocks_hit': blocks_hit,
|
||||
'cache_hit_ratio': round(cache_hit_ratio, 2),
|
||||
'tuples_returned': stats['tuples_returned'] or 0,
|
||||
'tuples_fetched': stats['tuples_fetched'] or 0,
|
||||
'tuples_inserted': stats['tuples_inserted'] or 0,
|
||||
'tuples_updated': stats['tuples_updated'] or 0,
|
||||
'tuples_deleted': stats['tuples_deleted'] or 0,
|
||||
'temp_files': stats['temp_files'] or 0,
|
||||
'temp_bytes': stats['temp_bytes'] or 0,
|
||||
'deadlocks': stats['deadlocks'] or 0,
|
||||
}
|
||||
|
||||
async def _get_mysql_performance(self) -> Dict[str, Any]:
|
||||
"""获取MySQL性能统计"""
|
||||
stats = {}
|
||||
status_queries = [
|
||||
('queries', 'Queries'),
|
||||
('connections', 'Connections'),
|
||||
('slow_queries', 'Slow_queries'),
|
||||
('bytes_received', 'Bytes_received'),
|
||||
('bytes_sent', 'Bytes_sent'),
|
||||
('innodb_buffer_pool_reads', 'Innodb_buffer_pool_reads'),
|
||||
('innodb_buffer_pool_read_requests', 'Innodb_buffer_pool_read_requests')
|
||||
]
|
||||
|
||||
async with self.connection.cursor() as cursor:
|
||||
for stat_name, mysql_var in status_queries:
|
||||
await cursor.execute(f"SHOW GLOBAL STATUS LIKE '{mysql_var}'")
|
||||
result = await cursor.fetchone()
|
||||
stats[stat_name] = int(result[1]) if result else 0
|
||||
|
||||
read_requests = stats['innodb_buffer_pool_read_requests']
|
||||
reads = stats['innodb_buffer_pool_reads']
|
||||
cache_hit_ratio = ((read_requests - reads) / read_requests * 100) if read_requests > 0 else 0
|
||||
|
||||
return {
|
||||
'total_queries': stats['queries'],
|
||||
'total_connections': stats['connections'],
|
||||
'slow_queries': stats['slow_queries'],
|
||||
'bytes_received': stats['bytes_received'],
|
||||
'bytes_sent': stats['bytes_sent'],
|
||||
'cache_hit_ratio': round(cache_hit_ratio, 2)
|
||||
}
|
||||
|
||||
async def get_table_stats(self) -> List[Dict[str, Any]]:
|
||||
"""获取表统计信息"""
|
||||
if not self.connection:
|
||||
if not await self.connect():
|
||||
return []
|
||||
|
||||
try:
|
||||
if self.db_type == 'POSTGRESQL':
|
||||
return await self._get_postgresql_tables()
|
||||
elif self.db_type == 'MYSQL':
|
||||
return await self._get_mysql_tables()
|
||||
elif self.db_type == 'SQLSERVER':
|
||||
return await self._get_sqlserver_tables()
|
||||
elif self.db_type == 'ORACLE':
|
||||
return await self._get_oracle_tables()
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting table stats: {e}")
|
||||
return []
|
||||
|
||||
async def _get_postgresql_tables(self) -> List[Dict[str, Any]]:
|
||||
"""获取PostgreSQL表统计"""
|
||||
rows = await self.connection.fetch("""
|
||||
SELECT st.schemaname,
|
||||
st.relname AS tablename,
|
||||
st.n_tup_ins AS inserts,
|
||||
st.n_tup_upd AS updates,
|
||||
st.n_tup_del AS deletes,
|
||||
st.n_live_tup AS live_tuples,
|
||||
st.n_dead_tup AS dead_tuples,
|
||||
st.seq_scan AS sequential_scans,
|
||||
st.idx_scan AS index_scans,
|
||||
COALESCE(PG_SIZE_PRETTY(PG_RELATION_SIZE(c.oid)), '0 bytes') AS size,
|
||||
COALESCE(PG_RELATION_SIZE(c.oid), 0) AS size_bytes,
|
||||
COALESCE(PG_SIZE_PRETTY(PG_TOTAL_RELATION_SIZE(c.oid)), '0 bytes') AS total_size,
|
||||
COALESCE(PG_TOTAL_RELATION_SIZE(c.oid), 0) AS total_size_bytes
|
||||
FROM pg_stat_user_tables st
|
||||
JOIN pg_class c ON c.relname = st.relname
|
||||
AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = st.schemaname)
|
||||
WHERE c.relkind = 'r'
|
||||
ORDER BY COALESCE(PG_TOTAL_RELATION_SIZE(c.oid), 0) DESC
|
||||
LIMIT 20
|
||||
""")
|
||||
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def _get_mysql_tables(self) -> List[Dict[str, Any]]:
|
||||
"""获取MySQL表统计"""
|
||||
async with self.connection.cursor() as cursor:
|
||||
await cursor.execute("""
|
||||
SELECT table_name,
|
||||
table_rows,
|
||||
data_length,
|
||||
index_length,
|
||||
(data_length + index_length) AS total_size,
|
||||
auto_increment
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = %s
|
||||
ORDER BY (data_length + index_length) DESC
|
||||
LIMIT 20
|
||||
""", (self.database,))
|
||||
|
||||
tables = []
|
||||
rows = await cursor.fetchall()
|
||||
for row in rows:
|
||||
tables.append({
|
||||
'table_name': row[0],
|
||||
'table_rows': row[1] or 0,
|
||||
'data_length': row[2] or 0,
|
||||
'index_length': row[3] or 0,
|
||||
'total_size': row[4] or 0,
|
||||
'auto_increment': row[5] or 0
|
||||
})
|
||||
return tables
|
||||
|
||||
async def _get_sqlserver_connections(self) -> Dict[str, Any]:
|
||||
"""获取 SQL Server 连接信息"""
|
||||
total_connections = int(
|
||||
await self._fetch_odbc_value(
|
||||
"SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE is_user_process = 1"
|
||||
)
|
||||
or 0
|
||||
)
|
||||
max_connections = int(await self._fetch_odbc_value("SELECT @@MAX_CONNECTIONS") or 0)
|
||||
active_connections = int(
|
||||
await self._fetch_odbc_value(
|
||||
"""
|
||||
SELECT COUNT(*)
|
||||
FROM sys.dm_exec_sessions
|
||||
WHERE is_user_process = 1
|
||||
AND status = 'running'
|
||||
"""
|
||||
)
|
||||
or 0
|
||||
)
|
||||
idle_connections = max(total_connections - active_connections, 0)
|
||||
return {
|
||||
"total_connections": total_connections,
|
||||
"max_connections": max_connections,
|
||||
"active_connections": active_connections,
|
||||
"idle_connections": idle_connections,
|
||||
"connection_usage_percent": round(
|
||||
(total_connections / max_connections) * 100, 2
|
||||
)
|
||||
if max_connections > 0
|
||||
else 0.0,
|
||||
}
|
||||
|
||||
async def _get_oracle_connections(self) -> Dict[str, Any]:
|
||||
"""获取 Oracle 连接信息"""
|
||||
total_connections = int(
|
||||
await self._fetch_oracle_value(
|
||||
"SELECT COUNT(*) FROM v$session WHERE type != 'BACKGROUND'"
|
||||
)
|
||||
or 0
|
||||
)
|
||||
max_connections = int(
|
||||
await self._fetch_oracle_value(
|
||||
"SELECT value FROM v$parameter WHERE name = 'processes'"
|
||||
)
|
||||
or 0
|
||||
)
|
||||
active_connections = int(
|
||||
await self._fetch_oracle_value(
|
||||
"""
|
||||
SELECT COUNT(*)
|
||||
FROM v$session
|
||||
WHERE type != 'BACKGROUND'
|
||||
AND status = 'ACTIVE'
|
||||
"""
|
||||
)
|
||||
or 0
|
||||
)
|
||||
idle_connections = max(total_connections - active_connections, 0)
|
||||
return {
|
||||
"total_connections": total_connections,
|
||||
"max_connections": max_connections,
|
||||
"active_connections": active_connections,
|
||||
"idle_connections": idle_connections,
|
||||
"connection_usage_percent": round(
|
||||
(total_connections / max_connections) * 100, 2
|
||||
)
|
||||
if max_connections > 0
|
||||
else 0.0,
|
||||
}
|
||||
|
||||
async def _get_sqlserver_size(self) -> Dict[str, Any]:
|
||||
"""获取 SQL Server 数据库大小"""
|
||||
size_bytes = int(
|
||||
await self._fetch_odbc_value(
|
||||
"SELECT SUM(size) * 8192 FROM sys.database_files"
|
||||
)
|
||||
or 0
|
||||
)
|
||||
return {
|
||||
"database_size_bytes": size_bytes,
|
||||
"database_size_mb": round(size_bytes / 1024 / 1024, 2),
|
||||
"database_size_gb": round(size_bytes / 1024 / 1024 / 1024, 2),
|
||||
}
|
||||
|
||||
async def _get_oracle_size(self) -> Dict[str, Any]:
|
||||
"""获取 Oracle 用户 Schema 占用空间"""
|
||||
size_bytes = int(
|
||||
await self._fetch_oracle_value(
|
||||
"SELECT NVL(SUM(bytes), 0) FROM user_segments"
|
||||
)
|
||||
or 0
|
||||
)
|
||||
return {
|
||||
"database_size_bytes": size_bytes,
|
||||
"database_size_mb": round(size_bytes / 1024 / 1024, 2),
|
||||
"database_size_gb": round(size_bytes / 1024 / 1024 / 1024, 2),
|
||||
}
|
||||
|
||||
async def _get_sqlserver_performance(self) -> Dict[str, Any]:
|
||||
"""获取 SQL Server 性能统计"""
|
||||
counters = await self._execute_odbc_query(
|
||||
"""
|
||||
SELECT RTRIM(counter_name) AS counter_name, cntr_value
|
||||
FROM sys.dm_os_performance_counters
|
||||
WHERE (counter_name = 'Batch Requests/sec' AND instance_name = '')
|
||||
OR (counter_name = 'Page life expectancy'
|
||||
AND object_name LIKE '%Buffer Manager%'
|
||||
AND instance_name = '')
|
||||
OR (counter_name = 'Buffer cache hit ratio'
|
||||
AND object_name LIKE '%Buffer Manager%'
|
||||
AND instance_name = '')
|
||||
"""
|
||||
)
|
||||
stats = {row["counter_name"]: row["cntr_value"] for row in counters}
|
||||
buffer_cache_hit_ratio = float(stats.get("Buffer cache hit ratio", 0) or 0)
|
||||
return {
|
||||
"batch_requests_per_sec": float(stats.get("Batch Requests/sec", 0) or 0),
|
||||
"page_life_expectancy": int(stats.get("Page life expectancy", 0) or 0),
|
||||
"buffer_cache_hit_ratio": round(buffer_cache_hit_ratio, 2),
|
||||
"cache_hit_ratio": round(buffer_cache_hit_ratio, 2),
|
||||
}
|
||||
|
||||
async def _get_oracle_performance(self) -> Dict[str, Any]:
|
||||
"""获取 Oracle 性能统计"""
|
||||
rows = await self._execute_oracle_query(
|
||||
"""
|
||||
SELECT name, value
|
||||
FROM v$sysstat
|
||||
WHERE name IN (
|
||||
'user commits',
|
||||
'user rollbacks',
|
||||
'physical reads',
|
||||
'db block gets',
|
||||
'consistent gets'
|
||||
)
|
||||
"""
|
||||
)
|
||||
stats = {row["name"]: float(row["value"] or 0) for row in rows}
|
||||
logical_reads = stats.get("db block gets", 0) + stats.get("consistent gets", 0)
|
||||
physical_reads = stats.get("physical reads", 0)
|
||||
cache_hit_ratio = (
|
||||
(1 - physical_reads / logical_reads) * 100 if logical_reads > 0 else 0.0
|
||||
)
|
||||
return {
|
||||
"transactions_commit": int(stats.get("user commits", 0)),
|
||||
"transactions_rollback": int(stats.get("user rollbacks", 0)),
|
||||
"cache_hit_ratio": round(cache_hit_ratio, 2),
|
||||
}
|
||||
|
||||
async def _get_sqlserver_tables(self) -> List[Dict[str, Any]]:
|
||||
"""获取 SQL Server 表统计"""
|
||||
rows = await self._execute_odbc_query(
|
||||
"""
|
||||
SELECT TOP 20
|
||||
s.name AS schemaname,
|
||||
t.name AS tablename,
|
||||
MAX(p.rows) AS table_rows,
|
||||
SUM(a.total_pages) * 8 AS total_size_kb,
|
||||
SUM(a.used_pages) * 8 AS used_size_kb,
|
||||
SUM(
|
||||
CASE WHEN i.index_id IN (0, 1) THEN a.used_pages ELSE 0 END
|
||||
) * 8 AS data_size_kb
|
||||
FROM sys.tables t
|
||||
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
||||
INNER JOIN sys.indexes i ON t.object_id = i.object_id
|
||||
INNER JOIN sys.partitions p
|
||||
ON i.object_id = p.object_id AND i.index_id = p.index_id
|
||||
INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
|
||||
WHERE t.is_ms_shipped = 0
|
||||
GROUP BY s.name, t.name
|
||||
ORDER BY total_size_kb DESC
|
||||
"""
|
||||
)
|
||||
return rows
|
||||
|
||||
async def _get_oracle_tables(self) -> List[Dict[str, Any]]:
|
||||
"""获取 Oracle 表统计"""
|
||||
rows = await self._execute_oracle_query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT t.table_name,
|
||||
NVL(t.num_rows, 0) AS table_rows,
|
||||
NVL(s.size_bytes, 0) AS size_bytes
|
||||
FROM user_tables t
|
||||
LEFT JOIN (
|
||||
SELECT segment_name, SUM(bytes) AS size_bytes
|
||||
FROM user_segments
|
||||
WHERE segment_type IN ('TABLE', 'TABLE PARTITION')
|
||||
GROUP BY segment_name
|
||||
) s ON s.segment_name = t.table_name
|
||||
ORDER BY NVL(s.size_bytes, 0) DESC
|
||||
)
|
||||
WHERE ROWNUM <= 20
|
||||
"""
|
||||
)
|
||||
return rows
|
||||
|
||||
async def get_all_info(self, connection_id: str, connection_name: str) -> Dict[str, Any]:
|
||||
"""获取所有数据库监控信息"""
|
||||
timestamp = datetime.now().isoformat()
|
||||
|
||||
try:
|
||||
if not await self.connect():
|
||||
return {
|
||||
'connection_id': connection_id,
|
||||
'connection_name': connection_name,
|
||||
'status': 'disconnected',
|
||||
'basic_info': {},
|
||||
'connection_info': {},
|
||||
'database_size': {},
|
||||
'performance_stats': {'cache_hit_ratio': 0.0},
|
||||
'table_stats': [],
|
||||
'timestamp': timestamp
|
||||
}
|
||||
|
||||
data = {
|
||||
'connection_id': connection_id,
|
||||
'connection_name': connection_name,
|
||||
'status': 'connected',
|
||||
'basic_info': await self.get_basic_info(),
|
||||
'connection_info': await self.get_connection_info(),
|
||||
'database_size': await self.get_database_size(),
|
||||
'performance_stats': await self.get_performance_stats(),
|
||||
'table_stats': await self.get_table_stats(),
|
||||
'timestamp': timestamp
|
||||
}
|
||||
return serialize_data(data)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting database all info: {e}")
|
||||
return {
|
||||
'connection_id': connection_id,
|
||||
'connection_name': connection_name,
|
||||
'status': 'error',
|
||||
'basic_info': {},
|
||||
'connection_info': {},
|
||||
'database_size': {},
|
||||
'performance_stats': {'cache_hit_ratio': 0.0},
|
||||
'table_stats': [],
|
||||
'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,
|
||||
'connections_used': 0,
|
||||
'connection_usage_percent': 0.0,
|
||||
'database_size_mb': 0.0,
|
||||
'cache_hit_ratio': 0.0,
|
||||
'active_connections': 0,
|
||||
'timestamp': timestamp
|
||||
}
|
||||
|
||||
connection_info = await self.get_connection_info()
|
||||
database_size = await self.get_database_size()
|
||||
performance_stats = await self.get_performance_stats()
|
||||
|
||||
data = {
|
||||
'connection_id': connection_id,
|
||||
'connections_used': connection_info.get('total_connections', 0),
|
||||
'connection_usage_percent': connection_info.get('connection_usage_percent', 0.0),
|
||||
'database_size_mb': database_size.get('database_size_mb', 0.0),
|
||||
'cache_hit_ratio': performance_stats.get('cache_hit_ratio', 0.0),
|
||||
'active_connections': connection_info.get('active_connections', 0),
|
||||
'timestamp': timestamp
|
||||
}
|
||||
return serialize_data(data)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting database realtime stats: {e}")
|
||||
return {
|
||||
'connection_id': connection_id,
|
||||
'connections_used': 0,
|
||||
'connection_usage_percent': 0.0,
|
||||
'database_size_mb': 0.0,
|
||||
'cache_hit_ratio': 0.0,
|
||||
'active_connections': 0,
|
||||
'timestamp': timestamp
|
||||
}
|
||||
finally:
|
||||
await self.disconnect()
|
||||
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
数据库监控Schema
|
||||
"""
|
||||
from typing import List, Optional, Dict, Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class DatabaseBasicInfoSchema(BaseModel):
|
||||
"""数据库基本信息Schema"""
|
||||
db_type: str
|
||||
host: str
|
||||
port: int
|
||||
database: str
|
||||
version: str
|
||||
uptime: str
|
||||
timezone: str
|
||||
charset: str
|
||||
|
||||
|
||||
class DatabaseConnectionInfoSchema(BaseModel):
|
||||
"""数据库连接信息Schema"""
|
||||
total_connections: int
|
||||
max_connections: int
|
||||
active_connections: int
|
||||
idle_connections: int
|
||||
connection_usage_percent: float
|
||||
|
||||
|
||||
class DatabaseSizeSchema(BaseModel):
|
||||
"""数据库大小信息Schema"""
|
||||
database_size_bytes: int
|
||||
database_size_mb: float
|
||||
database_size_gb: float
|
||||
|
||||
|
||||
class DatabasePerformanceStatsSchema(BaseModel):
|
||||
"""数据库性能统计Schema"""
|
||||
# PostgreSQL
|
||||
total_backends: Optional[int] = None
|
||||
transactions_commit: Optional[int] = None
|
||||
transactions_rollback: Optional[int] = None
|
||||
tuples_returned: Optional[int] = None
|
||||
tuples_fetched: Optional[int] = None
|
||||
tuples_inserted: Optional[int] = None
|
||||
tuples_updated: Optional[int] = None
|
||||
tuples_deleted: Optional[int] = None
|
||||
|
||||
# MySQL
|
||||
total_queries: Optional[int] = None
|
||||
total_connections: Optional[int] = None
|
||||
slow_queries: Optional[int] = None
|
||||
bytes_received: Optional[int] = None
|
||||
bytes_sent: Optional[int] = None
|
||||
|
||||
# SQL Server
|
||||
batch_requests_per_sec: Optional[int] = None
|
||||
page_life_expectancy: Optional[int] = None
|
||||
buffer_cache_hit_ratio: Optional[float] = None
|
||||
|
||||
# 通用
|
||||
cache_hit_ratio: float = 0.0
|
||||
|
||||
|
||||
class DatabaseTableStatsSchema(BaseModel):
|
||||
"""数据库表统计Schema"""
|
||||
# PostgreSQL
|
||||
schemaname: Optional[str] = None
|
||||
tablename: Optional[str] = None
|
||||
inserts: Optional[int] = None
|
||||
updates: Optional[int] = None
|
||||
deletes: Optional[int] = None
|
||||
live_tuples: Optional[int] = None
|
||||
dead_tuples: Optional[int] = None
|
||||
size: Optional[str] = None
|
||||
|
||||
# MySQL
|
||||
table_name: Optional[str] = None
|
||||
table_rows: Optional[int] = None
|
||||
data_length: Optional[int] = None
|
||||
index_length: Optional[int] = None
|
||||
total_size: Optional[int] = None
|
||||
auto_increment: Optional[int] = None
|
||||
|
||||
# SQL Server
|
||||
total_size_kb: Optional[int] = None
|
||||
used_size_kb: Optional[int] = None
|
||||
data_size_kb: Optional[int] = None
|
||||
|
||||
|
||||
class DatabaseOverviewSchema(BaseModel):
|
||||
"""数据库概览Schema"""
|
||||
connection_id: str
|
||||
connection_name: str
|
||||
status: str
|
||||
basic_info: Dict[str, Any]
|
||||
connection_info: Dict[str, Any]
|
||||
database_size: Dict[str, Any]
|
||||
performance_stats: Dict[str, Any]
|
||||
table_stats: List[Dict[str, Any]]
|
||||
timestamp: str
|
||||
|
||||
|
||||
class DatabaseRealtimeStatsSchema(BaseModel):
|
||||
"""数据库实时统计Schema"""
|
||||
connection_id: str
|
||||
connections_used: int
|
||||
connection_usage_percent: float
|
||||
database_size_mb: float
|
||||
cache_hit_ratio: float
|
||||
active_connections: int
|
||||
timestamp: str
|
||||
|
||||
|
||||
class DatabaseConnectionTestSchema(BaseModel):
|
||||
"""数据库连接测试Schema"""
|
||||
success: bool
|
||||
message: str
|
||||
response_time: Optional[float] = None
|
||||
version: Optional[str] = None
|
||||
db_type: str
|
||||
|
||||
|
||||
class DatabaseConfigSchema(BaseModel):
|
||||
"""数据库监控目标(对应数据库连接 code)"""
|
||||
name: str
|
||||
db_name: str
|
||||
db_type: str
|
||||
host: str
|
||||
port: int
|
||||
database: str
|
||||
user: str
|
||||
has_password: bool
|
||||
is_system: bool = False
|
||||
Reference in New Issue
Block a user