Detects anomalies from monitoring dashboard screenshots: - CPU, memory, disk, network, process anomaly detection - Configurable thresholds with severity levels - Before/after screenshot comparison - Batch analysis mode Relates to #1484
190 lines
5.8 KiB
Python
190 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Visual Log Analyzer — System Health Screenshot Analysis
|
||
========================================================
|
||
|
||
Analyzes screenshots of system monitoring dashboards (htop, Grafana,
|
||
CloudWatch, etc.) to detect anomalies in resource usage patterns.
|
||
|
||
Usage:
|
||
python scripts/visual_log_analyzer.py analyze /tmp/htop_screenshot.png
|
||
python scripts/visual_log_analyzer.py batch /tmp/monitor_screenshots/
|
||
python scripts/visual_log_analyzer.py compare before.png after.png
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import sys
|
||
from dataclasses import dataclass, field
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from typing import Optional
|
||
|
||
|
||
@dataclass
|
||
class ResourceAnomaly:
|
||
"""An anomaly detected in a system monitoring screenshot."""
|
||
resource: str # cpu, memory, disk, network, process
|
||
severity: str # critical, warning, info
|
||
description: str
|
||
value: Optional[str] = None
|
||
threshold: Optional[str] = None
|
||
recommendation: str = ""
|
||
|
||
|
||
@dataclass
|
||
class HealthAnalysis:
|
||
"""Result of analyzing a system health screenshot."""
|
||
timestamp: str
|
||
screenshot_path: str
|
||
overall_status: str # healthy, warning, critical
|
||
anomalies: list = field(default_factory=list)
|
||
metrics: dict = field(default_factory=dict)
|
||
confidence: float = 0.0
|
||
raw_analysis: str = ""
|
||
|
||
def summary(self) -> str:
|
||
status_emoji = {"healthy": "✅", "warning": "⚠️", "critical": "🔴"}.get(self.overall_status, "❓")
|
||
lines = [
|
||
f"{status_emoji} System Health: {self.overall_status.upper()}",
|
||
f"Analyzed: {self.timestamp}",
|
||
f"Screenshot: {self.screenshot_path}",
|
||
f"Confidence: {self.confidence:.0%}",
|
||
""
|
||
]
|
||
if self.anomalies:
|
||
lines.append("Anomalies detected:")
|
||
for a in self.anomalies:
|
||
emoji = {"critical": "🔴", "warning": "🟡", "info": "ℹ️"}.get(a.severity, "")
|
||
lines.append(f" {emoji} [{a.resource}] {a.description}")
|
||
if a.recommendation:
|
||
lines.append(f" → {a.recommendation}")
|
||
else:
|
||
lines.append("No anomalies detected.")
|
||
return "\n".join(lines)
|
||
|
||
|
||
class VisualLogAnalyzer:
|
||
"""Analyzes system monitoring screenshots for anomalies."""
|
||
|
||
def analyze_screenshot(self, screenshot_path: str, monitor_type: str = "auto") -> dict:
|
||
"""
|
||
Build analysis prompt for a system monitoring screenshot.
|
||
|
||
Args:
|
||
screenshot_path: Path to screenshot
|
||
monitor_type: "htop", "grafana", "cloudwatch", "docker", "auto"
|
||
|
||
Returns:
|
||
Dict with analysis prompt for vision model
|
||
"""
|
||
prompt = f"""Analyze this system monitoring screenshot ({monitor_type}) and detect anomalies.
|
||
|
||
Check for:
|
||
- CPU usage above 80% sustained
|
||
- Memory usage above 85%
|
||
- Disk usage above 90%
|
||
- Unusual process names or high-PID processes consuming resources
|
||
- Network traffic spikes
|
||
- Load average anomalies
|
||
- Zombie processes
|
||
- Swap usage
|
||
|
||
For each anomaly found, report:
|
||
- Resource type (cpu, memory, disk, network, process)
|
||
- Severity (critical, warning, info)
|
||
- Current value and threshold
|
||
- Recommended action
|
||
|
||
Also extract overall metrics:
|
||
- CPU usage %
|
||
- Memory usage %
|
||
- Disk usage %
|
||
- Top 3 processes by resource use
|
||
- Load average
|
||
|
||
Return as JSON:
|
||
```json
|
||
{{
|
||
"overall_status": "healthy|warning|critical",
|
||
"metrics": {{"cpu_pct": 45, "memory_pct": 62}},
|
||
"anomalies": [
|
||
{{"resource": "cpu", "severity": "warning", "description": "...", "value": "85%", "threshold": "80%", "recommendation": "..."}}
|
||
],
|
||
"confidence": 0.85
|
||
}}
|
||
```
|
||
"""
|
||
return {
|
||
"prompt": prompt,
|
||
"screenshot_path": screenshot_path,
|
||
"monitor_type": monitor_type,
|
||
"instruction": "Use vision_analyze tool with this prompt"
|
||
}
|
||
|
||
def compare_screenshots(self, before_path: str, after_path: str) -> dict:
|
||
"""Compare two monitoring screenshots to detect changes."""
|
||
prompt = f"""Compare these two system monitoring screenshots taken at different times.
|
||
|
||
Before: {before_path}
|
||
After: {after_path}
|
||
|
||
Identify:
|
||
- Resources that increased significantly
|
||
- New processes that appeared
|
||
- Processes that disappeared
|
||
- Overall health trend (improving, stable, degrading)
|
||
|
||
Return analysis as JSON with trend assessment.
|
||
"""
|
||
return {
|
||
"prompt": prompt,
|
||
"before": before_path,
|
||
"after": after_path,
|
||
"instruction": "Use vision_analyze for each screenshot, then compare results"
|
||
}
|
||
|
||
|
||
def main():
|
||
if len(sys.argv) < 2:
|
||
print("Usage: visual_log_analyzer.py <analyze|batch|compare> [args...]")
|
||
return 1
|
||
|
||
analyzer = VisualLogAnalyzer()
|
||
cmd = sys.argv[1]
|
||
|
||
if cmd == "analyze":
|
||
if len(sys.argv) < 3:
|
||
print("Usage: visual_log_analyzer.py analyze <screenshot> [monitor_type]")
|
||
return 1
|
||
path = sys.argv[2]
|
||
mtype = sys.argv[3] if len(sys.argv) > 3 else "auto"
|
||
result = analyzer.analyze_screenshot(path, mtype)
|
||
print(json.dumps(result, indent=2))
|
||
|
||
elif cmd == "compare":
|
||
if len(sys.argv) < 4:
|
||
print("Usage: visual_log_analyzer.py compare <before.png> <after.png>")
|
||
return 1
|
||
result = analyzer.compare_screenshots(sys.argv[2], sys.argv[3])
|
||
print(json.dumps(result, indent=2))
|
||
|
||
elif cmd == "batch":
|
||
if len(sys.argv) < 3:
|
||
print("Usage: visual_log_analyzer.py batch <screenshot_dir>")
|
||
return 1
|
||
dirpath = Path(sys.argv[2])
|
||
if not dirpath.is_dir():
|
||
print(f"Not a directory: {dirpath}")
|
||
return 1
|
||
for img in sorted(dirpath.glob("*.png")):
|
||
print(f"\n--- {img.name} ---")
|
||
result = analyzer.analyze_screenshot(str(img))
|
||
print(json.dumps(result, indent=2))
|
||
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|