Files
ruvnet--RuView/archive/v1/src/commands/status.py
T
rUv 81cc241b9e chore(repo): move v1/ → archive/v1/ + add archive/README.md (#430)
The Rust port at v2/ has been the primary codebase since the rename
in #427. The Python implementation at v1/ is no longer the active
target; the only load-bearing path is the deterministic proof bundle
at v1/data/proof/ (per ADR-011 / ADR-028 witness verification).

Move the whole Python tree into archive/v1/ and document the policy
in archive/README.md: no new features, bug fixes only when they affect
a still-load-bearing path (currently just the proof), CI continues to
verify the proof on every push and PR.

Path references updated in 26 files via path-pattern sed (only
matches v1/<known-child> patterns, never bare v1 or API URLs like
/api/v1/). Two double-prefix typos (archive/archive/v1/) caught and
hand-fixed in verify-pipeline.yml and ADR-011.

Validated:
- Python proof verify.py imports cleanly at archive/v1/data/proof/
  (numpy/scipy still required; CI installs requirements-lock.txt
  from archive/v1/ now)
- cargo test --workspace --no-default-features → 1,539 passed,
  0 failed, 8 ignored (unaffected by Python tree relocation)
- ESP32-S3 on COM7 untouched (no firmware paths changed)

After-merge: contributors should re-run any local `python v1/...`
commands as `python archive/v1/...` (CLAUDE.md and CHANGELOG already
updated).
2026-04-25 23:07:52 -04:00

511 lines
17 KiB
Python

"""
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:
import sqlalchemy as sa
from sqlalchemy import text, func, select
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,
}
# Whitelist of allowed table names to prevent SQL injection
allowed_table_names = set(tables.keys())
for table_name, model in tables.items():
try:
# Validate table_name against whitelist to prevent SQL injection
if table_name not in allowed_table_names:
db_status["tables"][table_name] = {"error": "Invalid table name"}
continue
# Use SQLAlchemy ORM model for safe query instead of raw SQL
result = await session.execute(
select(func.count()).select_from(model)
)
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