mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-27 04:20:09 +00:00
282 lines
9.6 KiB
Python
282 lines
9.6 KiB
Python
"""Comprehensive system and MCP statistics."""
|
|
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import TypedDict, Unpack, final, override
|
|
|
|
import psutil
|
|
from hanzo_tools.database.database_manager import DatabaseManager
|
|
from hanzo_tools.shell.run_background import RunBackgroundTool
|
|
from mcp.server.fastmcp import Context as MCPContext
|
|
|
|
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
|
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
from hanzo_mcp.tools.mcp.mcp_add import McpAddTool
|
|
|
|
|
|
class StatsParams(TypedDict, total=False):
|
|
"""Parameters for stats tool."""
|
|
|
|
pass
|
|
|
|
|
|
@final
|
|
class StatsTool(BaseTool):
|
|
"""Tool for showing comprehensive system and MCP statistics."""
|
|
|
|
def __init__(self, db_manager: DatabaseManager = None):
|
|
"""Initialize the stats tool.
|
|
|
|
Args:
|
|
db_manager: Optional database manager for DB stats
|
|
"""
|
|
self.db_manager = db_manager
|
|
|
|
@property
|
|
@override
|
|
def name(self) -> str:
|
|
"""Get the tool name."""
|
|
return "stats"
|
|
|
|
@property
|
|
@override
|
|
def description(self) -> str:
|
|
"""Get the tool description."""
|
|
return """Show comprehensive system and Hanzo AI statistics.
|
|
|
|
Displays:
|
|
- System resources (CPU, memory, disk)
|
|
- Running processes
|
|
- Database usage
|
|
- MCP server status
|
|
- Tool usage statistics
|
|
- Warnings for high resource usage
|
|
|
|
Example:
|
|
- stats
|
|
"""
|
|
|
|
@override
|
|
@auto_timeout("stats")
|
|
async def call(
|
|
self,
|
|
ctx: MCPContext,
|
|
**params: Unpack[StatsParams],
|
|
) -> str:
|
|
"""Get comprehensive statistics.
|
|
|
|
Args:
|
|
ctx: MCP context
|
|
**params: Tool parameters
|
|
|
|
Returns:
|
|
Comprehensive statistics
|
|
"""
|
|
tool_ctx = create_tool_context(ctx)
|
|
await tool_ctx.set_tool_info(self.name)
|
|
|
|
output = []
|
|
warnings = []
|
|
|
|
# Header
|
|
output.append("=== Hanzo AI System Statistics ===")
|
|
output.append(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
output.append("")
|
|
|
|
# System Resources
|
|
output.append("=== System Resources ===")
|
|
|
|
# CPU
|
|
cpu_percent = psutil.cpu_percent(interval=1)
|
|
cpu_count = psutil.cpu_count()
|
|
output.append(f"CPU Usage: {cpu_percent}% ({cpu_count} cores)")
|
|
if cpu_percent > 90:
|
|
warnings.append(f"⚠️ HIGH CPU USAGE: {cpu_percent}%")
|
|
|
|
# Memory
|
|
memory = psutil.virtual_memory()
|
|
memory_used_gb = memory.used / (1024**3)
|
|
memory_total_gb = memory.total / (1024**3)
|
|
memory_percent = memory.percent
|
|
output.append(
|
|
f"Memory: {memory_used_gb:.1f}/{memory_total_gb:.1f} GB ({memory_percent}%)"
|
|
)
|
|
if memory_percent > 90:
|
|
warnings.append(f"⚠️ HIGH MEMORY USAGE: {memory_percent}%")
|
|
|
|
# Disk
|
|
disk = psutil.disk_usage("/")
|
|
disk_used_gb = disk.used / (1024**3)
|
|
disk_total_gb = disk.total / (1024**3)
|
|
disk_percent = disk.percent
|
|
disk_free_gb = disk.free / (1024**3)
|
|
output.append(
|
|
f"Disk: {disk_used_gb:.1f}/{disk_total_gb:.1f} GB ({disk_percent}%)"
|
|
)
|
|
output.append(f"Free Space: {disk_free_gb:.1f} GB")
|
|
if disk_percent > 90:
|
|
warnings.append(
|
|
f"⚠️ LOW DISK SPACE: Only {disk_free_gb:.1f} GB free ({100 - disk_percent:.1f}% remaining)"
|
|
)
|
|
|
|
output.append("")
|
|
|
|
# Background Processes
|
|
output.append("=== Background Processes ===")
|
|
processes = RunBackgroundTool.get_processes()
|
|
running_count = 0
|
|
total_memory_mb = 0
|
|
|
|
if processes:
|
|
for proc in processes.values():
|
|
if proc.is_running():
|
|
running_count += 1
|
|
try:
|
|
ps_proc = psutil.Process(proc.process.pid)
|
|
memory_mb = ps_proc.memory_info().rss / (1024**2)
|
|
total_memory_mb += memory_mb
|
|
except Exception:
|
|
pass
|
|
|
|
output.append(f"Running Processes: {running_count}")
|
|
output.append(f"Total Memory Usage: {total_memory_mb:.1f} MB")
|
|
|
|
# List top processes by memory
|
|
if running_count > 0:
|
|
output.append("\nTop Processes:")
|
|
proc_list = []
|
|
for proc_id, proc in processes.items():
|
|
if proc.is_running():
|
|
try:
|
|
ps_proc = psutil.Process(proc.process.pid)
|
|
memory_mb = ps_proc.memory_info().rss / (1024**2)
|
|
cpu = ps_proc.cpu_percent(interval=0.1)
|
|
proc_list.append((proc.name, memory_mb, cpu, proc_id))
|
|
except Exception:
|
|
proc_list.append((proc.name, 0, 0, proc_id))
|
|
|
|
proc_list.sort(key=lambda x: x[1], reverse=True)
|
|
for name, mem, cpu, pid in proc_list[:5]:
|
|
output.append(f" - {name} ({pid}): {mem:.1f} MB, {cpu:.1f}% CPU")
|
|
else:
|
|
output.append("No background processes running")
|
|
|
|
output.append("")
|
|
|
|
# Database Usage
|
|
if self.db_manager:
|
|
output.append("=== Database Usage ===")
|
|
db_dir = Path.home() / ".hanzo" / "db"
|
|
total_db_size = 0
|
|
|
|
if db_dir.exists():
|
|
for db_file in db_dir.rglob("*.db"):
|
|
size = db_file.stat().st_size
|
|
total_db_size += size
|
|
|
|
output.append(
|
|
f"Total Database Size: {total_db_size / (1024**2):.1f} MB"
|
|
)
|
|
output.append(f"Active Projects: {len(self.db_manager.projects)}")
|
|
|
|
# List largest databases
|
|
db_sizes = []
|
|
for db_file in db_dir.rglob("*.db"):
|
|
size = db_file.stat().st_size / (1024**2)
|
|
if size > 0.1: # Only show DBs > 100KB
|
|
project = db_file.parent.parent.name
|
|
db_type = db_file.stem
|
|
db_sizes.append((project, db_type, size))
|
|
|
|
if db_sizes:
|
|
db_sizes.sort(key=lambda x: x[2], reverse=True)
|
|
output.append("\nLargest Databases:")
|
|
for project, db_type, size in db_sizes[:5]:
|
|
output.append(f" - {project}/{db_type}: {size:.1f} MB")
|
|
else:
|
|
output.append("No databases found")
|
|
|
|
output.append("")
|
|
|
|
# MCP Servers
|
|
output.append("=== MCP Servers ===")
|
|
mcp_servers = McpAddTool.get_servers()
|
|
if mcp_servers:
|
|
running_mcp = sum(
|
|
1 for s in mcp_servers.values() if s.get("status") == "running"
|
|
)
|
|
total_mcp_tools = sum(len(s.get("tools", [])) for s in mcp_servers.values())
|
|
|
|
output.append(f"Total Servers: {len(mcp_servers)}")
|
|
output.append(f"Running: {running_mcp}")
|
|
output.append(f"Total Tools Available: {total_mcp_tools}")
|
|
else:
|
|
output.append("No MCP servers configured")
|
|
|
|
output.append("")
|
|
|
|
# Hanzo AI Specifics
|
|
output.append("=== Hanzo AI ===")
|
|
|
|
# Log directory size
|
|
log_dir = Path.home() / ".hanzo" / "logs"
|
|
if log_dir.exists():
|
|
log_size = sum(f.stat().st_size for f in log_dir.rglob("*") if f.is_file())
|
|
log_count = len(list(log_dir.rglob("*.log")))
|
|
output.append(f"Log Files: {log_count} ({log_size / (1024**2):.1f} MB)")
|
|
|
|
if log_size > 100 * 1024**2: # > 100MB
|
|
warnings.append(
|
|
f"⚠️ Large log directory: {log_size / (1024**2):.1f} MB"
|
|
)
|
|
|
|
# Config directory
|
|
config_dir = Path.home() / ".hanzo" / "mcp"
|
|
if config_dir.exists():
|
|
config_count = len(list(config_dir.rglob("*.json")))
|
|
output.append(f"Config Files: {config_count}")
|
|
|
|
# Tool status (if available)
|
|
# Note: Tool usage statistics tracking can be added here
|
|
output.append("\nTool Categories:")
|
|
output.append(" - File Operations: grep, find_files, read, write, edit")
|
|
output.append(" - Shell: bash, run_background, processes, pkill")
|
|
output.append(" - Database: sql_query, graph_query, vector_search")
|
|
output.append(" - Package Runners: uvx, npx, uvx_background, npx_background")
|
|
output.append(" - MCP Management: mcp_add, mcp_remove, mcp_stats")
|
|
|
|
# Warnings Section
|
|
if warnings:
|
|
output.append("\n=== ⚠️ WARNINGS ===")
|
|
for warning in warnings:
|
|
output.append(warning)
|
|
output.append("")
|
|
|
|
# Recommendations
|
|
output.append("=== Recommendations ===")
|
|
if disk_free_gb < 5:
|
|
output.append("- Free up disk space (< 5GB remaining)")
|
|
if memory_percent > 80:
|
|
output.append("- Close unused applications to free memory")
|
|
if running_count > 10:
|
|
output.append("- Consider stopping unused background processes")
|
|
if log_size > 50 * 1024**2:
|
|
output.append("- Clean up old log files in ~/.hanzo/logs")
|
|
|
|
if not any(
|
|
[
|
|
disk_free_gb < 5,
|
|
memory_percent > 80,
|
|
running_count > 10,
|
|
log_size > 50 * 1024**2,
|
|
]
|
|
):
|
|
output.append("✅ System resources are healthy")
|
|
|
|
return "\n".join(output)
|
|
|
|
def register(self, mcp_server) -> None:
|
|
"""Register this tool with the MCP server."""
|
|
pass
|