feat: Complete Rust port of WiFi-DensePose with modular crates
Major changes: - Organized Python v1 implementation into v1/ subdirectory - Created Rust workspace with 9 modular crates: - wifi-densepose-core: Core types, traits, errors - wifi-densepose-signal: CSI processing, phase sanitization, FFT - wifi-densepose-nn: Neural network inference (ONNX/Candle/tch) - wifi-densepose-api: Axum-based REST/WebSocket API - wifi-densepose-db: SQLx database layer - wifi-densepose-config: Configuration management - wifi-densepose-hardware: Hardware abstraction - wifi-densepose-wasm: WebAssembly bindings - wifi-densepose-cli: Command-line interface Documentation: - ADR-001: Workspace structure - ADR-002: Signal processing library selection - ADR-003: Neural network inference strategy - DDD domain model with bounded contexts Testing: - 69 tests passing across all crates - Signal processing: 45 tests - Neural networks: 21 tests - Core: 3 doc tests Performance targets: - 10x faster CSI processing (~0.5ms vs ~5ms) - 5x lower memory usage (~100MB vs ~500MB) - WASM support for browser deployment
This commit is contained in:
501
v1/src/commands/status.py
Normal file
501
v1/src/commands/status.py
Normal file
@@ -0,0 +1,501 @@
|
||||
"""
|
||||
Status command implementation for WiFi-DensePose API
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import psutil
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from src.config.settings import Settings
|
||||
from src.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
async def status_command(
|
||||
settings: Settings,
|
||||
output_format: str = "text",
|
||||
detailed: bool = False
|
||||
) -> None:
|
||||
"""Show the status of the WiFi-DensePose API server."""
|
||||
|
||||
logger.debug("Gathering server status information...")
|
||||
|
||||
try:
|
||||
# Collect status information
|
||||
status_data = await _collect_status_data(settings, detailed)
|
||||
|
||||
# Output status
|
||||
if output_format == "json":
|
||||
print(json.dumps(status_data, indent=2, default=str))
|
||||
else:
|
||||
_print_text_status(status_data, detailed)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get status: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def _collect_status_data(settings: Settings, detailed: bool) -> Dict[str, Any]:
|
||||
"""Collect comprehensive status data."""
|
||||
|
||||
status_data = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"server": await _get_server_status(settings),
|
||||
"system": _get_system_status(),
|
||||
"configuration": _get_configuration_status(settings),
|
||||
}
|
||||
|
||||
if detailed:
|
||||
status_data.update({
|
||||
"database": await _get_database_status(settings),
|
||||
"background_tasks": await _get_background_tasks_status(settings),
|
||||
"resources": _get_resource_usage(),
|
||||
"health": await _get_health_status(settings),
|
||||
})
|
||||
|
||||
return status_data
|
||||
|
||||
|
||||
async def _get_server_status(settings: Settings) -> Dict[str, Any]:
|
||||
"""Get server process status."""
|
||||
|
||||
from src.commands.stop import get_server_status
|
||||
|
||||
status = get_server_status(settings)
|
||||
|
||||
server_info = {
|
||||
"running": status["running"],
|
||||
"pid": status["pid"],
|
||||
"pid_file": status["pid_file"],
|
||||
"pid_file_exists": status["pid_file_exists"],
|
||||
}
|
||||
|
||||
if status["running"] and status["pid"]:
|
||||
try:
|
||||
# Get process information
|
||||
process = psutil.Process(status["pid"])
|
||||
|
||||
server_info.update({
|
||||
"start_time": datetime.fromtimestamp(process.create_time()).isoformat(),
|
||||
"uptime_seconds": time.time() - process.create_time(),
|
||||
"memory_usage_mb": process.memory_info().rss / (1024 * 1024),
|
||||
"cpu_percent": process.cpu_percent(),
|
||||
"status": process.status(),
|
||||
"num_threads": process.num_threads(),
|
||||
"connections": len(process.connections()) if hasattr(process, 'connections') else None,
|
||||
})
|
||||
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
|
||||
server_info["error"] = f"Cannot access process info: {e}"
|
||||
|
||||
return server_info
|
||||
|
||||
|
||||
def _get_system_status() -> Dict[str, Any]:
|
||||
"""Get system status information."""
|
||||
|
||||
uname_info = psutil.os.uname()
|
||||
return {
|
||||
"hostname": uname_info.nodename,
|
||||
"platform": uname_info.sysname,
|
||||
"architecture": uname_info.machine,
|
||||
"python_version": f"{psutil.sys.version_info.major}.{psutil.sys.version_info.minor}.{psutil.sys.version_info.micro}",
|
||||
"boot_time": datetime.fromtimestamp(psutil.boot_time()).isoformat(),
|
||||
"uptime_seconds": time.time() - psutil.boot_time(),
|
||||
}
|
||||
|
||||
|
||||
def _get_configuration_status(settings: Settings) -> Dict[str, Any]:
|
||||
"""Get configuration status."""
|
||||
|
||||
return {
|
||||
"environment": settings.environment,
|
||||
"debug": settings.debug,
|
||||
"version": settings.version,
|
||||
"host": settings.host,
|
||||
"port": settings.port,
|
||||
"database_configured": bool(settings.database_url or (settings.db_host and settings.db_name)),
|
||||
"redis_enabled": settings.redis_enabled,
|
||||
"monitoring_enabled": settings.monitoring_interval_seconds > 0,
|
||||
"cleanup_enabled": settings.cleanup_interval_seconds > 0,
|
||||
"backup_enabled": settings.backup_interval_seconds > 0,
|
||||
}
|
||||
|
||||
|
||||
async def _get_database_status(settings: Settings) -> Dict[str, Any]:
|
||||
"""Get database status."""
|
||||
|
||||
db_status = {
|
||||
"connected": False,
|
||||
"connection_pool": None,
|
||||
"tables": {},
|
||||
"error": None,
|
||||
}
|
||||
|
||||
try:
|
||||
from src.database.connection import get_database_manager
|
||||
|
||||
db_manager = get_database_manager(settings)
|
||||
|
||||
# Test connection
|
||||
await db_manager.test_connection()
|
||||
db_status["connected"] = True
|
||||
|
||||
# Get connection stats
|
||||
connection_stats = await db_manager.get_connection_stats()
|
||||
db_status["connection_pool"] = connection_stats
|
||||
|
||||
# Get table counts
|
||||
async with db_manager.get_async_session() as session:
|
||||
from sqlalchemy import text, func
|
||||
from src.database.models import Device, Session, CSIData, PoseDetection, SystemMetric, AuditLog
|
||||
|
||||
tables = {
|
||||
"devices": Device,
|
||||
"sessions": Session,
|
||||
"csi_data": CSIData,
|
||||
"pose_detections": PoseDetection,
|
||||
"system_metrics": SystemMetric,
|
||||
"audit_logs": AuditLog,
|
||||
}
|
||||
|
||||
for table_name, model in tables.items():
|
||||
try:
|
||||
result = await session.execute(
|
||||
text(f"SELECT COUNT(*) FROM {table_name}")
|
||||
)
|
||||
count = result.scalar()
|
||||
db_status["tables"][table_name] = {"count": count}
|
||||
except Exception as e:
|
||||
db_status["tables"][table_name] = {"error": str(e)}
|
||||
|
||||
except Exception as e:
|
||||
db_status["error"] = str(e)
|
||||
|
||||
return db_status
|
||||
|
||||
|
||||
async def _get_background_tasks_status(settings: Settings) -> Dict[str, Any]:
|
||||
"""Get background tasks status."""
|
||||
|
||||
tasks_status = {}
|
||||
|
||||
try:
|
||||
# Cleanup tasks
|
||||
from src.tasks.cleanup import get_cleanup_manager
|
||||
cleanup_manager = get_cleanup_manager(settings)
|
||||
tasks_status["cleanup"] = cleanup_manager.get_stats()
|
||||
|
||||
except Exception as e:
|
||||
tasks_status["cleanup"] = {"error": str(e)}
|
||||
|
||||
try:
|
||||
# Monitoring tasks
|
||||
from src.tasks.monitoring import get_monitoring_manager
|
||||
monitoring_manager = get_monitoring_manager(settings)
|
||||
tasks_status["monitoring"] = monitoring_manager.get_stats()
|
||||
|
||||
except Exception as e:
|
||||
tasks_status["monitoring"] = {"error": str(e)}
|
||||
|
||||
try:
|
||||
# Backup tasks
|
||||
from src.tasks.backup import get_backup_manager
|
||||
backup_manager = get_backup_manager(settings)
|
||||
tasks_status["backup"] = backup_manager.get_stats()
|
||||
|
||||
except Exception as e:
|
||||
tasks_status["backup"] = {"error": str(e)}
|
||||
|
||||
return tasks_status
|
||||
|
||||
|
||||
def _get_resource_usage() -> Dict[str, Any]:
|
||||
"""Get system resource usage."""
|
||||
|
||||
# CPU usage
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
cpu_count = psutil.cpu_count()
|
||||
|
||||
# Memory usage
|
||||
memory = psutil.virtual_memory()
|
||||
swap = psutil.swap_memory()
|
||||
|
||||
# Disk usage
|
||||
disk = psutil.disk_usage('/')
|
||||
|
||||
# Network I/O
|
||||
network = psutil.net_io_counters()
|
||||
|
||||
return {
|
||||
"cpu": {
|
||||
"usage_percent": cpu_percent,
|
||||
"count": cpu_count,
|
||||
},
|
||||
"memory": {
|
||||
"total_mb": memory.total / (1024 * 1024),
|
||||
"used_mb": memory.used / (1024 * 1024),
|
||||
"available_mb": memory.available / (1024 * 1024),
|
||||
"usage_percent": memory.percent,
|
||||
},
|
||||
"swap": {
|
||||
"total_mb": swap.total / (1024 * 1024),
|
||||
"used_mb": swap.used / (1024 * 1024),
|
||||
"usage_percent": swap.percent,
|
||||
},
|
||||
"disk": {
|
||||
"total_gb": disk.total / (1024 * 1024 * 1024),
|
||||
"used_gb": disk.used / (1024 * 1024 * 1024),
|
||||
"free_gb": disk.free / (1024 * 1024 * 1024),
|
||||
"usage_percent": (disk.used / disk.total) * 100,
|
||||
},
|
||||
"network": {
|
||||
"bytes_sent": network.bytes_sent,
|
||||
"bytes_recv": network.bytes_recv,
|
||||
"packets_sent": network.packets_sent,
|
||||
"packets_recv": network.packets_recv,
|
||||
} if network else None,
|
||||
}
|
||||
|
||||
|
||||
async def _get_health_status(settings: Settings) -> Dict[str, Any]:
|
||||
"""Get overall health status."""
|
||||
|
||||
health = {
|
||||
"status": "healthy",
|
||||
"checks": {},
|
||||
"issues": [],
|
||||
}
|
||||
|
||||
# Check database health
|
||||
try:
|
||||
from src.database.connection import get_database_manager
|
||||
|
||||
db_manager = get_database_manager(settings)
|
||||
await db_manager.test_connection()
|
||||
health["checks"]["database"] = "healthy"
|
||||
|
||||
except Exception as e:
|
||||
health["checks"]["database"] = "unhealthy"
|
||||
health["issues"].append(f"Database connection failed: {e}")
|
||||
health["status"] = "unhealthy"
|
||||
|
||||
# Check disk space
|
||||
disk = psutil.disk_usage('/')
|
||||
disk_usage_percent = (disk.used / disk.total) * 100
|
||||
|
||||
if disk_usage_percent > 90:
|
||||
health["checks"]["disk_space"] = "critical"
|
||||
health["issues"].append(f"Disk usage critical: {disk_usage_percent:.1f}%")
|
||||
health["status"] = "critical"
|
||||
elif disk_usage_percent > 80:
|
||||
health["checks"]["disk_space"] = "warning"
|
||||
health["issues"].append(f"Disk usage high: {disk_usage_percent:.1f}%")
|
||||
if health["status"] == "healthy":
|
||||
health["status"] = "warning"
|
||||
else:
|
||||
health["checks"]["disk_space"] = "healthy"
|
||||
|
||||
# Check memory usage
|
||||
memory = psutil.virtual_memory()
|
||||
|
||||
if memory.percent > 90:
|
||||
health["checks"]["memory"] = "critical"
|
||||
health["issues"].append(f"Memory usage critical: {memory.percent:.1f}%")
|
||||
health["status"] = "critical"
|
||||
elif memory.percent > 80:
|
||||
health["checks"]["memory"] = "warning"
|
||||
health["issues"].append(f"Memory usage high: {memory.percent:.1f}%")
|
||||
if health["status"] == "healthy":
|
||||
health["status"] = "warning"
|
||||
else:
|
||||
health["checks"]["memory"] = "healthy"
|
||||
|
||||
# Check log directory
|
||||
log_dir = Path(settings.log_directory)
|
||||
if log_dir.exists() and log_dir.is_dir():
|
||||
health["checks"]["log_directory"] = "healthy"
|
||||
else:
|
||||
health["checks"]["log_directory"] = "unhealthy"
|
||||
health["issues"].append(f"Log directory not accessible: {log_dir}")
|
||||
health["status"] = "unhealthy"
|
||||
|
||||
# Check backup directory
|
||||
backup_dir = Path(settings.backup_directory)
|
||||
if backup_dir.exists() and backup_dir.is_dir():
|
||||
health["checks"]["backup_directory"] = "healthy"
|
||||
else:
|
||||
health["checks"]["backup_directory"] = "unhealthy"
|
||||
health["issues"].append(f"Backup directory not accessible: {backup_dir}")
|
||||
health["status"] = "unhealthy"
|
||||
|
||||
return health
|
||||
|
||||
|
||||
def _print_text_status(status_data: Dict[str, Any], detailed: bool) -> None:
|
||||
"""Print status in human-readable text format."""
|
||||
|
||||
print("=" * 60)
|
||||
print("WiFi-DensePose API Server Status")
|
||||
print("=" * 60)
|
||||
print(f"Timestamp: {status_data['timestamp']}")
|
||||
print()
|
||||
|
||||
# Server status
|
||||
server = status_data["server"]
|
||||
print("🖥️ Server Status:")
|
||||
if server["running"]:
|
||||
print(f" ✅ Running (PID: {server['pid']})")
|
||||
if "start_time" in server:
|
||||
uptime = timedelta(seconds=int(server["uptime_seconds"]))
|
||||
print(f" ⏱️ Uptime: {uptime}")
|
||||
print(f" 💾 Memory: {server['memory_usage_mb']:.1f} MB")
|
||||
print(f" 🔧 CPU: {server['cpu_percent']:.1f}%")
|
||||
print(f" 🧵 Threads: {server['num_threads']}")
|
||||
else:
|
||||
print(" ❌ Not running")
|
||||
if server["pid_file_exists"]:
|
||||
print(" ⚠️ Stale PID file exists")
|
||||
print()
|
||||
|
||||
# System status
|
||||
system = status_data["system"]
|
||||
print("🖥️ System:")
|
||||
print(f" Hostname: {system['hostname']}")
|
||||
print(f" Platform: {system['platform']} ({system['architecture']})")
|
||||
print(f" Python: {system['python_version']}")
|
||||
uptime = timedelta(seconds=int(system["uptime_seconds"]))
|
||||
print(f" Uptime: {uptime}")
|
||||
print()
|
||||
|
||||
# Configuration
|
||||
config = status_data["configuration"]
|
||||
print("⚙️ Configuration:")
|
||||
print(f" Environment: {config['environment']}")
|
||||
print(f" Debug: {config['debug']}")
|
||||
print(f" API Version: {config['version']}")
|
||||
print(f" Listen: {config['host']}:{config['port']}")
|
||||
print(f" Database: {'✅' if config['database_configured'] else '❌'}")
|
||||
print(f" Redis: {'✅' if config['redis_enabled'] else '❌'}")
|
||||
print(f" Monitoring: {'✅' if config['monitoring_enabled'] else '❌'}")
|
||||
print(f" Cleanup: {'✅' if config['cleanup_enabled'] else '❌'}")
|
||||
print(f" Backup: {'✅' if config['backup_enabled'] else '❌'}")
|
||||
print()
|
||||
|
||||
if detailed:
|
||||
# Database status
|
||||
if "database" in status_data:
|
||||
db = status_data["database"]
|
||||
print("🗄️ Database:")
|
||||
if db["connected"]:
|
||||
print(" ✅ Connected")
|
||||
if "tables" in db:
|
||||
print(" 📊 Table counts:")
|
||||
for table, info in db["tables"].items():
|
||||
if "count" in info:
|
||||
print(f" {table}: {info['count']:,}")
|
||||
else:
|
||||
print(f" {table}: Error - {info.get('error', 'Unknown')}")
|
||||
else:
|
||||
print(f" ❌ Not connected: {db.get('error', 'Unknown error')}")
|
||||
print()
|
||||
|
||||
# Background tasks
|
||||
if "background_tasks" in status_data:
|
||||
tasks = status_data["background_tasks"]
|
||||
print("🔄 Background Tasks:")
|
||||
for task_name, task_info in tasks.items():
|
||||
if "error" in task_info:
|
||||
print(f" ❌ {task_name}: {task_info['error']}")
|
||||
else:
|
||||
manager_info = task_info.get("manager", {})
|
||||
print(f" 📋 {task_name}:")
|
||||
print(f" Running: {manager_info.get('running', 'Unknown')}")
|
||||
print(f" Last run: {manager_info.get('last_run', 'Never')}")
|
||||
print(f" Run count: {manager_info.get('run_count', 0)}")
|
||||
print()
|
||||
|
||||
# Resource usage
|
||||
if "resources" in status_data:
|
||||
resources = status_data["resources"]
|
||||
print("📊 Resource Usage:")
|
||||
|
||||
cpu = resources["cpu"]
|
||||
print(f" 🔧 CPU: {cpu['usage_percent']:.1f}% ({cpu['count']} cores)")
|
||||
|
||||
memory = resources["memory"]
|
||||
print(f" 💾 Memory: {memory['usage_percent']:.1f}% "
|
||||
f"({memory['used_mb']:.0f}/{memory['total_mb']:.0f} MB)")
|
||||
|
||||
disk = resources["disk"]
|
||||
print(f" 💿 Disk: {disk['usage_percent']:.1f}% "
|
||||
f"({disk['used_gb']:.1f}/{disk['total_gb']:.1f} GB)")
|
||||
print()
|
||||
|
||||
# Health status
|
||||
if "health" in status_data:
|
||||
health = status_data["health"]
|
||||
print("🏥 Health Status:")
|
||||
|
||||
status_emoji = {
|
||||
"healthy": "✅",
|
||||
"warning": "⚠️",
|
||||
"critical": "❌",
|
||||
"unhealthy": "❌"
|
||||
}
|
||||
|
||||
print(f" Overall: {status_emoji.get(health['status'], '❓')} {health['status'].upper()}")
|
||||
|
||||
if health["issues"]:
|
||||
print(" Issues:")
|
||||
for issue in health["issues"]:
|
||||
print(f" • {issue}")
|
||||
|
||||
print(" Checks:")
|
||||
for check, status in health["checks"].items():
|
||||
emoji = status_emoji.get(status, "❓")
|
||||
print(f" {emoji} {check}: {status}")
|
||||
print()
|
||||
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
def get_quick_status(settings: Settings) -> str:
|
||||
"""Get a quick one-line status."""
|
||||
|
||||
from src.commands.stop import get_server_status
|
||||
|
||||
status = get_server_status(settings)
|
||||
|
||||
if status["running"]:
|
||||
return f"✅ Running (PID: {status['pid']})"
|
||||
elif status["pid_file_exists"]:
|
||||
return "⚠️ Not running (stale PID file)"
|
||||
else:
|
||||
return "❌ Not running"
|
||||
|
||||
|
||||
async def check_health(settings: Settings) -> bool:
|
||||
"""Quick health check - returns True if healthy."""
|
||||
|
||||
try:
|
||||
status_data = await _collect_status_data(settings, detailed=True)
|
||||
|
||||
# Check if server is running
|
||||
if not status_data["server"]["running"]:
|
||||
return False
|
||||
|
||||
# Check health status
|
||||
if "health" in status_data:
|
||||
health_status = status_data["health"]["status"]
|
||||
return health_status in ["healthy", "warning"]
|
||||
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
Reference in New Issue
Block a user