diff --git a/README.md b/README.md index fbf6de9..5162288 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Un agente configurabile basato su DeepAgents che supporta piรน provider LLM. - **Web UI**: Interfaccia web per gestione configurazioni - **Comandi integrati**: Comandi speciali con prefisso "/" per gestione rapida - **Monitoraggio**: Sistema di logging e monitoraggio delle performance +- **Dashboard performance**: Monitoraggio dettagliato delle performance - **Sistema di plugin**: Estendi le funzionalitร  con plugin personalizzati - **Supporto multilingue**: Interfaccia in italiano e inglese @@ -210,6 +211,7 @@ Quando interagisci con l'agente, puoi usare comandi speciali preceduti da "/" pe /clear - Pulisce lo schermo /plugins - Elenca i plugin disponibili /language - Cambia la lingua dell'interfaccia +/performance - Mostra il report delle performance /exit, /quit - Esci dall'agente ``` @@ -232,6 +234,16 @@ Ollama URL: http://192.168.0.50:11434 ๐Ÿ‘ค You: Elenca i file nella directory corrente ๐Ÿค– MaxClaw: [Esegue ls e mostra i file] +๐Ÿ‘ค You: /performance +๐Ÿ“Š Performance Report (Last 24 hours) +================================================== + +๐Ÿ“ˆ Request Statistics: + Total Requests: 150 + Total Errors: 2 + Error Rate: 1.33% + Avg Response Time: 0.245s + ๐Ÿ‘ค You: /exit ๐Ÿ‘‹ Goodbye! ``` @@ -495,6 +507,49 @@ Per aggiungere una nuova lingua: 3. Traduci tutti i valori 4. L'agente rileverร  automaticamente la nuova lingua +## Monitoraggio Performance + +MaxClaw Agent include un sistema avanzato di monitoraggio delle performance. + +### Dashboard Performance + +Il dashboard raccoglie e analizza: +- Richieste API e tempi di risposta +- Utilizzo degli strumenti +- Utilizzo dei provider +- Statistiche di sistema (CPU, memoria, disco) +- Errori e tassi di successo + +### Visualizzare le Performance + +```bash +# Usa il comando /performance +/performance + +# Oppure esegui lo script direttamente +python dashboard.py +``` + +### Report Disponibili + +Il report include: +- Statistiche richieste (totale, errori, tasso di errore) +- Distribuzione codici di stato +- Endpoint piรน utilizzati +- Utilizzo degli strumenti (chiamate, tasso di successo, durata media) +- Utilizzo dei provider +- Statistiche di sistema + +### Configurazione + +I dati di performance vengono salvati in `~/.maxclaw/performance/` e includono: +- Richieste API +- Errori +- Tempi di risposta +- Utilizzo strumenti +- Utilizzo provider +- Statistiche di sistema + ## Test Esegui la suite di test: @@ -566,6 +621,7 @@ MaxClaw Agent include un sistema di monitoraggio e logging: - **Statistiche di sistema**: Monitoraggio di uptime, richieste, errori - **Metriche di performance**: Tempi di risposta, utilizzo cache - **Report di stato**: Visualizzazione completa dello stato del sistema +- **Dashboard performance**: Monitoraggio avanzato delle performance ## Sicurezza diff --git a/dashboard.py b/dashboard.py new file mode 100644 index 0000000..6cf901e --- /dev/null +++ b/dashboard.py @@ -0,0 +1,420 @@ +""" +Performance monitoring dashboard for MaxClaw Agent +""" +import os +import sys +import time +import json +from datetime import datetime, timedelta +from typing import Dict, List, Any, Optional +from pathlib import Path +import threading + +# Add current directory to path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + + +class PerformanceDashboard: + """Performance monitoring dashboard""" + + def __init__(self, data_dir: Optional[str] = None): + """ + Initialize performance dashboard + + Args: + data_dir: Directory for storing performance data + """ + if data_dir is None: + self.data_dir = Path.home() / ".maxclaw" / "performance" + else: + self.data_dir = Path(data_dir) + + # Create data directory + self.data_dir.mkdir(parents=True, exist_ok=True) + + # Performance metrics + self.metrics = { + "requests": [], + "errors": [], + "response_times": [], + "tool_usage": {}, + "provider_usage": {}, + "system_stats": [] + } + + # Load existing data + self._load_data() + + # Start background monitoring + self._start_monitoring() + + def _load_data(self): + """Load existing performance data""" + data_file = self.data_dir / "performance_data.json" + + if data_file.exists(): + try: + with open(data_file, 'r') as f: + data = json.load(f) + self.metrics.update(data) + except Exception as e: + print(f"Warning: Could not load performance data: {e}") + + def _save_data(self): + """Save performance data to file""" + data_file = self.data_dir / "performance_data.json" + + try: + with open(data_file, 'w') as f: + json.dump(self.metrics, f, indent=2) + except Exception as e: + print(f"Warning: Could not save performance data: {e}") + + def _start_monitoring(self): + """Start background monitoring thread""" + def monitor(): + while True: + time.sleep(60) # Collect data every minute + self._collect_system_stats() + self._save_data() + + thread = threading.Thread(target=monitor, daemon=True) + thread.start() + + def _collect_system_stats(self): + """Collect system statistics""" + try: + import psutil + + stats = { + "timestamp": datetime.now().isoformat(), + "cpu_percent": psutil.cpu_percent(interval=1), + "memory_percent": psutil.virtual_memory().percent, + "disk_usage": psutil.disk_usage('/').percent, + "network_connections": len(psutil.net_connections()) + } + + self.metrics["system_stats"].append(stats) + + # Keep only last 1000 entries + if len(self.metrics["system_stats"]) > 1000: + self.metrics["system_stats"] = self.metrics["system_stats"][-1000:] + + except ImportError: + # psutil not available, skip system stats + pass + except Exception as e: + print(f"Warning: Could not collect system stats: {e}") + + def record_request(self, endpoint: str, method: str, status_code: int, duration: float): + """Record an API request""" + request = { + "timestamp": datetime.now().isoformat(), + "endpoint": endpoint, + "method": method, + "status_code": status_code, + "duration": duration + } + + self.metrics["requests"].append(request) + + # Keep only last 10000 requests + if len(self.metrics["requests"]) > 10000: + self.metrics["requests"] = self.metrics["requests"][-10000:] + + def record_error(self, error_type: str, error_message: str, context: Optional[str] = None): + """Record an error""" + error = { + "timestamp": datetime.now().isoformat(), + "type": error_type, + "message": error_message, + "context": context + } + + self.metrics["errors"].append(error) + + # Keep only last 1000 errors + if len(self.metrics["errors"]) > 1000: + self.metrics["errors"] = self.metrics["errors"][-1000:] + + def record_response_time(self, operation: str, duration: float): + """Record response time for an operation""" + response_time = { + "timestamp": datetime.now().isoformat(), + "operation": operation, + "duration": duration + } + + self.metrics["response_times"].append(response_time) + + # Keep only last 5000 response times + if len(self.metrics["response_times"]) > 5000: + self.metrics["response_times"] = self.metrics["response_times"][-5000:] + + def record_tool_usage(self, tool_name: str, success: bool, duration: float): + """Record tool usage""" + if tool_name not in self.metrics["tool_usage"]: + self.metrics["tool_usage"][tool_name] = { + "total_calls": 0, + "successful_calls": 0, + "failed_calls": 0, + "total_duration": 0, + "avg_duration": 0 + } + + tool_stats = self.metrics["tool_usage"][tool_name] + tool_stats["total_calls"] += 1 + tool_stats["total_duration"] += duration + + if success: + tool_stats["successful_calls"] += 1 + else: + tool_stats["failed_calls"] += 1 + + tool_stats["avg_duration"] = tool_stats["total_duration"] / tool_stats["total_calls"] + + def record_provider_usage(self, provider: str, model: str, success: bool): + """Record provider usage""" + key = f"{provider}:{model}" + + if key not in self.metrics["provider_usage"]: + self.metrics["provider_usage"][key] = { + "total_calls": 0, + "successful_calls": 0, + "failed_calls": 0 + } + + provider_stats = self.metrics["provider_usage"][key] + provider_stats["total_calls"] += 1 + + if success: + provider_stats["successful_calls"] += 1 + else: + provider_stats["failed_calls"] += 1 + + def get_summary(self, hours: int = 24) -> Dict[str, Any]: + """ + Get performance summary for the last N hours + + Args: + hours: Number of hours to include in summary + + Returns: + Dictionary with performance summary + """ + cutoff_time = datetime.now() - timedelta(hours=hours) + + # Filter recent data + recent_requests = [ + r for r in self.metrics["requests"] + if datetime.fromisoformat(r["timestamp"]) > cutoff_time + ] + + recent_errors = [ + e for e in self.metrics["errors"] + if datetime.fromisoformat(e["timestamp"]) > cutoff_time + ] + + recent_response_times = [ + rt for rt in self.metrics["response_times"] + if datetime.fromisoformat(rt["timestamp"]) > cutoff_time + ] + + # Calculate statistics + total_requests = len(recent_requests) + total_errors = len(recent_errors) + + if total_requests > 0: + error_rate = total_errors / total_requests + avg_response_time = sum(r["duration"] for r in recent_requests) / total_requests + else: + error_rate = 0 + avg_response_time = 0 + + # Status code distribution + status_codes = {} + for request in recent_requests: + code = request["status_code"] + status_codes[code] = status_codes.get(code, 0) + 1 + + # Endpoint usage + endpoints = {} + for request in recent_requests: + endpoint = request["endpoint"] + endpoints[endpoint] = endpoints.get(endpoint, 0) + 1 + + # Tool usage summary + tool_summary = {} + for tool_name, stats in self.metrics["tool_usage"].items(): + tool_summary[tool_name] = { + "total_calls": stats["total_calls"], + "success_rate": stats["successful_calls"] / stats["total_calls"] if stats["total_calls"] > 0 else 0, + "avg_duration": stats["avg_duration"] + } + + # Provider usage summary + provider_summary = {} + for provider_key, stats in self.metrics["provider_usage"].items(): + provider_summary[provider_key] = { + "total_calls": stats["total_calls"], + "success_rate": stats["successful_calls"] / stats["total_calls"] if stats["total_calls"] > 0 else 0 + } + + # System stats (if available) + system_stats = {} + if self.metrics["system_stats"]: + latest_stats = self.metrics["system_stats"][-1] + system_stats = { + "cpu_percent": latest_stats.get("cpu_percent", 0), + "memory_percent": latest_stats.get("memory_percent", 0), + "disk_usage": latest_stats.get("disk_usage", 0) + } + + return { + "period_hours": hours, + "total_requests": total_requests, + "total_errors": total_errors, + "error_rate": error_rate, + "avg_response_time": avg_response_time, + "status_codes": status_codes, + "endpoints": endpoints, + "tool_usage": tool_summary, + "provider_usage": provider_summary, + "system_stats": system_stats, + "generated_at": datetime.now().isoformat() + } + + def get_detailed_report(self, hours: int = 24) -> str: + """ + Get detailed performance report + + Args: + hours: Number of hours to include in report + + Returns: + Formatted report string + """ + summary = self.get_summary(hours) + + report = f"๐Ÿ“Š Performance Report (Last {hours} hours)\n" + report += "=" * 50 + "\n\n" + + # Request statistics + report += f"๐Ÿ“ˆ Request Statistics:\n" + report += f" Total Requests: {summary['total_requests']}\n" + report += f" Total Errors: {summary['total_errors']}\n" + report += f" Error Rate: {summary['error_rate']:.2%}\n" + report += f" Avg Response Time: {summary['avg_response_time']:.3f}s\n\n" + + # Status codes + if summary['status_codes']: + report += f"๐Ÿ“‹ Status Code Distribution:\n" + for code, count in sorted(summary['status_codes'].items()): + report += f" {code}: {count}\n" + report += "\n" + + # Top endpoints + if summary['endpoints']: + report += f"๐Ÿ”— Top Endpoints:\n" + sorted_endpoints = sorted(summary['endpoints'].items(), key=lambda x: x[1], reverse=True)[:10] + for endpoint, count in sorted_endpoints: + report += f" {endpoint}: {count}\n" + report += "\n" + + # Tool usage + if summary['tool_usage']: + report += f"๐Ÿ”ง Tool Usage:\n" + for tool_name, stats in summary['tool_usage'].items(): + report += f" {tool_name}:\n" + report += f" Calls: {stats['total_calls']}\n" + report += f" Success Rate: {stats['success_rate']:.2%}\n" + report += f" Avg Duration: {stats['avg_duration']:.3f}s\n" + report += "\n" + + # Provider usage + if summary['provider_usage']: + report += f"๐Ÿค– Provider Usage:\n" + for provider_key, stats in summary['provider_usage'].items(): + report += f" {provider_key}:\n" + report += f" Calls: {stats['total_calls']}\n" + report += f" Success Rate: {stats['success_rate']:.2%}\n" + report += "\n" + + # System stats + if summary['system_stats']: + report += f"๐Ÿ’ป System Statistics:\n" + report += f" CPU Usage: {summary['system_stats']['cpu_percent']}%\n" + report += f" Memory Usage: {summary['system_stats']['memory_percent']}%\n" + report += f" Disk Usage: {summary['system_stats']['disk_usage']}%\n" + + return report + + def clear_old_data(self, days: int = 30): + """ + Clear data older than specified days + + Args: + days: Number of days to keep data + """ + cutoff_time = datetime.now() - timedelta(days=days) + + # Filter out old data + self.metrics["requests"] = [ + r for r in self.metrics["requests"] + if datetime.fromisoformat(r["timestamp"]) > cutoff_time + ] + + self.metrics["errors"] = [ + e for e in self.metrics["errors"] + if datetime.fromisoformat(e["timestamp"]) > cutoff_time + ] + + self.metrics["response_times"] = [ + rt for rt in self.metrics["response_times"] + if datetime.fromisoformat(rt["timestamp"]) > cutoff_time + ] + + self.metrics["system_stats"] = [ + s for s in self.metrics["system_stats"] + if datetime.fromisoformat(s["timestamp"]) > cutoff_time + ] + + # Save cleaned data + self._save_data() + + print(f"โœ… Cleared data older than {days} days") + + +# Global performance dashboard instance +performance_dashboard = PerformanceDashboard() + + +def get_performance_summary(hours: int = 24) -> Dict[str, Any]: + """ + Get performance summary + + Args: + hours: Number of hours to include + + Returns: + Performance summary dictionary + """ + return performance_dashboard.get_summary(hours) + + +def get_performance_report(hours: int = 24) -> str: + """ + Get formatted performance report + + Args: + hours: Number of hours to include + + Returns: + Formatted report string + """ + return performance_dashboard.get_detailed_report(hours) + + +if __name__ == "__main__": + # Print performance report when run directly + print(get_performance_report()) \ No newline at end of file