-
-
Notifications
You must be signed in to change notification settings - Fork 50
feat(monitor): real-time system resource monitoring during installations #617
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4b4a6ad
585e344
a738324
29b495b
65dad2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any | ||
|
|
||
| import psutil | ||
| from rich.markdown import Markdown | ||
|
|
||
| from cortex.api_key_detector import auto_detect_api_key, setup_api_key | ||
|
|
@@ -24,6 +25,8 @@ | |
| from cortex.env_manager import EnvironmentManager, get_env_manager | ||
| from cortex.installation_history import InstallationHistory, InstallationStatus, InstallationType | ||
| from cortex.llm.interpreter import CommandInterpreter | ||
| from cortex.monitor.live_monitor_ui import MonitorUI | ||
| from cortex.monitor.resource_monitor import ResourceMonitor | ||
| from cortex.network_config import NetworkConfig | ||
| from cortex.notification_manager import NotificationManager | ||
| from cortex.role_manager import RoleManager | ||
|
|
@@ -58,6 +61,123 @@ def __init__(self, verbose: bool = False): | |
| self.spinner_idx = 0 | ||
| self.verbose = verbose | ||
|
|
||
| def monitor(self, args: argparse.Namespace) -> int: | ||
| """Show current system resource usage.""" | ||
| resource_monitor = ResourceMonitor(interval=1.0) | ||
| duration = getattr(args, "duration", None) | ||
|
|
||
| console.print("System Health:") | ||
|
|
||
| metrics = self._collect_monitoring_metrics(resource_monitor, duration) | ||
| if metrics: | ||
| console.print(MonitorUI.format_system_health(metrics)) | ||
|
|
||
| self._display_alerts(metrics) | ||
|
|
||
| export_result = self._handle_monitor_export(resource_monitor, args) | ||
| if export_result != 0: | ||
| return export_result | ||
|
|
||
| self._display_recommendations(resource_monitor) | ||
| return 0 | ||
|
|
||
| def _display_recommendations(self, resource_monitor: ResourceMonitor) -> None: | ||
| """Display performance recommendations.""" | ||
| if not resource_monitor.history or len(resource_monitor.history) <= 1: | ||
| return | ||
|
|
||
| recommendations = resource_monitor.get_recommendations() | ||
| if recommendations: | ||
| console.print("\n[bold cyan]⚡ Performance Recommendations:[/bold cyan]") | ||
| for rec in recommendations: | ||
| console.print(f" • {rec}") | ||
|
|
||
| def _collect_monitoring_metrics( | ||
| self, resource_monitor: ResourceMonitor, duration: float | None | ||
| ) -> dict[str, Any] | None: | ||
| """Collect monitoring metrics based on duration.""" | ||
|
|
||
| if duration: | ||
| # Run monitoring loop for the given duration | ||
| resource_monitor.monitor(duration) | ||
|
|
||
| # Show final snapshot after monitoring | ||
| summary = resource_monitor.get_summary() | ||
| if summary: | ||
| return summary["current"] | ||
| else: | ||
| console.print("[yellow]No monitoring data collected.[/yellow]") | ||
| return None | ||
| else: | ||
| return resource_monitor.sample() | ||
|
|
||
| def _display_alerts(self, metrics: dict[str, Any] | None) -> None: | ||
| """Display alerts from metrics.""" | ||
| if not metrics: | ||
| return | ||
|
|
||
| alerts = metrics.get("alerts", []) | ||
| if alerts: | ||
| console.print("\n[bold yellow]⚠️ Alerts:[/bold yellow]") | ||
| for alert in alerts: | ||
| console.print(f" • {alert}") | ||
|
|
||
| def _handle_monitor_export( | ||
| self, resource_monitor: ResourceMonitor, args: argparse.Namespace | ||
| ) -> int: | ||
| """Handle export of monitoring data.""" | ||
| if not getattr(args, "export", None): | ||
| return 0 | ||
|
|
||
| filename = self._export_monitor_data( | ||
| monitor=resource_monitor, | ||
| export=args.export, | ||
| output=args.output, | ||
| ) | ||
|
|
||
| if filename: | ||
| cx_print(f"✓ Monitoring data exported to {filename}", "success") | ||
| return 0 | ||
| else: | ||
| self._print_error("Failed to export monitoring data") | ||
| return 1 | ||
|
|
||
| def _display_recommendations(self, resource_monitor: ResourceMonitor) -> None: | ||
| """Display performance recommendations.""" | ||
| if not resource_monitor.history or len(resource_monitor.history) <= 1: | ||
| return | ||
|
|
||
| recommendations = resource_monitor.get_recommendations() | ||
| if recommendations: | ||
| console.print("\n[bold cyan]⚡ Performance Recommendations:[/bold cyan]") | ||
| for rec in recommendations: | ||
| console.print(f" • {rec}") | ||
|
|
||
| # MONITOR HELPERS | ||
| def _get_latest_metrics(self, monitor: ResourceMonitor) -> dict: | ||
| """Return latest collected metrics or take a fresh sample.""" | ||
| return monitor.history[-1] if monitor.history else monitor.sample() | ||
|
Comment on lines
+157
to
+159
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refresh metrics instead of reusing stale samples. Once history is non-empty, 🛠️ Proposed fix- def _get_latest_metrics(self, monitor: ResourceMonitor) -> dict:
+ def _get_latest_metrics(self, monitor: ResourceMonitor) -> dict[str, Any]:
"""Return latest collected metrics or take a fresh sample."""
- return monitor.history[-1] if monitor.history else monitor.sample()
+ if not monitor.history:
+ return monitor.sample()
+ last = monitor.history[-1]
+ if time.time() - last.get("timestamp", 0) >= monitor.interval:
+ return monitor.sample()
+ return lastAlso applies to: 1039-1046 🤖 Prompt for AI Agents |
||
|
|
||
| def _export_monitor_data( | ||
| self, | ||
| monitor: ResourceMonitor, | ||
| export: str, | ||
| output: str | None, | ||
| software: str | None = None, | ||
| ) -> str | None: | ||
| """Export monitoring data safely.""" | ||
| from cortex.monitor import export_monitoring_data | ||
|
|
||
| if output: | ||
| filename = f"{output}.{export}" | ||
| else: | ||
| safe_name = "".join(c if c.isalnum() else "_" for c in (software or "monitor")) | ||
| filename = f"{safe_name}_monitoring.{export}" | ||
|
|
||
| if export_monitoring_data(monitor, export, filename): # Check if successful | ||
| return filename # Return filename on success | ||
| return None # Return None on failure | ||
|
|
||
| # Define a method to handle Docker-specific permission repairs | ||
| def docker_permissions(self, args: argparse.Namespace) -> int: | ||
| """Handle the diagnosis and repair of Docker file permissions. | ||
|
|
@@ -817,7 +937,23 @@ def install( | |
| execute: bool = False, | ||
| dry_run: bool = False, | ||
| parallel: bool = False, | ||
| monitor: bool = False, | ||
| export: str | None = None, | ||
| output: str | None = None, | ||
| ): | ||
|
|
||
| # If --monitor is used, automatically enable execution and initialize the resource monitor. | ||
| resource_monitor = None | ||
| if monitor and not execute and not dry_run: | ||
| print(f"📊 Monitoring enabled for: {software}") | ||
| print("Note: Monitoring requires execution. Auto-enabling --execute flag.") | ||
| execute = True | ||
|
|
||
|
Comment on lines
+945
to
+951
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t auto‑enable execution when Auto‑enabling execution bypasses the dry‑run default and may run installs unintentionally. Require explicit 🛠️ Proposed fix resource_monitor = None
if monitor and not execute and not dry_run:
- print(f"📊 Monitoring enabled for: {software}")
- print("Note: Monitoring requires execution. Auto-enabling --execute flag.")
- execute = True
+ self._print_error("Monitoring requires --execute. Re-run with --execute or --dry-run.")
+ return 1🤖 Prompt for AI Agents |
||
| if monitor: | ||
| resource_monitor = ResourceMonitor(interval=1.0) | ||
| console.print(f"Installing {software}...") # Simple print | ||
| cx_print("📊 Monitoring system resources during installation...", "info") | ||
|
|
||
| # Validate input first | ||
| is_valid, error = validate_install_request(software) | ||
| if not is_valid: | ||
|
|
@@ -900,6 +1036,22 @@ def progress_callback(current, total, step): | |
| print(f"\n[{current}/{total}] {status_emoji} {step.description}") | ||
| print(f" Command: {step.command}") | ||
|
|
||
| # Samples current system resources during each install step and displays live metrics. | ||
| if resource_monitor: | ||
| metrics = self._get_latest_metrics(resource_monitor) | ||
| if current == 1 or "compil" in step.description.lower(): | ||
| from cortex.monitor.live_monitor_ui import MonitorUI | ||
|
|
||
| installation_display = MonitorUI.format_installation_metrics(metrics) | ||
| console.print("\n" + installation_display) | ||
|
|
||
| # Display alerts if any | ||
| alerts = metrics.get("alerts", []) | ||
| if alerts: | ||
| console.print("\n[yellow]⚠️ Resource Alert:[/yellow]") | ||
| for alert in alerts: | ||
| console.print(f" • {alert}") | ||
|
|
||
| print("\nExecuting commands...") | ||
|
|
||
| if parallel: | ||
|
|
@@ -1003,6 +1155,39 @@ def parallel_log_callback(message: str, level: str = "info"): | |
| self._print_success(f"{software} installed successfully!") | ||
| print(f"\nCompleted in {result.total_duration:.2f} seconds") | ||
|
|
||
| # Displays the highest CPU and memory usage recorded during the installation. | ||
| if monitor and resource_monitor: | ||
| summary = resource_monitor.get_summary() | ||
| peak = summary.get("peak", {}) | ||
|
|
||
| from cortex.monitor.live_monitor_ui import MonitorUI | ||
|
|
||
| peak_display = MonitorUI.format_peak_usage(peak) | ||
| console.print("\n" + peak_display) | ||
|
|
||
| # Display performance recommendation | ||
| recommendations = resource_monitor.get_recommendations() | ||
| if recommendations: | ||
| console.print( | ||
| "\n[bold cyan]⚡ Performance Recommendations:[/bold cyan]" | ||
| ) | ||
| for rec in recommendations: | ||
| console.print(f" • {rec}") | ||
|
|
||
| # Export if requested | ||
| if export: | ||
| filename = self._export_monitor_data( | ||
| monitor=resource_monitor, | ||
| export=export, | ||
| output=output, | ||
| software=software, | ||
| ) | ||
|
|
||
| if filename: | ||
| cx_print(f"✓ Monitoring data exported to {filename}", "success") | ||
| else: | ||
| self._print_error("Failed to export monitoring data") | ||
|
|
||
| # Record successful installation | ||
| if install_id: | ||
| history.update_installation(install_id, InstallationStatus.SUCCESS) | ||
|
|
@@ -1271,13 +1456,6 @@ def _display_summary_table(self, result, style: str, table_class) -> None: | |
| console.print("\n[bold]📊 Impact Summary:[/bold]") | ||
| console.print(summary_table) | ||
|
|
||
| def _display_recommendations(self, recommendations: list) -> None: | ||
| """Display recommendations.""" | ||
| if recommendations: | ||
| console.print("\n[bold green]💡 Recommendations:[/bold green]") | ||
| for rec in recommendations: | ||
| console.print(f" • {rec}") | ||
|
|
||
| def _execute_removal(self, package: str, purge: bool = False) -> int: | ||
| """Execute the actual package removal with audit logging""" | ||
| import datetime | ||
|
|
@@ -2965,6 +3143,30 @@ def main(): | |
| # Demo command | ||
| demo_parser = subparsers.add_parser("demo", help="See Cortex in action") | ||
|
|
||
| # Monitor command | ||
| monitor_parser = subparsers.add_parser( | ||
| "monitor", | ||
| help="Show real-time system resource usage", | ||
| ) | ||
|
|
||
| monitor_parser.add_argument( | ||
| "--export", | ||
| choices=["json", "csv"], | ||
| help="Export monitoring data to a file", | ||
| ) | ||
|
|
||
| monitor_parser.add_argument( | ||
| "--output", | ||
| default="monitoring_data", | ||
| help="Output filename (without extension)", | ||
| ) | ||
|
|
||
| monitor_parser.add_argument( | ||
| "--duration", | ||
| type=float, | ||
| help="Monitor for specified duration in seconds", | ||
| ) | ||
|
|
||
| # Wizard command | ||
| wizard_parser = subparsers.add_parser("wizard", help="Configure API key interactively") | ||
|
|
||
|
|
@@ -3067,6 +3269,21 @@ def main(): | |
| action="store_true", | ||
| help="Output impact analysis as JSON", | ||
| ) | ||
| install_parser.add_argument( | ||
| "--monitor", | ||
| action="store_true", | ||
| help="Monitor system resources during installation", | ||
| ) | ||
| install_parser.add_argument( | ||
| "--export", | ||
| choices=["json", "csv"], | ||
| help="Export monitoring data to a file (requires --monitor)", | ||
| ) | ||
| install_parser.add_argument( | ||
| "--output", | ||
| default="installation_monitoring", | ||
| help="Output filename (without extension, used with --export)", | ||
| ) | ||
|
|
||
| # Import command - import dependencies from package manager files | ||
| import_parser = subparsers.add_parser( | ||
|
|
@@ -3566,6 +3783,8 @@ def main(): | |
|
|
||
| if args.command == "demo": | ||
| return cli.demo() | ||
| elif args.command == "monitor": | ||
| return cli.monitor(args) | ||
| elif args.command == "wizard": | ||
| return cli.wizard() | ||
| elif args.command == "status": | ||
|
|
@@ -3596,6 +3815,9 @@ def main(): | |
| execute=args.execute, | ||
| dry_run=args.dry_run, | ||
| parallel=args.parallel, | ||
| monitor=args.monitor, | ||
| export=args.export, | ||
| output=args.output, | ||
| ) | ||
| elif args.command == "remove": | ||
| # Handle --execute flag to override default dry-run | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| from .exporter import ( | ||
| export_monitoring_data, | ||
| export_to_csv, | ||
| export_to_json, | ||
| ) | ||
| from .live_monitor_ui import LiveMonitorUI, MonitorUI | ||
| from .resource_monitor import ResourceMonitor | ||
|
|
||
| __all__ = [ | ||
| "ResourceMonitor", | ||
| "MonitorUI", | ||
| "LiveMonitorUI", | ||
| "export_to_csv", | ||
| "export_to_json", | ||
| "export_monitoring_data", | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add audit logging for
cortex monitor.The new monitor command doesn’t record an audit entry. Please log this operation to the history DB so it’s traceable.
🛠️ Example (using existing history API)
resource_monitor = ResourceMonitor(interval=1.0) duration = getattr(args, "duration", None) + + history = InstallationHistory() + history.record_installation( + InstallationType.CONFIG, + ["monitor"], + [f"cortex monitor --duration {duration}" if duration else "cortex monitor"], + datetime.now(timezone.utc), + )🤖 Prompt for AI Agents