diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..20261d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +*.pyc + +# Virtual Environment +venv/ +ENV/ +env/ +.venv + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.project +.pydevproject +.settings/ +*.sublime-project +*.sublime-workspace + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +*.bak + +# Logs +*.log +*.log.* +logs/ +/tmp/ +*.tmp + +# Monitoring data +/data/ +*.json.bak +power_monitoring.json +power_monitoring.log +claude_power_metrics.log +monitoring_service.log + +# Test outputs +.pytest_cache/ +.coverage +htmlcov/ +*.cover +.hypothesis/ +.tox/ + +# Documentation builds +docs/_build/ +site/ + +# Backups +*.backup +*.backup.* +*.old + +# Local configuration +.env +.env.local +local_config.py +config.local.conf + +# Compiled binaries +*.o +*.ko +*.obj +*.elf +*.out +*.app + +# Temporary files +.cache/ +temp/ +tmp/ + +# User-specific +.mypy_cache/ +.dmypy.json +dmypy.json +.pyre/ +.pytype/ + +# Integration-specific +integrations/*.backup.* +dmenu-launcher.sh.backup* diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a63dc..8e71c41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,178 @@ # Changelog +All notable changes to PowerManagement will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [3.1.0] - 2025-11-18 + +### Added - Major Feature Update + +#### GPU Monitoring & Control +- **Universal GPU Monitor** - Real-time GPU monitoring for NVIDIA, AMD, and Intel GPUs +- **GPU Temperature** - Track GPU core temperature with alerts +- **GPU Fan Control** - Control NVIDIA and AMD GPU fan speeds +- **GPU Power Monitoring** - Track power consumption and limits +- **Multi-GPU Support** - Monitor and control multiple GPUs simultaneously + +#### Advanced Sensor Detection +- **Universal Sensor Detector** - Comprehensive sensor detection across all hardware +- **Multiple Detection Methods** - lm-sensors, sysfs hwmon, thermal zones, ACPI +- **Atypical Hardware Support** - Works on all-in-one PCs (Acer, Dell, etc.) +- **40+ Sensor Types** - Temperature, fan, voltage, power, current sensors +- **Robust Detection** - Graceful fallbacks for difficult motherboards + +#### Fan Control System +- **PWM Fan Control** - Direct hardware fan control via Linux sysfs +- **Auto Fan Mode** - Temperature-based automatic fan adjustment +- **Manual Control** - Set specific fan speeds (0-100%) +- **Multi-Fan Support** - Control CPU, case, and GPU fans independently +- **Safety Limits** - Minimum speed enforcement to prevent overheating + +#### Professional Monitoring Service +- **Real-time Monitoring Daemon** - Continuous background monitoring +- **Thermal Alerts** - Automatic alerts on critical temperatures +- **JSON Logging** - Machine-readable metrics logging +- **Configurable Intervals** - Adjust monitoring frequency +- **Service Management** - Easy start/stop/status commands + +#### Project Integrations +- **MyMenu Integration** - dmenu launcher interface with automatic patching +- **claude-tools-monitor Integration** - Thermal-aware AI session monitoring +- **Unified Dashboard** - Combined monitoring across all projects +- **Integration Documentation** - Complete integration guides + +### Enhanced +- **Documentation** - Added SENSOR_MONITORING.md with comprehensive sensor guide +- **Documentation** - Added INTEGRATIONS.md with integration examples +- **Documentation** - Added INTEGRATION_PLAN.md with roadmap +- **Test Suite** - Comprehensive 40+ test suite for all new features +- **README** - Added integrations section with quick start guides + +### Fixed +- Sensor detection on atypical motherboards +- All-in-one PC compatibility issues +- Missing sensor values handled gracefully + +## [3.0.0] - 2025-11-17 + +### Added - Universal Hardware Support + +#### CPU Support +- **Universal CPU Detection** - Auto-detect Intel and AMD CPUs across generations +- **Intel Support** - Core 2 Quad, Nehalem, Sandy Bridge, Haswell, Skylake+ +- **AMD Support** - K8 (Phenom), K10, Bulldozer/Piledriver, Zen (Ryzen) +- **Adaptive Thermal Management** - CPU-specific temperature thresholds +- **Frequency Scaling** - Multi-method frequency control (cpufreq, MSR, cpupower) + +#### Hardware Detection +- **Hardware Detector** - Automatic CPU/GPU vendor and generation detection +- **Thermal Profiles** - CPU-specific thermal limits and safe ranges +- **Feature Detection** - MSR support, turbo boost, P-states + +#### Configuration System +- **Dynamic Path Resolution** - No more hardcoded paths +- **Power Configuration** - Centralized configuration management +- **Portable Installation** - Install anywhere, works from any directory + +### Changed +- **Removed Hardcoded Paths** - All `/home/milhy777/` paths replaced with dynamic detection +- **Universal GPU Detection** - Auto-detect AMD, NVIDIA, Intel GPUs +- **Refactored Daemon** - Uses new configuration system +- **Refactored Scripts** - All scripts use dynamic paths + +### Fixed +- Hardcoded path issues in daemon (`custom-power-profiles-daemon.py`) +- Hardcoded path in `performance_manager.sh` (line 305-307) +- Hardcoded path in `ai_process_manager.sh` (line 30) +- GPU card hardcoded to card1 - now auto-detects card0-9 +- Dead code removed (claude --agent calls, lines 253-266) + +### Enhanced +- **Documentation** - Added UNIVERSAL_HARDWARE.md with compatibility guide +- **Documentation** - Updated README with v3.0 features +- **Installation** - One-click install script +- **Testing** - Comprehensive test suite + +## [2.0.0] - Previous Version + +### Features +- Basic power profile management (Performance, Balanced, Power Save, Emergency) +- CPU frequency control via MSR registers +- GPU power profile control +- Thermal monitoring and response +- AI process management with thermal protection +- D-Bus power profiles daemon +- System monitoring scripts + +### Hardware +- Optimized for Intel Core 2 Quad Q9550 +- Basic AMD/NVIDIA GPU support + +## [1.0.0] - Initial Release + +### Features +- Manual CPU frequency control +- Basic power profiles +- Simple GPU control +- Emergency cleanup tools + +--- + +## Version Guidelines + +### Major Version (X.0.0) +- Breaking changes to API or configuration +- Major architectural changes +- Removal of deprecated features + +### Minor Version (0.X.0) +- New features (backward compatible) +- New hardware support +- New integrations + +### Patch Version (0.0.X) +- Bug fixes +- Documentation updates +- Performance improvements + +--- + +## Upgrade Notes + +### Upgrading to 3.1.0 +- No breaking changes +- New optional features (GPU monitoring, fan control, monitoring service) +- Existing configurations remain compatible +- New integrations are opt-in + +### Upgrading to 3.0.0 +- **Breaking**: Hardcoded paths removed - reinstall required +- **Breaking**: Old configuration files may need updates +- **Migration**: Run `install.sh` to update paths +- **Benefit**: Works on any hardware, install anywhere + +--- + +## Future Roadmap + +See [INTEGRATION_PLAN.md](docs/INTEGRATION_PLAN.md) for detailed roadmap. + +### Planned Features +- Web-based dashboard +- Prometheus exporter for metrics +- Systemd unit files for monitoring service +- Polybar/i3status integration +- Home Assistant integration +- More GPU vendor support (PowerVR, Mali, etc.) + +### Under Consideration +- Laptop battery optimization profiles +- Network-based remote monitoring +- Historical metrics database +- Machine learning thermal predictions +- Custom fan curves per application ## [2.0.0] - 2025-01-20 - Installation script - Configuration file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c45694f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,445 @@ +# Contributing to PowerManagement + +Thank you for your interest in contributing to PowerManagement! This document provides guidelines and instructions for contributing. + +## ๐Ÿ“‹ Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [Getting Started](#getting-started) +- [Development Setup](#development-setup) +- [Making Changes](#making-changes) +- [Testing](#testing) +- [Submitting Changes](#submitting-changes) +- [Coding Standards](#coding-standards) +- [Adding Hardware Support](#adding-hardware-support) +- [Documentation](#documentation) + +## ๐Ÿค Code of Conduct + +### Our Standards + +- **Be Respectful** - Treat everyone with respect and kindness +- **Be Inclusive** - Welcome contributors of all backgrounds and experience levels +- **Be Professional** - Keep discussions technical and constructive +- **Be Patient** - Remember that everyone is learning + +### Unacceptable Behavior + +- Harassment, discrimination, or offensive comments +- Personal attacks or trolling +- Publishing private information without permission +- Any conduct that creates an unwelcoming environment + +## ๐Ÿš€ Getting Started + +### Prerequisites + +- Linux system (Ubuntu, Debian, Fedora, Arch, etc.) +- Python 3.6+ +- Git +- Basic knowledge of bash scripting and Python +- Understanding of Linux power management + +### Fork and Clone + +```bash +# Fork the repository on GitHub, then: +git clone https://github.com/YOUR_USERNAME/PowerManagement.git +cd PowerManagement + +# Add upstream remote +git remote add upstream https://github.com/milhy545/PowerManagement.git +``` + +### Stay Updated + +```bash +# Fetch upstream changes +git fetch upstream + +# Merge upstream changes into your branch +git merge upstream/main +``` + +## ๐Ÿ’ป Development Setup + +### Install in Development Mode + +```bash +# Install dependencies +./install.sh + +# Set up development environment +export PYTHONPATH="$PWD/src:$PYTHONPATH" +export POWER_MGMT_DIR="$PWD" + +# Add to ~/.bashrc for persistence +echo 'export PYTHONPATH="/path/to/PowerManagement/src:$PYTHONPATH"' >> ~/.bashrc +echo 'export POWER_MGMT_DIR="/path/to/PowerManagement"' >> ~/.bashrc +``` + +### Development Tools + +```bash +# Python linting +pip3 install pylint black mypy + +# Shell script checking +sudo apt install shellcheck # Ubuntu/Debian +sudo dnf install shellcheck # Fedora +``` + +## ๐Ÿ”ง Making Changes + +### Branch Naming + +Use descriptive branch names: + +```bash +git checkout -b feature/add-battery-management +git checkout -b fix/sensor-detection-crash +git checkout -b docs/improve-installation-guide +``` + +**Prefixes:** +- `feature/` - New features +- `fix/` - Bug fixes +- `docs/` - Documentation changes +- `refactor/` - Code refactoring +- `test/` - Test additions/changes +- `perf/` - Performance improvements + +### Commit Messages + +Follow conventional commits format: + +``` +type(scope): short description + +Longer description if needed. + +- Bullet points for changes +- Reference issues: Fixes #123 +``` + +**Types:** +- `feat` - New feature +- `fix` - Bug fix +- `docs` - Documentation +- `refactor` - Code refactoring +- `test` - Testing +- `perf` - Performance +- `chore` - Maintenance + +**Examples:** +```bash +git commit -m "feat(sensors): Add support for ITE IT8712F chip" +git commit -m "fix(gpu): Handle missing nvidia-smi gracefully" +git commit -m "docs(readme): Update installation instructions" +``` + +## ๐Ÿงช Testing + +### Run Test Suite + +```bash +# Full test suite +./tests/test_sensors.sh + +# Individual component tests +python3 -m pytest tests/ + +# Specific test +python3 tests/test_hardware_detector.py +``` + +### Manual Testing + +Before submitting: + +1. **Test on Real Hardware** + ```bash + # Test sensor detection + python3 src/sensors/universal_sensor_detector.py + + # Test GPU monitoring + python3 src/sensors/gpu_monitor.py + + # Test power profiles + ./scripts/performance_manager.sh test + ``` + +2. **Check for Errors** + ```bash + # Python syntax check + python3 -m py_compile src/**/*.py + + # Shell script check + shellcheck scripts/*.sh integrations/*.sh + ``` + +3. **Verify Documentation** + ```bash + # Check README renders correctly + grip README.md # Requires: pip install grip + ``` + +### Test Coverage + +Aim for: +- **Python Code**: 80%+ coverage +- **Shell Scripts**: Test critical paths +- **Integration Tests**: Test end-to-end workflows + +## ๐Ÿ“ค Submitting Changes + +### Pull Request Process + +1. **Update Documentation** + - Update README.md if adding features + - Update relevant docs/ files + - Add entries to CHANGELOG.md + +2. **Ensure Tests Pass** + ```bash + ./tests/test_sensors.sh + ``` + +3. **Update CHANGELOG** + ```markdown + ## [Unreleased] + + ### Added + - New feature description + + ### Fixed + - Bug fix description + ``` + +4. **Create Pull Request** + - Use a clear title: "feat: Add temperature alert notifications" + - Fill out PR template completely + - Reference related issues: "Fixes #123" + - Add screenshots/logs if applicable + +5. **Address Review Comments** + - Respond to all review comments + - Make requested changes + - Mark conversations as resolved + +### PR Requirements + +โœ… **Required:** +- All tests pass +- Documentation updated +- CHANGELOG.md updated +- Commit messages follow conventions +- No merge conflicts + +โญ **Nice to Have:** +- Test coverage increased +- Performance improvements documented +- Examples added to docs/ + +## ๐Ÿ“ Coding Standards + +### Python Style + +Follow [PEP 8](https://pep8.org/): + +```python +# Good +def detect_cpu_temperature(sensor_path: str) -> Optional[float]: + """ + Detect CPU temperature from sensor path. + + Args: + sensor_path: Path to temperature sensor file + + Returns: + Temperature in Celsius or None if unavailable + """ + try: + with open(sensor_path, 'r') as f: + temp_raw = int(f.read().strip()) + return temp_raw / 1000.0 + except (FileNotFoundError, ValueError) as e: + logger.warning(f"Failed to read {sensor_path}: {e}") + return None +``` + +**Requirements:** +- Type hints for function signatures +- Docstrings for all public functions/classes +- Error handling with specific exceptions +- Logging instead of print statements + +### Shell Script Style + +```bash +#!/bin/bash +set -euo pipefail + +# Good +detect_gpu_card() { + local card_path + + for card in /sys/class/drm/card[0-9]; do + if [ -f "$card/device/power_profile" ]; then + echo "$card" + return 0 + fi + done + + return 1 +} +``` + +**Requirements:** +- Use `set -euo pipefail` +- Quote variables: `"$variable"` +- Use `local` for function variables +- Return meaningful exit codes +- Comment complex sections + +### Code Organization + +``` +src/ +โ”œโ”€โ”€ hardware/ # Hardware detection +โ”œโ”€โ”€ sensors/ # Sensor monitoring +โ”œโ”€โ”€ frequency/ # CPU frequency control +โ”œโ”€โ”€ config/ # Configuration management +โ””โ”€โ”€ services/ # Background services + +scripts/ # User-facing scripts +integrations/ # Third-party integrations +tests/ # Test suite +docs/ # Documentation +``` + +## ๐Ÿ–ฅ๏ธ Adding Hardware Support + +### Adding New CPU Support + +1. **Update Hardware Detector** + ```python + # src/hardware/hardware_detector.py + + def _detect_cpu_generation(self, vendor, model_name): + if vendor == CPUVendor.INTEL: + if "Raptor Lake" in model_name: + return CPUGeneration.RAPTORLAKE + ``` + +2. **Add Thermal Thresholds** + ```python + thermal_limits = { + CPUGeneration.RAPTORLAKE: { + 'max_temp': 100, + 'critical_temp': 95, + # ... + } + } + ``` + +3. **Test on Real Hardware** + - Run full test suite + - Verify thermal monitoring + - Check frequency scaling + +4. **Update Documentation** + - Add to UNIVERSAL_HARDWARE.md + - Update README compatibility list + +### Adding New Sensor Chip + +1. **Update Sensor Detector** + ```python + # src/sensors/universal_sensor_detector.py + + def _detect_sysfs_hwmon(self): + # Add chip detection logic + if "it8712" in chip_name: + # Handle ITE IT8712F specific quirks + ``` + +2. **Handle Edge Cases** + - Missing sensors + - Different sysfs paths + - Atypical naming + +3. **Add Tests** + ```bash + # tests/test_sensors.sh + test_it8712_detection + ``` + +### Adding New GPU Support + +1. **Update GPU Monitor** + ```python + # src/sensors/gpu_monitor.py + + def _detect_nouveau_gpus(self): + # Add Nouveau (open-source NVIDIA) support + ``` + +2. **Test Multi-GPU Scenarios** + +3. **Update Documentation** + +## ๐Ÿ“š Documentation + +### Documentation Standards + +- **Clear and Concise** - Get to the point quickly +- **Examples** - Provide code examples +- **Screenshots** - Add visuals where helpful +- **Up-to-Date** - Update docs with code changes + +### Documentation Files + +- `README.md` - Overview and quick start +- `docs/UNIVERSAL_HARDWARE.md` - Hardware compatibility +- `docs/SENSOR_MONITORING.md` - Sensor features +- `docs/INTEGRATIONS.md` - Integration guides +- `CHANGELOG.md` - Version history +- `CONTRIBUTING.md` - This file + +### Writing Documentation + +```markdown +# Feature Name + +## Overview +Brief description of the feature. + +## Installation +\`\`\`bash +# Installation commands +\`\`\` + +## Usage +\`\`\`bash +# Usage examples +\`\`\` + +## Troubleshooting +Common issues and solutions. +``` + +## โ“ Questions? + +- **Issues**: [GitHub Issues](https://github.com/milhy545/PowerManagement/issues) +- **Discussions**: [GitHub Discussions](https://github.com/milhy545/PowerManagement/discussions) +- **Email**: Contact maintainers for private inquiries + +## ๐ŸŽ‰ Recognition + +Contributors will be: +- Listed in CHANGELOG.md +- Mentioned in release notes +- Added to contributors list (if significant contributions) + +Thank you for contributing to PowerManagement! ๐Ÿš€ diff --git a/MERGE_INSTRUCTIONS.md b/MERGE_INSTRUCTIONS.md new file mode 100644 index 0000000..e86b55e --- /dev/null +++ b/MERGE_INSTRUCTIONS.md @@ -0,0 +1,58 @@ +# ๐ŸŽฏ Jak Mergovat na GitHubu - Jednoduchรฉ Instrukce + +Mรกm to pล™ipravenรฉ! Staฤรญ udฤ›lat tohle: + +## Krok 1: Otevล™i GitHub + +Jdi na: https://github.com/milhy545/PowerManagement + +## Krok 2: Vytvoล™ Pull Request + +GitHub ti pravdฤ›podobnฤ› ukรกลพe zelenรฝ banner: + +``` +claude/merge-to-main-012t9or7WBz5LBALB35NA2Aj had recent pushes +[Compare & pull request] <-- KLIKNI TADY +``` + +**Pokud banner nevidรญลก:** +1. Klikni na "Pull requests" (nahoล™e) +2. Klikni zelenรฉ tlaฤรญtko "New pull request" +3. Vyber: + - **base:** main + - **compare:** claude/merge-to-main-012t9or7WBz5LBALB35NA2Aj +4. Klikni "Create pull request" + +## Krok 3: Mergni + +1. V PR uvidรญลก velkรฝ zelenรฝ button **"Merge pull request"** +2. Klikni na nฤ›j +3. Klikni **"Confirm merge"** + +โœ… HOTOVO! + +## Krok 4: Smaลพ Branch (volitelnรฉ) + +GitHub ti po merge nabรญdne: +``` +Pull request successfully merged and closed +[Delete branch] <-- Klikni tady +``` + +## Co se Stalo? + +Pล™ipravil jsem ti **1 branch** se vลกemi zmฤ›nami (v3.0 + v3.1): +- โœ… Universal hardware support +- โœ… GPU monitoring +- โœ… Sensor detection +- โœ… Fan control +- โœ… Integrations +- โœ… Kompletnรญ dokumentace + +Po merge bude **vลกechno v main** - jeden projekt, jedna branch! ๐ŸŽ‰ + +--- + +## Problรฉm s Merge? + +Pokud to nejde, mลฏลพu to udฤ›lat jinak. Napiลก mi. diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..023b923 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,332 @@ +# โšก Quick Start Guide + +**Get started with PowerManagement in 5 minutes!** + +## ๐Ÿš€ Installation + +### Option 1: One-Click Install (Recommended) + +```bash +# Clone repository +git clone https://github.com/milhy545/PowerManagement.git +cd PowerManagement + +# Run installation script +./install.sh +``` + +That's it! โœ… + +### Option 2: Manual Install + +```bash +# Clone repository +git clone https://github.com/milhy545/PowerManagement.git +cd PowerManagement + +# Install dependencies +pip3 install psutil + +# Optional: Install lm-sensors for full sensor support +sudo apt install lm-sensors +sudo sensors-detect # Answer YES to all questions +``` + +--- + +## ๐ŸŽฏ First Steps + +### 1. Check Your Hardware + +```bash +# Detect CPU, GPU, and capabilities +python3 src/hardware/hardware_detector.py +``` + +**Example Output:** +``` +๐Ÿ–ฅ๏ธ CPU: Intel(R) Core(TM)2 Quad CPU Q9550 @ 2.83GHz + Vendor: INTEL + Thermal Max: 85ยฐC + +๐ŸŽฎ GPU: AMD Radeon RV710 + Power Profile Support: โœ… Yes +``` + +### 2. View GPU Status + +```bash +# Show GPU temperature, fan speed, power +python3 src/sensors/gpu_monitor.py +``` + +**Example Output:** +``` +๐ŸŽฎ GPU: NVIDIA GeForce RTX 3080 + ๐ŸŒก๏ธ Temperature: 62ยฐC + ๐Ÿ’จ Fan: 45% (1850 RPM) + โšก Power: 245W / 320W +``` + +### 3. View All Sensors + +```bash +# Detect and show ALL system sensors +python3 src/sensors/universal_sensor_detector.py +``` + +**Example Output:** +``` +๐ŸŒก๏ธ TEMPERATURE SENSORS (12) + โ€ข Package id 0 : 45.0 ยฐC + โ€ข Core 0 : 42.0 ยฐC + โ€ข GPU edge : 62.0 ยฐC + +๐Ÿ’จ FAN SENSORS (6) + โ€ข CPU Fan : 1245 RPM + โ€ข GPU Fan : 1850 RPM +``` + +### 4. Control Fans (Requires sudo) + +```bash +# Show controllable fans +python3 src/sensors/fan_controller.py status + +# Set CPU fan to 60% +sudo python3 src/sensors/fan_controller.py set 0 60 + +# Set back to automatic +sudo python3 src/sensors/fan_controller.py auto 0 +``` + +--- + +## ๐Ÿ’ช Power Profiles + +### Set Power Mode + +```bash +# Maximum performance +./scripts/performance_manager.sh performance + +# Balanced (recommended) +./scripts/performance_manager.sh balanced + +# Power saving +./scripts/performance_manager.sh powersave + +# Emergency (thermal issues) +./scripts/performance_manager.sh emergency +``` + +### View Current Status + +```bash +./scripts/performance_manager.sh status +``` + +--- + +## ๐Ÿ“Š Real-Time Monitoring + +### Start Monitoring Service + +```bash +# Start with default settings (5 second interval) +python3 src/services/monitoring_service.py + +# Custom interval (10 seconds) +python3 src/services/monitoring_service.py --interval 10 + +# Disable automatic fan control +python3 src/services/monitoring_service.py --no-auto-fan +``` + +**What it does:** +- โœ… Monitors CPU & GPU temperature +- โœ… Monitors fan speeds +- โœ… Automatically adjusts fans based on temperature +- โœ… Sends alerts when temps are high +- โœ… Logs data to JSON file + +**Example Output:** +``` +[22:45:06] ๐Ÿ“Š CPU: 45.0ยฐC | GPU: 62.0ยฐC | Fan: 1245RPM +[22:45:11] ๐Ÿ“Š CPU: 46.0ยฐC | GPU: 63.0ยฐC | Fan: 1250RPM +[22:45:16] โšก CPU WARNING: 72.0ยฐC +[22:45:21] โš ๏ธ CRITICAL: Setting fans to 75% +``` + +--- + +## ๐Ÿ”ง Common Use Cases + +### Use Case 1: Gaming / High Performance + +```bash +# Set to performance mode +./scripts/performance_manager.sh performance + +# Monitor GPU while gaming +watch -n 1 python3 src/sensors/gpu_monitor.py +``` + +### Use Case 2: Silent Operation + +```bash +# Set to power save mode +./scripts/performance_manager.sh powersave + +# Manually set fans to 30% +sudo python3 src/sensors/fan_controller.py set 0 30 +``` + +### Use Case 3: Overheating Prevention + +```bash +# Start automatic monitoring with fan control +python3 src/services/monitoring_service.py + +# It will automatically increase fan speed when temps rise! +``` + +### Use Case 4: All-in-One PC (Limited Cooling) + +```bash +# Start monitoring with aggressive fan control +python3 src/services/monitoring_service.py --interval 3 + +# The service will keep your AIO PC cool automatically +``` + +--- + +## ๐Ÿ†˜ Troubleshooting + +### No GPU Detected + +**NVIDIA:** +```bash +# Check if nvidia-smi works +nvidia-smi + +# If not, install NVIDIA drivers +sudo apt install nvidia-driver-525 +``` + +**AMD:** +```bash +# Check if AMD GPU is detected +ls /sys/class/drm/card*/device/vendor + +# Should show "0x1002" for AMD +``` + +### No Sensors Detected + +```bash +# Install lm-sensors +sudo apt install lm-sensors + +# Detect sensors +sudo sensors-detect +# Answer YES to all questions + +# Test +sensors +``` + +### Fan Control Not Working + +```bash +# Check if PWM control exists +ls /sys/class/hwmon/hwmon*/pwm* + +# If not, enable in BIOS: +# - Look for "Smart Fan Control" or "PWM Mode" +# - Set to "Manual" or "PWM" +``` + +### Permission Denied + +```bash +# Fan control requires root +sudo python3 src/sensors/fan_controller.py set 0 60 + +# Or add user to groups +sudo usermod -aG gpio $USER +sudo usermod -aG i2c $USER +# Log out and back in +``` + +--- + +## ๐Ÿ“– Learn More + +- **[SENSOR_MONITORING.md](docs/SENSOR_MONITORING.md)** - Detailed sensor & fan guide +- **[UNIVERSAL_HARDWARE.md](docs/UNIVERSAL_HARDWARE.md)** - Hardware compatibility +- **[README.md](README.md)** - Full documentation + +--- + +## ๐ŸŽฏ Quick Command Reference + +| Task | Command | +|------|---------| +| Show GPU | `python3 src/sensors/gpu_monitor.py` | +| Show Sensors | `python3 src/sensors/universal_sensor_detector.py` | +| Fan Status | `python3 src/sensors/fan_controller.py status` | +| Set Fan Speed | `sudo python3 src/sensors/fan_controller.py set 0 60` | +| Performance Mode | `./scripts/performance_manager.sh performance` | +| Balanced Mode | `./scripts/performance_manager.sh balanced` | +| Power Save Mode | `./scripts/performance_manager.sh powersave` | +| Monitor Service | `python3 src/services/monitoring_service.py` | +| Hardware Info | `python3 src/hardware/hardware_detector.py` | + +--- + +## ๐Ÿ’ก Pro Tips + +1. **Auto-start monitoring on boot:** + ```bash + # Add to crontab + @reboot cd /path/to/PowerManagement && python3 src/services/monitoring_service.py >> /tmp/power-monitor.log 2>&1 + ``` + +2. **Monitor GPU while running AI:** + ```bash + # In terminal 1: Run AI workload + # In terminal 2: + watch -n 1 python3 src/sensors/gpu_monitor.py + ``` + +3. **Create desktop shortcuts:** + ```bash + # Performance mode shortcut + echo "./scripts/performance_manager.sh performance" > ~/Desktop/performance.sh + chmod +x ~/Desktop/performance.sh + ``` + +4. **Get alerts on high temps:** + ```bash + # The monitoring service automatically alerts + # Logs are in /tmp/power_monitoring.log + tail -f /tmp/power_monitoring.log + ``` + +--- + +## ๐ŸŽ‰ You're Ready! + +Your system is now equipped with professional power management! + +**Next Steps:** +- Set your preferred power mode +- Start the monitoring service +- Enjoy optimal performance and cooling! + +**Need Help?** +- Check [SENSOR_MONITORING.md](docs/SENSOR_MONITORING.md) +- Open an issue on GitHub +- Read troubleshooting section above diff --git a/README.md b/README.md index 2b6d1bc..0899437 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,35 @@ # ๐Ÿš€ Linux Power Management Suite -**Professional power management tools for Linux systems with safety-first design.** +**Version 3.1 - Professional Monitoring & Fan Control** + +Professional power management with **universal CPU/GPU compatibility**, **advanced sensor monitoring**, and **intelligent fan control**. Works on Intel, AMD, NVIDIA GPUs, and atypical systems including all-in-one PCs. [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![GitHub Ready](https://img.shields.io/badge/GitHub-Ready-green.svg)](https://github.com) [![Tested](https://img.shields.io/badge/Tested-Passing-brightgreen.svg)](tests/) +[![Universal](https://img.shields.io/badge/Hardware-Universal-blue.svg)](docs/UNIVERSAL_HARDWARE.md) + +## ๐ŸŒŸ What's New in V3.1 + +- ๐ŸŽฎ **GPU Monitoring** - Real-time temp, fan, power (NVIDIA/AMD/Intel) +- ๐Ÿ” **Universal Sensors** - ALL sensors including atypical motherboards +- ๐Ÿ’จ **Fan Control** - PWM control for CPU & GPU with auto-adjustment +- ๐Ÿ“Š **Monitoring Service** - Professional daemon with alerts & JSON logging +- ๐Ÿญ **All-in-One Support** - Works on difficult configs (Acer, Dell AIO) + +๐Ÿ“– **[Advanced Sensor Monitoring Guide โ†’](docs/SENSOR_MONITORING.md)** + +## ๐ŸŒ What's New in V3.0 + +- โœ… **Universal CPU Support** - Intel (Core 2, i3/i5/i7, Skylake+), AMD (K8, K10, FX, Ryzen) +- โœ… **Auto GPU Detection** - AMD, NVIDIA, Intel - automatically finds your GPU +- โœ… **Adaptive Thermal Management** - CPU-specific temperature thresholds +- โœ… **No Hardcoded Paths** - Install anywhere, works from any directory +- โœ… **Portable** - Clone and run on any Linux system +- โœ… **Backward Compatible** - Original Q9550 optimizations preserved + +๐Ÿ“– **[Universal Hardware Documentation โ†’](docs/UNIVERSAL_HARDWARE.md)** ## ๐ŸŽฏ Features @@ -95,6 +119,47 @@ python3 examples/ai_workloads/final_mycoder_test.py # Thermal-protected A python3 examples/ai_workloads/ultra_safe_mycoder.py # Ultra-safe AI demo ``` +## ๐Ÿ”— Integrations + +PowerManagement integrates with other system tools for a unified monitoring experience: + +### MyMenu Integration (dmenu) +```bash +# Add PowerManagement to MyMenu dmenu launcher +bash integrations/mymenu_patch.sh /path/to/MyMenu + +# Provides: +# - ๐ŸŒก๏ธ Power & Thermal category +# - Quick access to power profiles +# - GPU and sensor monitoring +# - Fan control submenu +``` + +### claude-tools-monitor Integration +```bash +# Monitor system metrics during Claude AI sessions +cp integrations/power_integration.py /path/to/claude-tools-monitor/integrations/ + +# Features: +# - Real-time CPU/GPU temperature logging +# - Thermal throttling protection +# - Power consumption tracking +``` + +### Unified Dashboard +```bash +# Launch unified monitoring dashboard +bash integrations/unified_monitor.sh + +# Shows: +# - PowerManagement metrics (CPU/GPU temps, fans, power) +# - Claude monitor status +# - Real-time thermal alerts +# - Interactive menu system +``` + +๐Ÿ“– **[Complete Integration Guide โ†’](docs/INTEGRATIONS.md)** + ## ๐Ÿ›ก๏ธ Safety Features - **Process Limit Monitoring** - Prevents fork bombs (max 10 instances) diff --git a/daemons/custom-power-profiles-daemon.py b/daemons/custom-power-profiles-daemon.py index 401828a..c150834 100755 --- a/daemons/custom-power-profiles-daemon.py +++ b/daemons/custom-power-profiles-daemon.py @@ -11,12 +11,19 @@ import subprocess import time import logging +from pathlib import Path from threading import Thread import dbus import dbus.service import dbus.mainloop.glib from gi.repository import GLib +# Add src directory to path for imports +daemon_dir = Path(__file__).resolve().parent +sys.path.insert(0, str(daemon_dir.parent / "src")) + +from config.power_config import PowerConfig + # Logging setup logging.basicConfig( level=logging.INFO, @@ -44,7 +51,10 @@ def __init__(self): self.bus = dbus.SystemBus() bus_name = dbus.service.BusName(self.DBUS_SERVICE, self.bus) dbus.service.Object.__init__(self, bus_name, self.DBUS_PATH) - + + # Load configuration with dynamic paths + self.config = PowerConfig() + # Power profiles state self.active_profile = "balanced" self.profiles = [ @@ -54,30 +64,39 @@ def __init__(self): "Driver": "platform_profile" }, { - "Profile": "balanced", + "Profile": "balanced", "PlatformDriver": "platform_profile", "Driver": "platform_profile" }, { "Profile": "performance", - "PlatformDriver": "platform_profile", + "PlatformDriver": "platform_profile", "Driver": "platform_profile" } ] - + self.holds = [] self.performance_degraded = "" self.performance_inhibited = "" self.actions = ["trickle_charge", "ai_optimization", "emergency_mode"] - self.version = "2.0-custom" - - # Performance manager script paths - self.performance_script = "/home/milhy777/performance_manager.sh" - self.ai_manager_script = "/home/milhy777/ai_process_manager.sh" - self.emergency_script = "/home/milhy777/EMERGENCY_CLEANUP.sh" - - log.info("๐Ÿค– Custom Power Profiles Daemon initialized") + self.version = "3.0-universal" # Updated version + + # Get script paths from configuration + self.performance_script = self.config.get_script_path("performance_manager") + self.ai_manager_script = self.config.get_script_path("ai_process_manager") + self.emergency_script = self.config.get_script_path("emergency_cleanup") + + log.info("๐Ÿค– Custom Power Profiles Daemon initialized (Universal Edition)") log.info(f"๐Ÿ“‹ Available profiles: {[p['Profile'] for p in self.profiles]}") + log.info(f"๐Ÿ“ Install directory: {self.config.paths.install_dir}") + + # Warn if scripts not found + if not self.performance_script: + log.warning("โš ๏ธ Performance manager script not found") + if not self.ai_manager_script: + log.warning("โš ๏ธ AI manager script not found") + if not self.emergency_script: + log.warning("โš ๏ธ Emergency cleanup script not found") # D-Bus Properties @dbus.service.method(DBUS_INTERFACE, in_signature='', out_signature='as') @@ -199,36 +218,30 @@ def PropertiesChanged(self, interface_name, changed_properties, invalidated_prop def _execute_profile_change(self, profile): """Execute the actual profile change using our scripts""" + if not self.performance_script: + log.error("โŒ Performance manager script not available") + return + try: if profile == "performance": log.info("๐Ÿ”ฅ Executing PERFORMANCE profile") - subprocess.run([self.performance_script, "performance"], + subprocess.run([self.performance_script, "performance"], timeout=30, capture_output=True) - - elif profile == "balanced": + + elif profile == "balanced": log.info("โš–๏ธ Executing BALANCED profile") subprocess.run([self.performance_script, "balanced"], timeout=30, capture_output=True) - + elif profile == "power-saver": - log.info("๐Ÿ”‹ Executing POWER-SAVER profile") + log.info("๐Ÿ”‹ Executing POWER-SAVER profile") subprocess.run([self.performance_script, "powersave"], timeout=30, capture_output=True) - - # Trigger System-Optimizer-Guardian Agent for optimization - if os.path.exists("/usr/local/bin/claude"): - log.info("๐Ÿค– Triggering System-Optimizer-Guardian Agent") - agent_cmd = [ - "claude", "--agent", "system-optimizer-guardian", - f"Power profile changed to {profile}. Optimize system accordingly: " - f"1. Apply {profile} optimizations 2. Check system load " - f"3. Optimize GPU/CPU states 4. Clear caches if needed " - f"5. Monitor thermal conditions" - ] - subprocess.Popen(agent_cmd) - + except subprocess.TimeoutExpired: log.error(f"โš ๏ธ Profile change timeout for {profile}") + except FileNotFoundError: + log.error(f"โŒ Script not found: {self.performance_script}") except Exception as e: log.error(f"โŒ Profile change failed for {profile}: {e}") diff --git a/docs/INTEGRATIONS.md b/docs/INTEGRATIONS.md new file mode 100644 index 0000000..6471ce4 --- /dev/null +++ b/docs/INTEGRATIONS.md @@ -0,0 +1,518 @@ +# PowerManagement Integrations + +## ๐Ÿ”— Overview + +PowerManagement v3.1 now provides integration with other system tools and workflows, creating a unified monitoring and management ecosystem. + +## ๐Ÿ“‹ Available Integrations + +### 1. MyMenu Integration (dmenu Launcher) + +Add PowerManagement capabilities to your MyMenu dmenu launcher. + +**Features:** +- ๐ŸŒก๏ธ Power & Thermal category in dmenu +- Quick access to all power profiles +- GPU and sensor monitoring +- Fan control submenu +- Monitoring service launcher + +**Installation:** + +```bash +# Automatic integration (creates backup) +bash integrations/mymenu_patch.sh /path/to/MyMenu + +# Manual integration +# Add PowerManagement commands to your dmenu-launcher.sh +source integrations/mymenu_integration.sh +``` + +**Usage:** + +1. Launch MyMenu: `bash /path/to/MyMenu/dmenu-launcher.sh` +2. Select: `๐ŸŒก๏ธ Power & Thermal` +3. Choose action: + - `๐Ÿ”ฅ Performance Mode` - Max performance + - `โš–๏ธ Balanced Mode` - Balanced power + - `๐Ÿ”‹ Power Save Mode` - Battery saver + - `๐Ÿšจ Emergency Mode` - Thermal emergency + - `๐Ÿ“Š Current Status` - System overview + - `๐ŸŽฎ GPU Metrics` - GPU details + - `๐ŸŒก๏ธ All Sensors` - Complete sensor list + - `๐Ÿ’จ Fan Status` - Fan speeds + - `๐Ÿ’จ Fan Control` - Manual fan control + - `๐Ÿ“ˆ Start Monitoring` - Launch monitoring service + - `๐Ÿ“– Documentation` - View docs + +**Menu Structure:** + +``` +MyMenu +โ”œโ”€โ”€ ... +โ”œโ”€โ”€ ๐ŸŒก๏ธ Power & Thermal +โ”‚ โ”œโ”€โ”€ Power Profiles +โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ”ฅ Performance Mode +โ”‚ โ”‚ โ”œโ”€โ”€ โš–๏ธ Balanced Mode +โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ”‹ Power Save Mode +โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿšจ Emergency Mode +โ”‚ โ”œโ”€โ”€ Monitoring +โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ“Š Current Status +โ”‚ โ”‚ โ”œโ”€โ”€ ๐ŸŽฎ GPU Metrics +โ”‚ โ”‚ โ”œโ”€โ”€ ๐ŸŒก๏ธ All Sensors +โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ’จ Fan Status +โ”‚ โ”œโ”€โ”€ Control +โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ’จ Fan Control +โ”‚ โ”‚ โ”œโ”€โ”€ ๐ŸŒ€ 30% Silent +โ”‚ โ”‚ โ”œโ”€โ”€ ๐ŸŒ€ 50% Normal +โ”‚ โ”‚ โ”œโ”€โ”€ ๐ŸŒ€ 75% High +โ”‚ โ”‚ โ”œโ”€โ”€ ๐ŸŒ€ 100% Max +โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ”„ Auto Mode +โ”‚ โ””โ”€โ”€ Services +โ”‚ โ”œโ”€โ”€ ๐Ÿ“ˆ Start Monitoring +โ”‚ โ””โ”€โ”€ ๐Ÿ“– Documentation +โ””โ”€โ”€ ... +``` + +**Uninstall:** + +```bash +# Restore from backup +cd /path/to/MyMenu +cp dmenu-launcher.sh.backup.YYYYMMDD_HHMMSS dmenu-launcher.sh +``` + +--- + +### 2. claude-tools-monitor Integration + +Integrate system power/thermal monitoring into Claude AI session monitoring. + +**Features:** +- ๐Ÿ“Š Real-time CPU/GPU temperature logging during Claude sessions +- โšก Thermal throttling protection (warn when temps critical) +- ๐Ÿ’จ Fan speed tracking +- ๐Ÿ”Œ Power consumption logging +- ๐Ÿ“ JSON metrics log for analysis + +**Installation:** + +```bash +# Copy integration to claude-tools-monitor +cp integrations/power_integration.py /path/to/claude-tools-monitor/integrations/ + +# Set PowerManagement directory +export POWER_MGMT_DIR=/home/user/PowerManagement +``` + +**Usage:** + +```python +from integrations.power_integration import PowerMetricsLogger + +# Initialize logger +logger = PowerMetricsLogger(log_file="/tmp/claude_power_metrics.log") + +# Get current metrics +metrics = logger.get_current_metrics() +print(f"CPU: {metrics['cpu_temp']}ยฐC") +print(f"GPU: {metrics['gpu_temp']}ยฐC") + +# Check thermal status +if logger.should_throttle_claude(): + print("๐Ÿšจ CRITICAL: Consider pausing Claude!") + +# Log metrics with Claude activity +logger.log_metrics(claude_activity="code generation") +``` + +**Automatic Monitoring:** + +The integration automatically monitors: + +| Metric | Source | Purpose | +|--------|--------|---------| +| CPU Temperature | UniversalSensorDetector | Thermal throttling detection | +| GPU Temperature | UniversalGPUMonitor | GPU workload monitoring | +| CPU Fan RPM | Fan sensors | Cooling efficiency | +| GPU Fan RPM | GPU monitor | GPU cooling | +| GPU Power | GPU monitor | Power consumption | + +**Thermal Protection:** + +The integration provides automatic thermal protection: + +```python +# Check if should throttle +if logger.should_throttle_claude(): + # CPU temperature >= critical threshold (85% of max) + # Recommend pausing intensive operations +``` + +**Log Format:** + +``` +2025-11-18T22:45:06 | Claude: code generation | CPU: 45.0ยฐC GPU: 62.0ยฐC Fan: 1245RPM GPU Power: 245W +2025-11-18T22:45:11 | Claude: testing | CPU: 46.0ยฐC GPU: 63.0ยฐC Fan: 1250RPM GPU Power: 240W +2025-11-18T22:45:16 | Claude: building | CPU: 72.0ยฐC GPU: 68.0ยฐC Fan: 1450RPM GPU Power: 265W +``` + +**Example Integration in tmux Monitor:** + +```bash +#!/bin/bash +# Enhanced tmux monitoring with thermal protection + +source /path/to/claude-tools-monitor/integrations/power_integration.py + +while true; do + # Monitor Claude activity + monitor_claude_sessions + + # Check thermal status + if should_throttle_claude; then + echo "๐Ÿšจ THERMAL WARNING: Consider pausing Claude" + notify-send "Claude Thermal Warning" "CPU temperature critical" + fi + + sleep 5 +done +``` + +--- + +### 3. Unified Monitoring Dashboard + +Combined monitoring dashboard showing all system metrics in one view. + +**Features:** +- ๐ŸŽฏ Unified view of PowerManagement + claude-tools-monitor +- ๐Ÿ“Š Real-time system overview +- ๐ŸŒก๏ธ Temperature monitoring +- ๐Ÿ’จ Fan status +- โšก Power consumption +- ๐Ÿค– Claude activity status +- ๐Ÿ”„ Auto-refresh capability + +**Installation:** + +```bash +# Already included in PowerManagement +bash integrations/unified_monitor.sh +``` + +**Usage:** + +**Interactive Mode:** +```bash +# Launch interactive dashboard +bash integrations/unified_monitor.sh +``` + +**dmenu Mode:** +```bash +# Show menu options +bash integrations/unified_monitor.sh --dmenu | dmenu -p "Dashboard:" + +# Direct action +bash integrations/unified_monitor.sh "๐Ÿ“Š View Full Status" +``` + +**Available Commands:** + +| Command | Description | +|---------|-------------| +| `๐Ÿ“Š View Full Status` | Comprehensive system overview | +| `๐Ÿ”„ Refresh Dashboard` | Reload all metrics | +| `๐ŸŒก๏ธ Temperature Details` | All temperature sensors | +| `๐Ÿ’จ Fan Control` | Fan management | +| `๐ŸŽฎ GPU Details` | GPU metrics | +| `โšก Power Profiles` | Switch power mode | +| `๐Ÿ“ˆ Start Monitoring Service` | Launch monitoring daemon | +| `๐Ÿ›‘ Stop Monitoring Service` | Stop monitoring daemon | + +**Dashboard Output Example:** + +``` +============================================ + ๐ŸŽฏ Unified System Dashboard +============================================ + +๐Ÿ–ฅ๏ธ SYSTEM OVERVIEW + Power Profile: โš–๏ธ Balanced + Thermal Status: โœ… GOOD + +๐ŸŒก๏ธ TEMPERATURE + CPU: 45.0ยฐC + GPU: 62.0ยฐC + +๐Ÿ’จ FANS + CPU Fan: 1245 RPM + +โšก POWER + CPU: 45.2W + GPU: 245.0W + +๐Ÿค– CLAUDE MONITOR + Status: Running + Activity: code generation + +============================================ +``` + +**Integration with MyMenu:** + +```bash +# Add to MyMenu for quick access +echo "๐ŸŽฏ System Dashboard" >> MyMenu/dmenu-launcher.sh +# Handler: +bash /path/to/PowerManagement/integrations/unified_monitor.sh --dmenu +``` + +--- + +## ๐Ÿ› ๏ธ Advanced Integration Scenarios + +### Scenario 1: Automated Thermal Management During AI Sessions + +**Goal:** Automatically adjust fan speeds when Claude is active + +```bash +#!/bin/bash +# claude_thermal_guard.sh + +POWER_MGMT="/home/user/PowerManagement" +export PYTHONPATH="$POWER_MGMT/src" + +while true; do + # Check if Claude is running + if pgrep -f "claude" >/dev/null; then + # Get CPU temperature + cpu_temp=$(python3 -c " +from sensors.universal_sensor_detector import UniversalSensorDetector +detector = UniversalSensorDetector() +temps = detector.get_temperature_sensors() +for t in temps: + if 'package' in t.label.lower(): + print(t.value) + break + ") + + # Adjust fans based on temp + if (( $(echo "$cpu_temp > 75" | bc -l) )); then + echo "๐Ÿšจ High temp during Claude session: ${cpu_temp}ยฐC" + sudo python3 "$POWER_MGMT/src/sensors/fan_controller.py" set 0 75 + fi + fi + + sleep 10 +done +``` + +### Scenario 2: Power Profile Switcher Based on Workload + +**Goal:** Automatically switch power profiles based on active applications + +```bash +#!/bin/bash +# smart_power_switcher.sh + +POWER_MGMT="/home/user/PowerManagement" + +while true; do + # Check active windows + if xdotool getwindowfocus getwindowname | grep -i "game\|blender\|premiere"; then + # Gaming/rendering - Performance mode + bash "$POWER_MGMT/scripts/performance_manager.sh" performance + elif xdotool getwindowfocus getwindowname | grep -i "battery\|unplugged"; then + # On battery - Power save mode + bash "$POWER_MGMT/scripts/performance_manager.sh" powersave + else + # Normal work - Balanced mode + bash "$POWER_MGMT/scripts/performance_manager.sh" balanced + fi + + sleep 60 +done +``` + +### Scenario 3: Data Logging for Performance Analysis + +**Goal:** Log all metrics for later analysis + +```bash +#!/bin/bash +# metrics_logger.sh + +POWER_MGMT="/home/user/PowerManagement" +LOG_DIR="$HOME/.power_metrics" +mkdir -p "$LOG_DIR" + +# Start monitoring service +PYTHONPATH="$POWER_MGMT/src" python3 "$POWER_MGMT/src/services/monitoring_service.py" \ + --interval 5 \ + --log-dir "$LOG_DIR" + +# Analyze later with: +# python3 -m json.tool ~/.power_metrics/power_monitoring.json +``` + +--- + +## ๐Ÿ”ง Configuration + +### Environment Variables + +```bash +# PowerManagement directory +export POWER_MGMT_DIR=/home/user/PowerManagement + +# Add to PATH for shortcuts +export PATH="$HOME/.local/bin:$PATH" + +# Python path for imports +export PYTHONPATH="$POWER_MGMT_DIR/src:$PYTHONPATH" +``` + +### Add to `.bashrc`: + +```bash +# PowerManagement Integration +export POWER_MGMT_DIR=/home/user/PowerManagement +export PATH="$HOME/.local/bin:$PATH" +export PYTHONPATH="$POWER_MGMT_DIR/src:$PYTHONPATH" + +# Aliases +alias pm-status='bash $POWER_MGMT_DIR/integrations/unified_monitor.sh "๐Ÿ“Š View Full Status"' +alias pm-gpu='gpu-monitor' +alias pm-sensors='sensor-detector' +alias pm-fans='fan-control status' +alias pm-perf='power-manager performance' +alias pm-balanced='power-manager balanced' +alias pm-save='power-manager powersave' +``` + +--- + +## ๐Ÿ“Š Integration Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ User Interface Layer โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ MyMenu โ”‚ โ”‚ Unified Dash โ”‚ โ”‚ CLI Commands โ”‚ โ”‚ +โ”‚ โ”‚ (dmenu) โ”‚ โ”‚ (terminal) โ”‚ โ”‚ (shortcuts) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ PowerManagement โ”‚ โ”‚ claude-tools-monitorโ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ€ข Sensors โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ€ข Activity Log โ”‚ +โ”‚ โ€ข GPU Monitor โ”‚ โ”‚ โ€ข Session Monitor โ”‚ +โ”‚ โ€ข Fan Control โ”‚ โ”‚ โ€ข Thermal Guard โ”‚ +โ”‚ โ€ข Power Profilesโ”‚ โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Hardware Abstraction Layer โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ sysfs โ”‚ โ”‚lm-sensorsโ”‚ โ”‚ GPU drivers โ”‚ โ”‚ +โ”‚ โ”‚ (hwmon) โ”‚ โ”‚ (sensors)โ”‚ โ”‚ (nvidia/amd/i915)โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๐Ÿ› Troubleshooting + +### Integration Not Working + +**Check installation paths:** +```bash +echo $POWER_MGMT_DIR +ls -la $POWER_MGMT_DIR/integrations/ +``` + +**Verify Python path:** +```bash +python3 -c "import sys; print('\n'.join(sys.path))" +``` + +**Test integration directly:** +```bash +# MyMenu integration +bash integrations/mymenu_integration.sh status + +# Claude monitor integration +python3 claude-tools-monitor/integrations/power_integration.py + +# Unified dashboard +bash integrations/unified_monitor.sh "๐Ÿ“Š View Full Status" +``` + +### MyMenu Patch Failed + +```bash +# Check backup exists +ls -la /path/to/MyMenu/dmenu-launcher.sh.backup* + +# Manual restore +cp dmenu-launcher.sh.backup.YYYYMMDD_HHMMSS dmenu-launcher.sh + +# Try manual integration instead +source /path/to/PowerManagement/integrations/mymenu_integration.sh +``` + +### Permissions Issues + +```bash +# Add user to required groups +sudo usermod -aG gpio $USER +sudo usermod -aG i2c $USER + +# Make scripts executable +chmod +x integrations/*.sh +``` + +--- + +## ๐Ÿ“š Related Documentation + +- [SENSOR_MONITORING.md](SENSOR_MONITORING.md) - Sensor detection and fan control +- [UNIVERSAL_HARDWARE.md](UNIVERSAL_HARDWARE.md) - Hardware compatibility +- [INTEGRATION_PLAN.md](INTEGRATION_PLAN.md) - Integration roadmap +- [README.md](../README.md) - Main documentation + +--- + +## ๐ŸŽฏ Future Integration Ideas + +- **Polybar Module** - Show metrics in polybar +- **i3status Integration** - Display in i3 status bar +- **Conky Widget** - Real-time monitoring widget +- **Notification Daemon** - Desktop notifications for alerts +- **Web Dashboard** - Browser-based monitoring +- **Grafana Integration** - Professional metrics visualization +- **Home Assistant** - Smart home integration +- **Prometheus Exporter** - Metrics collection + +--- + +## ๐Ÿ’ก Contributing + +Want to add integration for another tool? See [INTEGRATION_PLAN.md](INTEGRATION_PLAN.md) for guidelines. + +**Integration Checklist:** +- [ ] Create integration script in `integrations/` +- [ ] Add documentation to this file +- [ ] Test on multiple systems +- [ ] Update [INTEGRATION_PLAN.md](INTEGRATION_PLAN.md) +- [ ] Add example usage +- [ ] Create troubleshooting section diff --git a/docs/INTEGRATION_PLAN.md b/docs/INTEGRATION_PLAN.md new file mode 100644 index 0000000..a8a22df --- /dev/null +++ b/docs/INTEGRATION_PLAN.md @@ -0,0 +1,211 @@ +# Integration Plan: PowerManagement + claude-tools-monitor + MyMenu + +## ๐ŸŽฏ Overview + +Integration roadmap for connecting three projects into unified ecosystem: +- **PowerManagement** - Universal power/thermal/sensor management +- **claude-tools-monitor** - Claude AI monitoring & automation +- **MyMenu** - dmenu launcher for 3-PC development ecosystem + +## ๐Ÿ”— Integration Possibilities + +### 1. **MyMenu + PowerManagement Integration** โญ PRIORITY + +**Problem:** MyMenu has Q9550 thermal management built-in, but it's specific to that CPU. + +**Solution:** Replace with universal PowerManagement system + +**Benefits:** +- Universal CPU support (not just Q9550) +- GPU monitoring added to MyMenu +- Fan control from dmenu +- Real-time sensor display +- Better thermal management + +**Implementation:** +```bash +# MyMenu category: "๐ŸŒก๏ธ Power & Thermal" +โ”œโ”€โ”€ Performance Mode +โ”œโ”€โ”€ Balanced Mode +โ”œโ”€โ”€ Power Save Mode +โ”œโ”€โ”€ Show GPU Status +โ”œโ”€โ”€ Show All Sensors +โ”œโ”€โ”€ Fan Control +โ””โ”€โ”€ Monitoring Service +``` + +**Files to modify:** +- `MyMenu/dmenu-launcher.sh` - Add PowerManagement category +- Create integration script: `MyMenu/integrations/power_management.sh` + +--- + +### 2. **claude-tools-monitor + PowerManagement Integration** + +**Use Case:** Monitor system resources while Claude is running + +**Benefits:** +- Track GPU/CPU usage during AI inference +- Auto thermal throttling if Claude heats up system +- Log system metrics alongside Claude activity +- Alert if temps too high during long Claude sessions + +**Implementation:** +```python +# claude_monitor.py enhancement +from power_management.sensors import UniversalGPUMonitor, UniversalSensorDetector + +class ClaudeMonitorEnhanced: + def __init__(self): + self.gpu_monitor = UniversalGPUMonitor() + self.sensor_detector = UniversalSensorDetector() + + def log_system_metrics(self): + # Log CPU temp, GPU temp, fan speed + # alongside Claude activity + pass + + def check_thermal_throttle(self): + # If CPU > 80ยฐC, warn user or throttle + pass +``` + +**Files to create:** +- `claude-tools-monitor/integrations/power_integration.py` + +--- + +### 3. **Cross-Project Monitoring Dashboard** + +**Concept:** Unified monitoring for all 3 PCs + Claude + Power + +**Architecture:** +``` +MyMenu (dmenu interface) + โ”œโ”€โ”€ PowerManagement (sensors/GPU/fans) + โ”œโ”€โ”€ claude-tools-monitor (Claude status) + โ”œโ”€โ”€ 3-PC monitoring (LLMS, HAS, Aspire) + โ””โ”€โ”€ Unified dashboard +``` + +**Implementation:** +```bash +# New script: unified-monitor.sh +# Shows: +# - All 3 PC statuses +# - PowerManagement metrics +# - Claude session status +# - Combined thermal/power view +``` + +--- + +### 4. **Thermal-Aware Claude Automation** + +**Smart Feature:** Pause Claude if system overheats + +**Logic:** +```python +# In claude_monitor.py +if cpu_temp > 85ยฐC: + pause_claude_session() + trigger_cooling() + wait_until_temp_drops() + resume_claude_session() +``` + +**Benefits:** +- Prevents thermal shutdowns during long AI sessions +- Protects Q9550 from overheating +- Automatic recovery + +--- + +## ๐Ÿš€ Implementation Priority + +### Phase 1: MyMenu Integration (Highest Impact) +1. โœ… Add PowerManagement category to dmenu-launcher.sh +2. โœ… Create integration wrapper scripts +3. โœ… Replace old Q9550-specific thermal with universal system +4. โœ… Test on Aspire PC (Q9550) + +### Phase 2: claude-tools-monitor Enhancement +1. โœ… Add system metrics logging to claude_monitor.py +2. โœ… Thermal throttling for Claude sessions +3. โœ… Integration with PowerManagement monitoring service + +### Phase 3: Unified Dashboard +1. โœ… Create unified monitoring script +2. โœ… dmenu integration for dashboard +3. โœ… Real-time updates + +## ๐Ÿ“ฆ New Files to Create + +``` +MyMenu/ +โ”œโ”€โ”€ integrations/ +โ”‚ โ”œโ”€โ”€ power_management.sh # PowerManagement wrapper +โ”‚ โ””โ”€โ”€ unified_monitor.sh # Combined monitoring + +claude-tools-monitor/ +โ”œโ”€โ”€ integrations/ +โ”‚ โ”œโ”€โ”€ power_integration.py # System metrics +โ”‚ โ””โ”€โ”€ thermal_throttle.py # Thermal protection + +PowerManagement/ +โ”œโ”€โ”€ integrations/ +โ”‚ โ”œโ”€โ”€ mymenu_integration.sh # MyMenu hooks +โ”‚ โ””โ”€โ”€ claude_monitor_hooks.py # Claude integration +``` + +## ๐ŸŽฏ Expected Benefits + +### For MyMenu Users: +- โœ… Universal hardware support (not just Q9550) +- โœ… GPU monitoring added +- โœ… Better thermal management +- โœ… Fan control from dmenu + +### For claude-tools-monitor Users: +- โœ… System resource tracking +- โœ… Thermal protection during AI work +- โœ… Better logging + +### For PowerManagement Users: +- โœ… Easy access via dmenu +- โœ… Claude-aware power management +- โœ… Multi-PC coordination + +## ๐Ÿ”ง Configuration + +All integrations will use shared config: +```bash +# ~/.config/ecosystem/integration.conf +POWER_MGMT_DIR="/path/to/PowerManagement" +MYMENU_DIR="/path/to/MyMenu" +CLAUDE_MONITOR_DIR="/path/to/claude-tools-monitor" + +# Enable integrations +ENABLE_POWER_INTEGRATION=true +ENABLE_THERMAL_THROTTLE=true +ENABLE_UNIFIED_DASHBOARD=true +``` + +## ๐Ÿ“Š Success Metrics + +- โœ… MyMenu can launch PowerManagement features +- โœ… Claude monitor logs system metrics +- โœ… Thermal throttling prevents overheating +- โœ… Unified dashboard shows all data +- โœ… Works across all 3 PCs (Aspire, LLMS, HAS) + +## ๐ŸŽ‰ Timeline + +- **Week 1:** MyMenu integration +- **Week 2:** claude-tools-monitor enhancement +- **Week 3:** Unified dashboard +- **Week 4:** Testing & documentation + +--- + +**Next Step:** Implement Phase 1 - MyMenu Integration diff --git a/docs/SENSOR_MONITORING.md b/docs/SENSOR_MONITORING.md new file mode 100644 index 0000000..188f626 --- /dev/null +++ b/docs/SENSOR_MONITORING.md @@ -0,0 +1,466 @@ +# Advanced Sensor Monitoring & Fan Control + +## ๐ŸŒก๏ธ Overview + +Version 3.1 adds **professional-grade sensor monitoring and fan control** capabilities: + +- ๐ŸŽฎ **GPU Monitoring** - NVIDIA, AMD, Intel (temperature, fan speed, power) +- ๐Ÿ” **Universal Sensor Detection** - ALL system sensors (even atypical motherboards) +- ๐Ÿ’จ **Fan Control** - CPU & GPU fans (PWM control, automatic adjustment) +- ๐Ÿ“Š **Monitoring Service** - Real-time daemon with auto fan control & alerts +- ๐Ÿญ **All-in-One PC Support** - Works on difficult configurations (Acer, Dell AIO, etc.) + +## ๐ŸŽฎ GPU Monitoring + +### Supported GPUs + +- **NVIDIA** - via nvidia-smi (GeForce, Quadro, Tesla) +- **AMD** - via sysfs hwmon (Radeon, RX series) +- **Intel** - via sysfs (integrated graphics) + +### Features + +```python +from sensors.gpu_monitor import UniversalGPUMonitor + +monitor = UniversalGPUMonitor() + +# Get all GPU metrics +for metrics in monitor.get_all_metrics(): + print(f"GPU: {metrics.name}") + print(f" Temperature: {metrics.temperature}ยฐC") + print(f" Fan Speed: {metrics.fan_speed}%") + print(f" Fan RPM: {metrics.fan_rpm}") + print(f" Power: {metrics.power_usage}W / {metrics.power_limit}W") + print(f" Utilization: {metrics.utilization}%") + print(f" Memory: {metrics.memory_used}MB / {metrics.memory_total}MB") + +# Generate report +print(monitor.generate_report()) +``` + +**Output Example:** +``` +============================================================ +๐ŸŽฎ GPU MONITORING REPORT +============================================================ + +๐Ÿ“Š Detected GPUs: 2 + +GPU #0: NVIDIA GeForce RTX 3080 (NVIDIA) + ๐ŸŒก๏ธ Temperature: 62ยฐC + ๐Ÿ’จ Fan: 45% (1850 RPM) + โšก Power: 245W / 320W + ๐Ÿ“Š Utilization: 87% + ๐Ÿ’พ Memory: 8245MB / 10240MB (80%) + +GPU #1: AMD Radeon RX 6800 (AMD) + ๐ŸŒก๏ธ Temperature: 58ยฐC + ๐Ÿ’จ Fan: 40% (1650 RPM) + โšก Power: 180W + +============================================================ +``` + +### Command Line + +```bash +# Show all GPU metrics +python3 src/sensors/gpu_monitor.py +``` + +## ๐Ÿ” Universal Sensor Detection + +Detects **ALL** sensors in your system: + +- ๐ŸŒก๏ธ **Temperature** - CPU, GPU, motherboard, drives +- ๐Ÿ’จ **Fans** - All fan sensors with RPM readings +- โšก **Voltage** - CPU, RAM, motherboard voltages +- ๐Ÿ”Œ **Power** - Package power, GPU power +- ๐Ÿ”‹ **Battery** - Voltage, current, power, energy (laptops) + +### Features + +```python +from sensors.universal_sensor_detector import UniversalSensorDetector + +detector = UniversalSensorDetector() + +# Get all temperature sensors +temps = detector.get_temperature_sensors() +for sensor in temps: + print(f"{sensor.label}: {sensor.value}ยฐC [{sensor.chip}]") + +# Get all fan sensors +fans = detector.get_fan_sensors() +for sensor in fans: + print(f"{sensor.label}: {sensor.value} RPM [{sensor.chip}]") + +# Generate comprehensive report +print(detector.generate_report()) +``` + +**Output Example:** +``` +====================================================================== +๐Ÿ” UNIVERSAL SENSOR DETECTION REPORT +====================================================================== + +๐Ÿ“Š Total Sensors Detected: 42 + +๐ŸŒก๏ธ TEMPERATURE SENSORS (12) +---------------------------------------------------------------------- + โ€ข Package id 0 : 45.0 ยฐC [coretemp-isa-0000] + โ€ข Core 0 : 42.0 ยฐC [coretemp-isa-0000] + โ€ข Core 1 : 43.0 ยฐC [coretemp-isa-0000] + โ€ข Core 2 : 44.0 ยฐC [coretemp-isa-0000] + โ€ข Core 3 : 45.0 ยฐC [coretemp-isa-0000] + โ€ข edge : 62.0 ยฐC [amdgpu-pci-0300] + โ€ข junction : 68.0 ยฐC [amdgpu-pci-0300] + โ€ข mem : 56.0 ยฐC [amdgpu-pci-0300] + โ€ข Motherboard : 38.0 ยฐC [nct6775-isa-0290] + โ€ข CPU : 45.0 ยฐC [nct6775-isa-0290] + โ€ข SATA 1 : 35.0 ยฐC [drivetemp-scsi-0-0] + โ€ข SATA 2 : 36.0 ยฐC [drivetemp-scsi-1-0] + +๐Ÿ’จ FAN SENSORS (6) +---------------------------------------------------------------------- + โ€ข CPU Fan : 1245 RPM [nct6775-isa-0290] + โ€ข System Fan 1 : 865 RPM [nct6775-isa-0290] + โ€ข System Fan 2 : 920 RPM [nct6775-isa-0290] + โ€ข GPU Fan : 1850 RPM [amdgpu-pci-0300] + โ€ข PSU Fan : 450 RPM [corsairpsu-hid-3-2] + โ€ข AIO Pump : 2400 RPM [nct6775-isa-0290] + +โšก VOLTAGE SENSORS (8) +---------------------------------------------------------------------- + โ€ข Vcore : 1.2 V [nct6775-isa-0290] + โ€ข +12V : 12.1 V [nct6775-isa-0290] + โ€ข +5V : 5.0 V [nct6775-isa-0290] + โ€ข +3.3V : 3.3 V [nct6775-isa-0290] + โ€ข VDDCR_SOC : 0.9 V [k10temp-pci-00c3] + โ€ข Vddq : 1.35 V [nct6775-isa-0290] + +๐Ÿ”Œ POWER SENSORS (4) +---------------------------------------------------------------------- + โ€ข Package : 45.2 W [coretemp-isa-0000] + โ€ข GPU : 245.0 W [amdgpu-pci-0300] + โ€ข PSU Input : 385.0 W [corsairpsu-hid-3-2] + โ€ข PSU Output : 325.0 W [corsairpsu-hid-3-2] + +====================================================================== +``` + +### Command Line + +```bash +# Detect all sensors +python3 src/sensors/universal_sensor_detector.py +``` + +## ๐Ÿ’จ Fan Control + +Control CPU and GPU fans programmatically. + +### Supported Methods + +1. **PWM Control** (Linux sysfs) - CPU & case fans +2. **NVIDIA** (nvidia-settings) - NVIDIA GPU fans +3. **AMD** (sysfs hwmon) - AMD GPU fans + +### Features + +```python +from sensors.fan_controller import UniversalFanController + +controller = UniversalFanController() + +# Show all controllable fans +print(controller.generate_report()) + +# Set CPU fan to 60% +controller.set_pwm_fan_speed(fan_index=0, percent=60) + +# Set fan to automatic mode +controller.set_fan_auto(fan_index=0) + +# Set NVIDIA GPU fan +controller.set_nvidia_gpu_fan(gpu_index=0, percent=50) + +# Set NVIDIA GPU to auto +controller.set_nvidia_gpu_fan_auto(gpu_index=0) +``` + +**Output Example:** +``` +====================================================================== +๐Ÿ’จ FAN CONTROLLER REPORT +====================================================================== + +๐ŸŒ€ PWM Fans: 3 +---------------------------------------------------------------------- + [0] nct6775/pwm1 + Speed: 48% (122/255 PWM) - 1245 RPM + Mode: auto + Control: /sys/class/hwmon/hwmon1/pwm1 + + [1] nct6775/pwm2 + Speed: 35% (89/255 PWM) - 865 RPM + Mode: auto + Control: /sys/class/hwmon/hwmon1/pwm2 + + [2] nct6775/pwm5 + Speed: 95% (242/255 PWM) - 2400 RPM + Mode: manual + Control: /sys/class/hwmon/hwmon1/pwm5 + +๐ŸŽฎ GPU Fans: 2 +---------------------------------------------------------------------- + โ€ข NVIDIA GeForce RTX 3080 Fan (NVIDIA) + โ€ข AMD Radeon RX 6800 Fan (AMD) + +====================================================================== +``` + +### Command Line + +```bash +# Show fan status +python3 src/sensors/fan_controller.py status + +# Set fan speed (requires sudo) +sudo python3 src/sensors/fan_controller.py set 0 60 + +# Set fan to automatic +sudo python3 src/sensors/fan_controller.py auto 0 + +# Set NVIDIA GPU fan +python3 src/sensors/fan_controller.py nvidia 0 50 +``` + +### โš ๏ธ Requirements + +**For PWM fan control:** +- Root access (sudo) +- lm-sensors configured +- Fan control enabled in BIOS + +**Setup:** +```bash +# Install lm-sensors +sudo apt install lm-sensors + +# Detect sensors +sudo sensors-detect + +# Enable fan control (if needed) +sudo pwmconfig +``` + +## ๐Ÿ“Š Monitoring Service + +Real-time monitoring daemon with **automatic fan control** and alerts. + +### Features + +- โœ… Real-time monitoring (CPU, GPU, fans, power) +- โœ… Automatic fan speed adjustment based on temperature +- โœ… Thermal alerts (warning, critical, emergency) +- โœ… JSON logging for data analysis +- โœ… Works on atypical systems (all-in-one PCs, laptops) + +### Usage + +```bash +# Start monitoring service (5 second interval) +python3 src/services/monitoring_service.py + +# Custom interval (10 seconds) +python3 src/services/monitoring_service.py --interval 10 + +# Disable auto fan control +python3 src/services/monitoring_service.py --no-auto-fan + +# Custom log directory +python3 src/services/monitoring_service.py --log-dir /var/log/power-mgmt +``` + +### Auto Fan Control + +The service automatically adjusts fan speeds based on temperature: + +| Temperature Range | Fan Speed | Action | +|------------------|-----------|--------| +| < Warning (65-75ยฐC) | 30% | Low speed | +| Warning to Critical | 50% | Medium speed | +| Critical to Emergency | 75% | High speed | +| Emergency (>85ยฐC) | 100% | Maximum cooling | + +### Alerts + +The service monitors and alerts on: + +- โšก **Warning** - Temperature approaching safe limits +- โš ๏ธ **Critical** - Temperature in critical zone +- ๐Ÿšจ **Emergency** - Temperature at dangerous levels + +**Example Output:** +``` +[2025-11-18 22:45:01] ๐Ÿš€ Monitoring service started +[2025-11-18 22:45:01] CPU: Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz +[2025-11-18 22:45:01] Thermal limits: 70ยฐC / 80ยฐC / 95ยฐC +[2025-11-18 22:45:01] Interval: 5s +[2025-11-18 22:45:01] Auto fan control: โœ… Enabled +[2025-11-18 22:45:06] ๐Ÿ“Š CPU: 45.0ยฐC | GPU: 62.0ยฐC | Fan: 1245RPM +[2025-11-18 22:45:11] ๐Ÿ“Š CPU: 46.0ยฐC | GPU: 63.0ยฐC | Fan: 1250RPM +[2025-11-18 22:45:16] ๐Ÿ“Š CPU: 72.0ยฐC | GPU: 68.0ยฐC | Fan: 1450RPM +[2025-11-18 22:45:16] โšก CPU WARNING: 72.0ยฐC +[2025-11-18 22:45:21] ๐Ÿ“Š CPU: 75.0ยฐC | GPU: 70.0ยฐC | Fan: 1650RPM +[2025-11-18 22:45:21] โš ๏ธ CPU CRITICAL: 75.0ยฐC +[2025-11-18 22:45:21] โš ๏ธ CRITICAL: Setting fans to 75% +``` + +### JSON Logging + +All snapshots are logged to `/tmp/power_monitoring.json`: + +```json +[ + { + "timestamp": "2025-11-18T22:45:06", + "cpu_temp": 45.0, + "gpu_temp": 62.0, + "cpu_fan_rpm": 1245, + "gpu_fan_rpm": 1850, + "gpu_power": 245, + "cpu_power": 45.2, + "voltages": { + "Vcore": 1.2, + "+12V": 12.1, + "+5V": 5.0 + }, + "alerts": [] + } +] +``` + +## ๐Ÿญ All-in-One PC & Atypical System Support + +The sensor system is designed to work on **difficult configurations**: + +### Supported Scenarios + +โœ… **All-in-One PCs** (Acer, Dell, HP) +- Limited sensor access +- Non-standard fan configurations +- Embedded/integrated components + +โœ… **Laptops** +- Battery sensors +- Embedded controllers +- Hybrid graphics + +โœ… **Exotic Motherboards** +- Custom OEM boards +- Non-standard sensor chips +- Multiple hwmon devices + +### How It Works + +The system uses **multiple detection methods** with fallbacks: + +1. **lm-sensors** - Primary method (most comprehensive) +2. **sysfs hwmon** - Direct hardware access +3. **thermal zones** - Kernel thermal subsystem +4. **ACPI** - Battery & power supply info +5. **GPU-specific** - nvidia-smi, AMD sysfs, Intel sysfs + +**Example: All-in-One Acer System** +``` +๐Ÿ” Detected on Acer Aspire C24-865: + - CPU temp via acpi_thermal_rel + - No dedicated CPU fan sensor (embedded in case) + - GPU temp via i915 (Intel integrated) + - System fan via embedded controller + - Battery sensors (if model has battery) +``` + +## ๐Ÿ› ๏ธ Troubleshooting + +### No Sensors Detected + +```bash +# Install lm-sensors +sudo apt install lm-sensors + +# Detect sensors +sudo sensors-detect +# Answer YES to all questions + +# Test detection +sensors +``` + +### No GPU Detected + +**NVIDIA:** +```bash +# Install NVIDIA drivers +sudo apt install nvidia-driver-525 + +# Install nvidia-smi +nvidia-smi +``` + +**AMD:** +```bash +# Check if amdgpu driver loaded +lsmod | grep amdgpu + +# AMD sysfs should be available +ls /sys/class/drm/card*/device/hwmon/*/temp*_input +``` + +### Fan Control Not Working + +```bash +# Check if PWM control available +ls /sys/class/hwmon/hwmon*/pwm* + +# Enable fan control (if supported) +sudo pwmconfig + +# Set manual mode +echo 1 | sudo tee /sys/class/hwmon/hwmon1/pwm1_enable + +# Set fan speed +echo 128 | sudo tee /sys/class/hwmon/hwmon1/pwm1 # 50% +``` + +### Permission Denied + +```bash +# Run with sudo for fan control +sudo python3 src/sensors/fan_controller.py set 0 60 + +# Or add user to appropriate group +sudo usermod -aG gpio $USER +sudo usermod -aG i2c $USER +``` + +## ๐Ÿ“š API Reference + +See source code for detailed API: +- `src/sensors/gpu_monitor.py` - GPU monitoring +- `src/sensors/universal_sensor_detector.py` - Sensor detection +- `src/sensors/fan_controller.py` - Fan control +- `src/services/monitoring_service.py` - Monitoring service + +## ๐ŸŽฏ Next Steps + +1. **Install lm-sensors** for full sensor support +2. **Test detection** with provided scripts +3. **Configure fan control** if needed +4. **Run monitoring service** for continuous monitoring + +For more information, see [UNIVERSAL_HARDWARE.md](UNIVERSAL_HARDWARE.md) diff --git a/docs/UNIVERSAL_HARDWARE.md b/docs/UNIVERSAL_HARDWARE.md new file mode 100644 index 0000000..df67df8 --- /dev/null +++ b/docs/UNIVERSAL_HARDWARE.md @@ -0,0 +1,378 @@ +# Universal Hardware Support + +## ๐ŸŒ Overview + +Version 3.0 introduces **universal hardware support**, making PowerManagement portable across different CPUs, GPUs, and system configurations. The system now automatically detects your hardware and adapts thermal thresholds, frequency ranges, and power management strategies accordingly. + +## ๐Ÿš€ What's New in V3.0 + +### โœ… **No More Hardcoded Paths** +- All scripts use dynamic path detection +- Works from any installation directory +- No user-specific paths like `/home/username/` + +### ๐Ÿ–ฅ๏ธ **Universal CPU Support** +Previously: Only Intel Core 2 Quad Q9550 +Now: Supports: +- **Intel**: Core 2, Nehalem, Sandy Bridge, Ivy Bridge, Haswell, Broadwell, Skylake+ +- **AMD**: K8, K10, Bulldozer, Zen (Ryzen) +- **Old Hardware**: Special support for legacy CPUs (2006+) + +### ๐ŸŽฎ **Universal GPU Support** +Previously: Only AMD Radeon RV710 at `/sys/class/drm/card1` +Now: Auto-detects: +- AMD GPUs (any card number) +- NVIDIA GPUs +- Intel integrated graphics +- Gracefully handles missing GPU + +### ๐ŸŒก๏ธ **Adaptive Thermal Management** +- Thermal thresholds automatically adjusted based on CPU specs +- **Old CPUs** (Core 2): Max 85ยฐC emergency threshold +- **Modern Intel**: Max 100ยฐC emergency threshold +- **AMD Ryzen**: Max 95ยฐC emergency threshold +- Percentile-based thresholds (65%, 75%, 85%, 95% of CPU max) + +## ๐Ÿ“Š Hardware Detection + +The system automatically detects: + +```bash +python3 src/hardware/hardware_detector.py +``` + +**Output Example:** +``` +============================================================ +๐Ÿ” HARDWARE DETECTION REPORT +============================================================ + +๐Ÿ–ฅ๏ธ CPU INFORMATION: + Vendor: INTEL + Model: Intel(R) Core(TM)2 Quad CPU Q9550 @ 2.83GHz + Generation: core2 + Cores: 4 + Frequency: 1333-2833 MHz (current: 2833 MHz) + MSR Support: โœ… Yes + CPUFreq Support: โœ… Yes + Thermal Max Safe: 85ยฐC + +๐ŸŽฎ GPU INFORMATION: + Vendor: AMD + Model: AMD Radeon RV710 + Device Path: /sys/class/drm/card1 + Power Profile Support: โœ… Yes + Power Cap Support: โŒ No + +๐ŸŒก๏ธ THERMAL INFORMATION: + Thermal Zones: 1 + Current Temperature: 45ยฐC + Maximum Safe Temp: 85ยฐC +``` + +## ๐Ÿ”ง Universal CPU Frequency Manager + +### Automatic CPU Detection + +The new universal manager automatically: +1. Detects your CPU model and vendor +2. Determines available frequency control methods +3. Calculates optimal frequencies for each power profile +4. Uses CPU-specific MSR multipliers (if available) + +### Usage + +```bash +# Show status with auto-detected settings +python3 src/frequency/universal_cpu_manager.py status + +# Detect hardware +python3 src/frequency/universal_cpu_manager.py detect + +# Set profile (works on ANY CPU) +python3 src/frequency/universal_cpu_manager.py profile performance +python3 src/frequency/universal_cpu_manager.py profile balanced +python3 src/frequency/universal_cpu_manager.py profile powersave +python3 src/frequency/universal_cpu_manager.py profile emergency +``` + +### Supported Control Methods + +1. **cpufreq** (preferred for most systems) + - Standard Linux cpufreq subsystem + - Works with acpi-cpufreq driver + +2. **intel_pstate** (modern Intel CPUs) + - Built-in Intel P-state driver + - Skylake and newer + +3. **MSR** (legacy CPUs) + - Direct Model Specific Register access + - Core 2 Quad with known multipliers + +4. **cpupower** (fallback) + - Command-line utility + - Universal but requires manual setup + +## โš™๏ธ Configuration System + +### Dynamic Configuration + +The system now uses a centralized configuration that adapts to your hardware: + +```python +from config.power_config import PowerConfig + +config = PowerConfig() +config.print_config() +``` + +**Example Output:** +``` +============================================================ +โš™๏ธ POWER MANAGEMENT CONFIGURATION +============================================================ + +๐Ÿ“ PATHS: + Install Dir: /opt/PowerManagement + Scripts Dir: /opt/PowerManagement/scripts + Config File: /etc/power-management/config.json + +๐ŸŒก๏ธ THERMAL CONFIG: + Comfort: < 55ยฐC (65% of max safe) + Warning: 63ยฐC (75% of max safe) + Critical: 72ยฐC (85% of max safe) + Emergency: 80ยฐC (95% of max safe) + +โšก FREQUENCY CONFIG: + Range: 800-3500 MHz + Performance: 3500 MHz + Balanced: 2690 MHz + Powersave: 2150 MHz + Emergency: 800 MHz +``` + +### Configuration Locations + +The system searches for config in this order: +1. `/etc/power-management/config.json` (system-wide) +2. `~/.config/power-management/config.json` (user-specific) +3. `./config/config.json` (local installation) + +## ๐Ÿ”Œ Installation Flexibility + +### Install Anywhere + +The refactored system works from any directory: + +```bash +# Clone to any location +git clone https://github.com/milhy545/PowerManagement.git /opt/PowerManagement +cd /opt/PowerManagement + +# Or to your home directory +git clone https://github.com/milhy545/PowerManagement.git ~/my-power-mgmt +cd ~/my-power-mgmt + +# Scripts automatically detect their location +./scripts/performance_manager.sh status +``` + +### No Hardcoded Dependencies + +**Before (V2.0):** +```bash +# โŒ Would fail on different systems +SCRIPT="/home/milhy777/performance_manager.sh" +``` + +**After (V3.0):** +```bash +# โœ… Works anywhere +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +INSTALL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +SCRIPT="$INSTALL_DIR/scripts/performance_manager.sh" +``` + +## ๐Ÿงช Testing Universal Support + +### Run Universal Tests + +```bash +# Test all universal features +bash tests/test_universal_system.sh +``` + +**Test Coverage:** +- โœ… Hardware detection +- โœ… Configuration system +- โœ… Universal CPU manager +- โœ… Thermal manager adaptation +- โœ… Dynamic path resolution +- โœ… GPU auto-detection +- โœ… Hardcoded path removal verification + +### Manual Testing + +```bash +# 1. Test hardware detection +python3 src/hardware/hardware_detector.py + +# 2. Test configuration +python3 src/config/power_config.py + +# 3. Test CPU manager +python3 src/frequency/universal_cpu_manager.py status + +# 4. Test performance manager +./scripts/performance_manager.sh status +``` + +## ๐Ÿ“ Migration from V2.0 + +### If You're Using Q9550 + +**Good news:** Everything still works! The system detects Q9550 and uses the original optimized settings. + +### If You're Using Different Hardware + +**V3.0 automatically:** +1. Detects your CPU model +2. Calculates safe frequency ranges +3. Sets appropriate thermal thresholds +4. Finds your GPU device path +5. Uses compatible control methods + +**No manual configuration required!** + +## ๐ŸŽฏ Hardware Compatibility Matrix + +| CPU Family | Frequency Control | Thermal Mgmt | Status | +|------------|------------------|--------------|--------| +| Intel Core 2 (2006-2011) | MSR/cpufreq | โœ… 85ยฐC max | Tested | +| Intel Core i (1st-5th gen) | cpufreq | โœ… 95ยฐC max | Compatible | +| Intel Core i (6th+ gen) | intel_pstate | โœ… 100ยฐC max | Compatible | +| AMD K8/K10 | cpufreq | โœ… 70ยฐC max | Compatible | +| AMD Bulldozer | cpufreq | โœ… 75ยฐC max | Compatible | +| AMD Zen/Ryzen | cpufreq | โœ… 95ยฐC max | Compatible | + +| GPU Vendor | Power Control | Auto-detect | Status | +|------------|---------------|-------------|--------| +| AMD | power_profile | โœ… Yes | Tested | +| NVIDIA | power_cap | โš ๏ธ Limited | Compatible | +| Intel iGPU | Basic | โœ… Yes | Compatible | + +## ๐Ÿšจ Limitations + +### CI/GitHub Actions +In CI environments, hardware features are limited: +- No MSR access +- No cpufreq control +- No thermal zones +- System runs in simulation mode + +### Root Requirements +Some features require root: +- MSR register access +- cpufreq governor changes +- GPU power profile changes + +**Solution:** Use `sudo` or add user to appropriate groups. + +## ๐Ÿ“š Architecture + +### New Modules + +1. **hardware/hardware_detector.py** + - Universal CPU detection + - GPU detection + - Thermal capability detection + +2. **config/power_config.py** + - Dynamic path resolution + - Hardware-adaptive configuration + - Cross-platform settings + +3. **frequency/universal_cpu_manager.py** + - Multi-vendor CPU support + - Automatic method selection + - Adaptive frequency profiles + +### Refactored Components + +- `daemons/custom-power-profiles-daemon.py` - Uses PowerConfig +- `scripts/performance_manager.sh` - Auto-detects GPU, uses universal CPU manager +- `scripts/ai_process_manager.sh` - Dynamic path resolution +- `scripts/smart_thermal_manager.py` - Adaptive thermal thresholds + +## ๐Ÿ› ๏ธ Troubleshooting + +### Hardware Not Detected + +```bash +# Check detection +python3 src/hardware/hardware_detector.py + +# If CPU vendor is UNKNOWN +# -> May be running in VM or limited environment +# -> System will use conservative defaults + +# If GPU not found +# -> Power management will skip GPU features +# -> Core CPU/thermal management still works +``` + +### Frequency Control Not Working + +```bash +# Check available methods +python3 src/frequency/universal_cpu_manager.py status + +# If control method is "none": +# 1. Check if cpufreq is available: ls /sys/devices/system/cpu/cpu0/cpufreq +# 2. Check if MSR module loaded: lsmod | grep msr +# 3. Try loading MSR: sudo modprobe msr +# 4. Install cpupower: sudo apt install linux-tools-generic +``` + +### Path Issues + +```bash +# If scripts can't find modules: +# 1. Check installation directory +echo $INSTALL_DIR + +# 2. Verify Python path +python3 -c "import sys; print(sys.path)" + +# 3. Test import +python3 -c "import sys; sys.path.insert(0, 'src'); from hardware.hardware_detector import HardwareDetector" +``` + +## ๐Ÿ“– Additional Resources + +- [README.md](../README.md) - Main documentation +- [PORTFOLIO.md](../PORTFOLIO.md) - Technical deep-dive +- [POWER_MODES_TABLE.md](POWER_MODES_TABLE.md) - Power mode reference +- [Test Suite](../tests/) - Comprehensive tests + +## ๐Ÿ’ก Contributing + +To add support for new hardware: + +1. Update `hardware_detector.py` with new CPU/GPU detection +2. Add MSR multipliers (if applicable) to `universal_cpu_manager.py` +3. Test on your hardware +4. Submit pull request with hardware specs + +## ๐ŸŽ‰ Summary + +**Version 3.0 transforms PowerManagement from a single-system tool into a universal power management solution.** + +- โœ… Works on old and new hardware +- โœ… No configuration required +- โœ… Portable across systems +- โœ… Gracefully handles missing features +- โœ… Maintains backward compatibility + +**Install once, run anywhere!** diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..cf695a0 --- /dev/null +++ b/install.sh @@ -0,0 +1,231 @@ +#!/bin/bash + +#============================================================================== +# PowerManagement Installation Script +# One-click installation for universal power management system +#============================================================================== + +set -euo pipefail + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Installation directory +INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +#============================================================================== +# Header +#============================================================================== + +echo -e "${BLUE}============================================${NC}" +echo -e "${BLUE} PowerManagement Installation${NC}" +echo -e "${BLUE} Version 3.1 - Universal Edition${NC}" +echo -e "${BLUE}============================================${NC}" +echo "" +echo -e "${YELLOW}Installation directory: $INSTALL_DIR${NC}" +echo "" + +#============================================================================== +# Check Requirements +#============================================================================== + +echo -e "${BLUE}๐Ÿ“‹ Checking requirements...${NC}" +echo "" + +# Python version +PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}') +echo -e " ๐Ÿ Python: $PYTHON_VERSION" + +if ! python3 -c 'import sys; exit(0 if sys.version_info >= (3, 6) else 1)' 2>/dev/null; then + echo -e "${RED} โŒ Python 3.6+ required${NC}" + exit 1 +fi + +# Check if running as root +if [ "$EUID" -eq 0 ]; then + echo -e "${YELLOW} โš ๏ธ Running as root - will install system-wide${NC}" + INSTALL_MODE="system" +else + echo -e "${GREEN} โœ… Running as user - will install for current user${NC}" + INSTALL_MODE="user" +fi + +echo "" + +#============================================================================== +# Install Python Dependencies +#============================================================================== + +echo -e "${BLUE}๐Ÿ“ฆ Installing Python dependencies...${NC}" +echo "" + +# Check psutil +if python3 -c 'import psutil' 2>/dev/null; then + echo -e " โœ… psutil already installed" +else + echo -e " ๐Ÿ“ฅ Installing psutil..." + if [ "$INSTALL_MODE" = "system" ]; then + pip3 install psutil || apt-get install -y python3-psutil + else + pip3 install --user psutil + fi + echo -e " โœ… psutil installed" +fi + +echo "" + +#============================================================================== +# Install Optional Dependencies +#============================================================================== + +echo -e "${BLUE}๐Ÿ”ง Checking optional dependencies...${NC}" +echo "" + +# lm-sensors +if command -v sensors >/dev/null 2>&1; then + echo -e " โœ… lm-sensors installed" +else + echo -e " โš ๏ธ lm-sensors not found (recommended for full sensor support)" + echo -e " Install with: sudo apt install lm-sensors" +fi + +# nvidia-smi +if command -v nvidia-smi >/dev/null 2>&1; then + echo -e " โœ… nvidia-smi installed (NVIDIA GPU support enabled)" +else + echo -e " โ„น๏ธ nvidia-smi not found (NVIDIA GPU support disabled)" +fi + +# nvidia-settings +if command -v nvidia-settings >/dev/null 2>&1; then + echo -e " โœ… nvidia-settings installed (NVIDIA fan control enabled)" +else + echo -e " โ„น๏ธ nvidia-settings not found (NVIDIA fan control disabled)" +fi + +echo "" + +#============================================================================== +# Hardware Detection +#============================================================================== + +echo -e "${BLUE}๐Ÿ” Detecting hardware...${NC}" +echo "" + +export PYTHONPATH="$INSTALL_DIR/src:${PYTHONPATH:-}" + +# Run hardware detection +python3 "$INSTALL_DIR/src/hardware/hardware_detector.py" 2>/dev/null | head -20 || echo " โ„น๏ธ Limited hardware detection (may need root)" + +echo "" + +#============================================================================== +# Create Symlinks (Optional) +#============================================================================== + +echo -e "${BLUE}๐Ÿ”— Creating convenient shortcuts...${NC}" +echo "" + +# Create local bin directory if needed +if [ "$INSTALL_MODE" = "user" ]; then + mkdir -p "$HOME/.local/bin" + BIN_DIR="$HOME/.local/bin" +else + BIN_DIR="/usr/local/bin" +fi + +# Create symlinks +echo -e " Creating shortcuts in $BIN_DIR..." + +# Performance manager +if [ -f "$INSTALL_DIR/scripts/performance_manager.sh" ]; then + ln -sf "$INSTALL_DIR/scripts/performance_manager.sh" "$BIN_DIR/power-manager" 2>/dev/null || echo " โš ๏ธ Could not create power-manager symlink (may need sudo)" +fi + +# GPU monitor +cat > "$BIN_DIR/gpu-monitor" << EOF +#!/bin/bash +PYTHONPATH="$INSTALL_DIR/src" python3 "$INSTALL_DIR/src/sensors/gpu_monitor.py" "\$@" +EOF +chmod +x "$BIN_DIR/gpu-monitor" 2>/dev/null || echo " โš ๏ธ Could not create gpu-monitor (may need sudo)" + +# Sensor detector +cat > "$BIN_DIR/sensor-detector" << EOF +#!/bin/bash +PYTHONPATH="$INSTALL_DIR/src" python3 "$INSTALL_DIR/src/sensors/universal_sensor_detector.py" "\$@" +EOF +chmod +x "$BIN_DIR/sensor-detector" 2>/dev/null || echo " โš ๏ธ Could not create sensor-detector (may need sudo)" + +# Fan controller +cat > "$BIN_DIR/fan-control" << EOF +#!/bin/bash +PYTHONPATH="$INSTALL_DIR/src" python3 "$INSTALL_DIR/src/sensors/fan_controller.py" "\$@" +EOF +chmod +x "$BIN_DIR/fan-control" 2>/dev/null || echo " โš ๏ธ Could not create fan-control (may need sudo)" + +echo -e " โœ… Shortcuts created" + +# Add to PATH message +if [ "$INSTALL_MODE" = "user" ]; then + if ! echo "$PATH" | grep -q "$HOME/.local/bin"; then + echo -e " ${YELLOW}โš ๏ธ Add $HOME/.local/bin to PATH:${NC}" + echo -e " ${BLUE}echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.bashrc${NC}" + echo -e " ${BLUE}source ~/.bashrc${NC}" + fi +fi + +echo "" + +#============================================================================== +# Run Tests +#============================================================================== + +echo -e "${BLUE}๐Ÿงช Running tests...${NC}" +echo "" + +if [ -f "$INSTALL_DIR/tests/test_sensors.sh" ]; then + bash "$INSTALL_DIR/tests/test_sensors.sh" || echo -e "${YELLOW} โš ๏ธ Some tests failed (may be expected in limited environments)${NC}" +else + echo -e "${YELLOW} โš ๏ธ Test suite not found${NC}" +fi + +echo "" + +#============================================================================== +# Installation Complete +#============================================================================== + +echo -e "${GREEN}============================================${NC}" +echo -e "${GREEN} โœ… Installation Complete!${NC}" +echo -e "${GREEN}============================================${NC}" +echo "" +echo -e "${BLUE}๐Ÿ“š Quick Start:${NC}" +echo "" +echo -e " ${YELLOW}Show GPU metrics:${NC}" +echo -e " gpu-monitor" +echo "" +echo -e " ${YELLOW}Show all sensors:${NC}" +echo -e " sensor-detector" +echo "" +echo -e " ${YELLOW}Show fan status:${NC}" +echo -e " fan-control status" +echo "" +echo -e " ${YELLOW}Set power profile:${NC}" +echo -e " power-manager performance" +echo -e " power-manager balanced" +echo -e " power-manager powersave" +echo "" +echo -e " ${YELLOW}Start monitoring service:${NC}" +echo -e " PYTHONPATH=\"$INSTALL_DIR/src\" python3 $INSTALL_DIR/src/services/monitoring_service.py" +echo "" +echo -e "${BLUE}๐Ÿ“– Documentation:${NC}" +echo -e " - docs/SENSOR_MONITORING.md - Sensor & fan control guide" +echo -e " - docs/UNIVERSAL_HARDWARE.md - Hardware compatibility" +echo -e " - README.md - Main documentation" +echo "" +echo -e "${GREEN}๐ŸŽ‰ Enjoy PowerManagement!${NC}" +echo "" diff --git a/integrations/mymenu_integration.sh b/integrations/mymenu_integration.sh new file mode 100755 index 0000000..fdf902b --- /dev/null +++ b/integrations/mymenu_integration.sh @@ -0,0 +1,175 @@ +#!/bin/bash + +#============================================================================== +# PowerManagement Integration for MyMenu +# Provides dmenu-compatible interface for power management +#============================================================================== + +set -euo pipefail + +# Detect PowerManagement installation +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +POWER_MGMT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +SRC_DIR="$POWER_MGMT_DIR/src" + +export PYTHONPATH="$SRC_DIR:${PYTHONPATH:-}" + +#============================================================================== +# Functions +#============================================================================== + +show_power_menu() { + echo "๐Ÿ”ฅ Performance Mode" + echo "โš–๏ธ Balanced Mode" + echo "๐Ÿ”‹ Power Save Mode" + echo "๐Ÿšจ Emergency Mode" + echo "---" + echo "๐Ÿ“Š Show Current Status" + echo "๐ŸŽฎ Show GPU Metrics" + echo "๐ŸŒก๏ธ Show All Sensors" + echo "๐Ÿ’จ Show Fan Status" + echo "---" + echo "๐Ÿ”ง Fan Control Menu" + echo "๐Ÿ“ˆ Start Monitoring Service" + echo "๐Ÿ“– Show Documentation" +} + +show_fan_menu() { + echo "๐Ÿ’จ Fan Status" + echo "---" + echo "๐ŸŒ€ Set CPU Fan 30% (Silent)" + echo "๐ŸŒ€ Set CPU Fan 50% (Normal)" + echo "๐ŸŒ€ Set CPU Fan 75% (High)" + echo "๐ŸŒ€ Set CPU Fan 100% (Max)" + echo "---" + echo "๐Ÿ”„ Set Auto Mode" +} + +gpu_metrics() { + python3 "$SRC_DIR/sensors/gpu_monitor.py" | \ + zenity --text-info --title="GPU Metrics" --width=800 --height=600 || \ + xterm -e "python3 $SRC_DIR/sensors/gpu_monitor.py; read -p 'Press Enter to close...'" +} + +all_sensors() { + python3 "$SRC_DIR/sensors/universal_sensor_detector.py" | \ + zenity --text-info --title="All Sensors" --width=900 --height=700 || \ + xterm -e "python3 $SRC_DIR/sensors/universal_sensor_detector.py; read -p 'Press Enter to close...'" +} + +fan_status() { + python3 "$SRC_DIR/sensors/fan_controller.py" status | \ + zenity --text-info --title="Fan Status" --width=800 --height=500 || \ + xterm -e "python3 $SRC_DIR/sensors/fan_controller.py status; read -p 'Press Enter to close...'" +} + +current_status() { + "$POWER_MGMT_DIR/scripts/performance_manager.sh" status | \ + zenity --text-info --title="Power Management Status" --width=800 --height=600 || \ + xterm -e "$POWER_MGMT_DIR/scripts/performance_manager.sh status; read -p 'Press Enter to close...'" +} + +start_monitoring() { + # Start in terminal + xterm -T "Power Monitoring Service" -e \ + "python3 $SRC_DIR/services/monitoring_service.py" & +} + +show_docs() { + if command -v xdg-open >/dev/null 2>&1; then + xdg-open "$POWER_MGMT_DIR/docs/SENSOR_MONITORING.md" + else + xterm -e "less $POWER_MGMT_DIR/docs/SENSOR_MONITORING.md" + fi +} + +#============================================================================== +# Main Logic +#============================================================================== + +case "${1:-menu}" in + "menu") + # Show main power menu + show_power_menu + ;; + + "fan-menu") + # Show fan control menu + show_fan_menu + ;; + + "performance") + "$POWER_MGMT_DIR/scripts/performance_manager.sh" performance + notify-send "Power Management" "Performance mode activated" -i dialog-information + ;; + + "balanced") + "$POWER_MGMT_DIR/scripts/performance_manager.sh" balanced + notify-send "Power Management" "Balanced mode activated" -i dialog-information + ;; + + "powersave") + "$POWER_MGMT_DIR/scripts/performance_manager.sh" powersave + notify-send "Power Management" "Power save mode activated" -i dialog-information + ;; + + "emergency") + "$POWER_MGMT_DIR/scripts/performance_manager.sh" emergency + notify-send "Power Management" "Emergency mode activated" -u critical + ;; + + "status") + current_status + ;; + + "gpu") + gpu_metrics + ;; + + "sensors") + all_sensors + ;; + + "fans") + fan_status + ;; + + "fan-30") + sudo python3 "$SRC_DIR/sensors/fan_controller.py" set 0 30 + notify-send "Fan Control" "CPU fan set to 30%" -i dialog-information + ;; + + "fan-50") + sudo python3 "$SRC_DIR/sensors/fan_controller.py" set 0 50 + notify-send "Fan Control" "CPU fan set to 50%" -i dialog-information + ;; + + "fan-75") + sudo python3 "$SRC_DIR/sensors/fan_controller.py" set 0 75 + notify-send "Fan Control" "CPU fan set to 75%" -i dialog-information + ;; + + "fan-100") + sudo python3 "$SRC_DIR/sensors/fan_controller.py" set 0 100 + notify-send "Fan Control" "CPU fan set to 100%" -u critical + ;; + + "fan-auto") + sudo python3 "$SRC_DIR/sensors/fan_controller.py" auto 0 + notify-send "Fan Control" "Auto mode enabled" -i dialog-information + ;; + + "monitoring") + start_monitoring + notify-send "Power Management" "Monitoring service started" -i dialog-information + ;; + + "docs") + show_docs + ;; + + *) + echo "Unknown command: $1" + exit 1 + ;; +esac diff --git a/integrations/mymenu_patch.sh b/integrations/mymenu_patch.sh new file mode 100755 index 0000000..0f96981 --- /dev/null +++ b/integrations/mymenu_patch.sh @@ -0,0 +1,176 @@ +#!/bin/bash + +#============================================================================== +# MyMenu Integration Patch +# Adds PowerManagement category to MyMenu dmenu-launcher.sh +#============================================================================== + +set -euo pipefail + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +echo -e "${BLUE}============================================${NC}" +echo -e "${BLUE} MyMenu PowerManagement Integration${NC}" +echo -e "${BLUE}============================================${NC}" +echo "" + +# Find MyMenu installation +MYMENU_DIR="${1:-/home/user/MyMenu}" + +if [ ! -f "$MYMENU_DIR/dmenu-launcher.sh" ]; then + echo -e "${YELLOW}โŒ MyMenu not found at: $MYMENU_DIR${NC}" + echo -e "${YELLOW}Usage: $0 /path/to/MyMenu${NC}" + exit 1 +fi + +echo -e "${BLUE}๐Ÿ“ MyMenu found: $MYMENU_DIR${NC}" +echo "" + +# Detect PowerManagement installation +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +POWER_MGMT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +echo -e "${BLUE}โšก PowerManagement: $POWER_MGMT_DIR${NC}" +echo "" + +# Create backup +BACKUP_FILE="$MYMENU_DIR/dmenu-launcher.sh.backup.$(date +%Y%m%d_%H%M%S)" +cp "$MYMENU_DIR/dmenu-launcher.sh" "$BACKUP_FILE" +echo -e "${GREEN}โœ… Backup created: $BACKUP_FILE${NC}" +echo "" + +# Create PowerManagement category addition +cat > "/tmp/power_mgmt_category.sh" << 'EOF' + +#============================================================================== +# PowerManagement Integration - Auto-generated +#============================================================================== + +show_power_management_menu() { + cat << MENU +๐Ÿ”ฅ Performance Mode +โš–๏ธ Balanced Mode +๐Ÿ”‹ Power Save Mode +๐Ÿšจ Emergency Mode +--- +๐Ÿ“Š Current Status +๐ŸŽฎ GPU Metrics +๐ŸŒก๏ธ All Sensors +๐Ÿ’จ Fan Status +๐Ÿ’จ Fan Control +--- +๐Ÿ“ˆ Start Monitoring +๐Ÿ“– Documentation +MENU +} + +handle_power_management() { + local choice="$1" + local integration="POWER_MGMT_INTEGRATION_PATH" + + case "$choice" in + "๐Ÿ”ฅ Performance Mode") + $integration performance + ;; + "โš–๏ธ Balanced Mode") + $integration balanced + ;; + "๐Ÿ”‹ Power Save Mode") + $integration powersave + ;; + "๐Ÿšจ Emergency Mode") + $integration emergency + ;; + "๐Ÿ“Š Current Status") + $integration status + ;; + "๐ŸŽฎ GPU Metrics") + $integration gpu + ;; + "๐ŸŒก๏ธ All Sensors") + $integration sensors + ;; + "๐Ÿ’จ Fan Status") + $integration fans + ;; + "๐Ÿ’จ Fan Control") + # Show fan submenu + local fan_choice=$(echo -e "๐ŸŒ€ 30% Silent\n๐ŸŒ€ 50% Normal\n๐ŸŒ€ 75% High\n๐ŸŒ€ 100% Max\n๐Ÿ”„ Auto Mode" | dmenu -i -p "Fan Control:") + case "$fan_choice" in + "๐ŸŒ€ 30% Silent") $integration fan-30 ;; + "๐ŸŒ€ 50% Normal") $integration fan-50 ;; + "๐ŸŒ€ 75% High") $integration fan-75 ;; + "๐ŸŒ€ 100% Max") $integration fan-100 ;; + "๐Ÿ”„ Auto Mode") $integration fan-auto ;; + esac + ;; + "๐Ÿ“ˆ Start Monitoring") + $integration monitoring + ;; + "๐Ÿ“– Documentation") + $integration docs + ;; + esac +} +EOF + +# Replace placeholder with actual path +sed -i "s|POWER_MGMT_INTEGRATION_PATH|$POWER_MGMT_DIR/integrations/mymenu_integration.sh|g" /tmp/power_mgmt_category.sh + +echo -e "${BLUE}๐Ÿ”ง Adding PowerManagement category to MyMenu...${NC}" +echo "" + +# Check if already integrated +if grep -q "PowerManagement Integration" "$MYMENU_DIR/dmenu-launcher.sh"; then + echo -e "${YELLOW}โš ๏ธ PowerManagement already integrated${NC}" + echo -e "${YELLOW} Remove existing integration first${NC}" + exit 1 +fi + +# Add integration code before final line +# Find the line before "esac" at the end +LINE_NUM=$(grep -n "^esac$" "$MYMENU_DIR/dmenu-launcher.sh" | tail -1 | cut -d: -f1) + +if [ -z "$LINE_NUM" ]; then + echo -e "${YELLOW}โŒ Could not find insertion point${NC}" + exit 1 +fi + +# Insert before that line +head -n $((LINE_NUM - 1)) "$MYMENU_DIR/dmenu-launcher.sh" > /tmp/dmenu_new.sh +cat /tmp/power_mgmt_category.sh >> /tmp/dmenu_new.sh +tail -n +$LINE_NUM "$MYMENU_DIR/dmenu-launcher.sh" >> /tmp/dmenu_new.sh + +# Add category to main menu (after line with main categories) +# Find monitoring category and add power management after it +sed -i '/๐Ÿ“Š Monitoring/a ๐ŸŒก๏ธ Power & Thermal' /tmp/dmenu_new.sh + +# Add handler in main switch +sed -i '/handle_monitoring_menu/a \ "๐ŸŒก๏ธ Power & Thermal")\n local pm_choice=$(show_power_management_menu | dmenu -i -p "Power Management:")\n [ -n "$pm_choice" ] \&\& handle_power_management "$pm_choice"\n ;;' /tmp/dmenu_new.sh + +# Replace original +mv /tmp/dmenu_new.sh "$MYMENU_DIR/dmenu-launcher.sh" +chmod +x "$MYMENU_DIR/dmenu-launcher.sh" + +echo -e "${GREEN}============================================${NC}" +echo -e "${GREEN} โœ… Integration Complete!${NC}" +echo -e "${GREEN}============================================${NC}" +echo "" +echo -e "${BLUE}๐Ÿ“‹ What was added:${NC}" +echo -e " โ€ข ๐ŸŒก๏ธ Power & Thermal category in main menu" +echo -e " โ€ข 11 power management actions" +echo -e " โ€ข GPU monitoring" +echo -e " โ€ข Sensor detection" +echo -e " โ€ข Fan control" +echo -e " โ€ข Monitoring service launcher" +echo "" +echo -e "${BLUE}๐Ÿš€ Usage:${NC}" +echo -e " Run: $MYMENU_DIR/dmenu-launcher.sh" +echo -e " Select: ๐ŸŒก๏ธ Power & Thermal" +echo "" +echo -e "${YELLOW}๐Ÿ’พ Backup saved: $(basename $BACKUP_FILE)${NC}" +echo "" diff --git a/integrations/unified_monitor.sh b/integrations/unified_monitor.sh new file mode 100755 index 0000000..4db3d29 --- /dev/null +++ b/integrations/unified_monitor.sh @@ -0,0 +1,448 @@ +#!/bin/bash + +#============================================================================== +# Unified Monitoring Dashboard +# Combines PowerManagement, claude-tools-monitor, and MyMenu +# Displays comprehensive system state in dmenu-compatible format +#============================================================================== + +set -euo pipefail + +# Colors for terminal output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Detect installation directories +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +POWER_MGMT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Auto-detect claude-tools-monitor directory +if [ -z "${CLAUDE_MONITOR_DIR:-}" ]; then + # Try common locations + for dir in "$HOME/claude-tools-monitor" "$HOME/projects/claude-tools-monitor" "/opt/claude-tools-monitor"; do + if [ -d "$dir" ]; then + CLAUDE_MONITOR_DIR="$dir" + break + fi + done + # Fallback to HOME if still not set + CLAUDE_MONITOR_DIR="${CLAUDE_MONITOR_DIR:-$HOME/claude-tools-monitor}" +fi + +# Python path setup +export PYTHONPATH="$POWER_MGMT_DIR/src:${PYTHONPATH:-}" + +#============================================================================== +# Data Collection Functions +#============================================================================== + +get_power_metrics() { + # Get PowerManagement metrics + local metrics_json + + if ! metrics_json=$(python3 - </dev/null); then + echo '{"available": false}' + return + fi + + echo "$metrics_json" +} + +get_claude_monitor_status() { + # Get claude-tools-monitor status + + if [ ! -d "$CLAUDE_MONITOR_DIR" ]; then + echo '{"available": false}' + return + fi + + # Check if monitoring service is running + local running=false + if pgrep -f "claude.*monitor" >/dev/null 2>&1; then + running=true + fi + + # Check recent activity + local activity="idle" + if [ -f "/tmp/claude_activity.log" ]; then + local last_activity=$(tail -1 /tmp/claude_activity.log 2>/dev/null) + if [ -n "$last_activity" ]; then + activity="active" + fi + fi + + echo "{\"available\": true, \"running\": $running, \"activity\": \"$activity\"}" +} + +get_current_power_profile() { + # Get current power profile + + # Check cpufreq governor + if [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]; then + local governor=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor) + case "$governor" in + performance) echo "๐Ÿ”ฅ Performance" ;; + powersave) echo "๐Ÿ”‹ Power Save" ;; + schedutil|ondemand) echo "โš–๏ธ Balanced" ;; + *) echo "โ“ $governor" ;; + esac + else + echo "โ“ Unknown" + fi +} + +get_thermal_status() { + # Get thermal status with color coding + local cpu_temp="$1" + + if [ -z "$cpu_temp" ] || [ "$cpu_temp" = "null" ] || [ "$cpu_temp" = "None" ] || [ "$cpu_temp" = "N/A" ]; then + echo "โ“ N/A" + return + fi + + # Compare temps (bash integer comparison) + local temp_int=${cpu_temp%.*} + + # Check if temp_int is actually a number + if ! [[ "$temp_int" =~ ^[0-9]+$ ]]; then + echo "โ“ N/A" + return + fi + + if [ "$temp_int" -ge 85 ]; then + echo "๐Ÿšจ CRITICAL" + elif [ "$temp_int" -ge 75 ]; then + echo "โš ๏ธ WARNING" + elif [ "$temp_int" -ge 65 ]; then + echo "โšก ELEVATED" + else + echo "โœ… GOOD" + fi +} + +#============================================================================== +# Display Functions +#============================================================================== + +show_dashboard_menu() { + # Show main dashboard menu + + cat </dev/null 2>&1; then + python3 "$POWER_MGMT_DIR/src/sensors/universal_sensor_detector.py" 2>/dev/null | head -40 + else + echo -e "${RED}Python3 not available${NC}" + fi +} + +show_gpu_details() { + # Show detailed GPU information + + echo -e "${BLUE}============================================${NC}" + echo -e "${BLUE} ๐ŸŽฎ GPU Details${NC}" + echo -e "${BLUE}============================================${NC}" + echo "" + + # Run GPU monitor + if command -v python3 >/dev/null 2>&1; then + python3 "$POWER_MGMT_DIR/src/sensors/gpu_monitor.py" 2>/dev/null + else + echo -e "${RED}Python3 not available${NC}" + fi +} + +start_monitoring_service() { + # Start the monitoring service + + echo -e "${GREEN}Starting monitoring service...${NC}" + + # Check if already running + if pgrep -f "monitoring_service.py" >/dev/null 2>&1; then + echo -e "${YELLOW}โš ๏ธ Monitoring service already running${NC}" + return + fi + + # Start service in background + nohup python3 "$POWER_MGMT_DIR/src/services/monitoring_service.py" \ + --interval 5 \ + --log-dir /tmp \ + >/tmp/monitoring_service.log 2>&1 & + + echo -e "${GREEN}โœ… Monitoring service started${NC}" + echo -e " Log: /tmp/power_monitoring.log" + echo -e " JSON: /tmp/power_monitoring.json" +} + +stop_monitoring_service() { + # Stop the monitoring service + + echo -e "${YELLOW}Stopping monitoring service...${NC}" + + if ! pgrep -f "monitoring_service.py" >/dev/null 2>&1; then + echo -e "${RED}โŒ Monitoring service not running${NC}" + return + fi + + pkill -f "monitoring_service.py" + echo -e "${GREEN}โœ… Monitoring service stopped${NC}" +} + +#============================================================================== +# Menu Handler +#============================================================================== + +handle_menu_choice() { + local choice="$1" + + case "$choice" in + "๐Ÿ“Š View Full Status") + show_full_status + read -p "Press Enter to continue..." + ;; + "๐Ÿ”„ Refresh Dashboard") + exec "$0" + ;; + "๐ŸŒก๏ธ Temperature Details") + show_temperature_details + read -p "Press Enter to continue..." + ;; + "๐Ÿ’จ Fan Control") + bash "$POWER_MGMT_DIR/integrations/mymenu_integration.sh" "๐Ÿ’จ Fan Control" + ;; + "๐ŸŽฎ GPU Details") + show_gpu_details + read -p "Press Enter to continue..." + ;; + "โšก Power Profiles") + bash "$POWER_MGMT_DIR/integrations/mymenu_integration.sh" "โšก Power Profiles" + ;; + "๐Ÿ“ˆ Start Monitoring Service") + start_monitoring_service + read -p "Press Enter to continue..." + ;; + "๐Ÿ›‘ Stop Monitoring Service") + stop_monitoring_service + read -p "Press Enter to continue..." + ;; + "โš™๏ธ Settings") + echo "Settings menu - Coming soon" + read -p "Press Enter to continue..." + ;; + *) + echo "Unknown option: $choice" + ;; + esac +} + +#============================================================================== +# Main Entry Point +#============================================================================== + +main() { + # Check if running in dmenu mode + if [ "${1:-}" = "--dmenu" ]; then + show_dashboard_menu + exit 0 + fi + + # Check if choice provided + if [ $# -gt 0 ]; then + handle_menu_choice "$*" + exit 0 + fi + + # Interactive mode - show status and menu + while true; do + clear + show_full_status + echo "" + echo "Select option:" + + # Show menu and get choice + local choice + choice=$(show_dashboard_menu | nl -w2 -s'. ' | fzf --prompt="Select: " | sed 's/^[[:space:]]*[0-9]*\.[[:space:]]*//') + + if [ -z "$choice" ]; then + break + fi + + handle_menu_choice "$choice" + done +} + +# Run main function +main "$@" diff --git a/scripts/ai_process_manager.sh b/scripts/ai_process_manager.sh index 642b1dc..08efa46 100755 --- a/scripts/ai_process_manager.sh +++ b/scripts/ai_process_manager.sh @@ -16,18 +16,20 @@ set -euo pipefail # Exit on error, undefined vars, pipe failures # CONFIGURATION CONSTANTS #============================================================================== -readonly SCRIPT_NAME="AI Process Manager v2.0" -readonly VERSION="2.0.0" +readonly SCRIPT_NAME="AI Process Manager v3.0" +readonly VERSION="3.0.0" -# Temperature thresholds (Celsius) +# Temperature thresholds (Celsius) - will be adjusted based on CPU readonly TEMP_WARNING_THRESHOLD=75 readonly TEMP_CRITICAL_THRESHOLD=79 # Process patterns to monitor readonly AI_PROCESS_PATTERNS="claude|gemini|anthropic|python.*telegram|node.*claude" -# Paths -readonly POWER_MANAGER_PATH="/home/milhy777/Develop/Production/PowerManagement/scripts/performance_manager.sh" +# Paths - Dynamic detection +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly INSTALL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +readonly POWER_MANAGER_PATH="$SCRIPT_DIR/performance_manager.sh" readonly THERMAL_ZONE="/sys/class/thermal/thermal_zone0/temp" # Colors for output diff --git a/scripts/claude_context_bridge.sh b/scripts/claude_context_bridge.sh index 9644c01..0e67438 100755 --- a/scripts/claude_context_bridge.sh +++ b/scripts/claude_context_bridge.sh @@ -13,8 +13,9 @@ set -euo pipefail -readonly SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" -readonly TELEGRAM_BOT_DIR="/home/milhy777/Develop/claude-code-telegram" +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# Optional: Configure Telegram bot directory if you have claude-code-telegram installed +readonly TELEGRAM_BOT_DIR="${TELEGRAM_BOT_DIR:-$HOME/claude-code-telegram}" readonly CONTEXT_SHARE_DIR="/tmp/claude-context-bridge" readonly CLI_SESSIONS_DIR="$HOME/.claude" readonly BOT_DB_PATH="$TELEGRAM_BOT_DIR/data/bot.db" diff --git a/scripts/performance_manager.sh b/scripts/performance_manager.sh index 91e27d8..f932313 100755 --- a/scripts/performance_manager.sh +++ b/scripts/performance_manager.sh @@ -1,19 +1,27 @@ #!/bin/bash -# Performance Manager - Safe Power Management for Linux Systems -# Version: 2.0 - GitHub Ready -# Author: Claude AI Assistant +# Performance Manager - Universal Power Management for Linux Systems +# Version: 3.0 - Universal Hardware Support +# Author: PowerManagement Team # License: MIT # Exit on any error for safety set -euo pipefail -# Configuration +# Configuration - Dynamic path detection readonly SCRIPT_NAME="$(basename "$0")" -readonly SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" -readonly LOG_FILE="/tmp/performance_manager.log" +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly INSTALL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +readonly SRC_DIR="$INSTALL_DIR/src" +readonly LOG_DIR="${POWER_MGMT_LOG_DIR:-/tmp}" +readonly LOG_FILE="$LOG_DIR/performance_manager.log" readonly MAX_PROCESSES=10 +# Dynamic script paths +readonly CPU_FREQ_MANAGER="$SRC_DIR/frequency/universal_cpu_manager.py" +readonly AI_MANAGER="$SCRIPT_DIR/ai_process_manager.sh" +readonly EMERGENCY_CLEANUP="$SCRIPT_DIR/EMERGENCY_CLEANUP.sh" + # Safety check - prevent multiple instances check_running_instances() { local count @@ -70,9 +78,10 @@ safe_sudo() { # Performance Manager Header show_header() { - echo "โšก Performance Manager v2.0 - GitHub Ready" - echo "===========================================" + echo "โšก Performance Manager v3.0 - Universal Edition" + echo "===============================================" echo "๐Ÿ›ก๏ธ Safe mode: Process monitoring enabled" + echo "๐Ÿ“ Install: $INSTALL_DIR" check_running_instances } @@ -89,14 +98,28 @@ get_current_status() { echo " Model: $cpu_model" echo " Current: ${cpu_mhz} MHz (Max: 2833 MHz)" - # GPU info (safely) + # GPU info (safely) - auto-detect GPU card echo "" echo "๐ŸŽฎ GPU:" - local gpu_profile gpu_method - gpu_profile=$(cat /sys/class/drm/card1/device/power_profile 2>/dev/null || echo "unknown") - gpu_method=$(cat /sys/class/drm/card1/device/power_method 2>/dev/null || echo "unknown") - echo " Power Profile: $gpu_profile" - echo " Power Method: $gpu_method" + local gpu_card gpu_profile gpu_method + # Find first GPU with power_profile support + gpu_card="" + for card in /sys/class/drm/card[0-9]; do + if [ -f "$card/device/power_profile" 2>/dev/null ]; then + gpu_card="$card" + break + fi + done + + if [ -n "$gpu_card" ]; then + gpu_profile=$(cat "$gpu_card/device/power_profile" 2>/dev/null || echo "unknown") + gpu_method=$(cat "$gpu_card/device/power_method" 2>/dev/null || echo "unknown") + echo " Card: $(basename "$gpu_card")" + echo " Power Profile: $gpu_profile" + echo " Power Method: $gpu_method" + else + echo " No GPU with power management found" + fi # Power Profiles (safely) echo "" @@ -132,17 +155,21 @@ set_performance_profile() { fi fi - # GPU high performance (safely) - if safe_sudo "echo 'high' > /sys/class/drm/card1/device/power_profile" 3; then - log "โœ… GPU: high power mode set" - else - log "โš ๏ธ GPU: high power mode failed" - fi - - # CPU frequency to maximum (safely) - if [ -f "$SCRIPT_DIR/../src/frequency/cpu_frequency_manager.py" ]; then - if safe_exec "python3 '$SCRIPT_DIR/../src/frequency/cpu_frequency_manager.py' thermal performance" 10; then - log "โœ… CPU: performance frequency set (2.83GHz)" + # GPU high performance (safely) - auto-detect GPU + local gpu_card + for card in /sys/class/drm/card[0-9]; do + if [ -f "$card/device/power_profile" 2>/dev/null ]; then + if safe_sudo "echo 'high' > $card/device/power_profile" 3; then + log "โœ… GPU: high power mode set ($(basename "$card"))" + fi + break + fi + done + + # CPU frequency to maximum (universal manager) + if [ -f "$CPU_FREQ_MANAGER" ]; then + if safe_exec "python3 '$CPU_FREQ_MANAGER' profile performance" 10; then + log "โœ… CPU: performance frequency set" else log "โš ๏ธ CPU: frequency control failed" fi @@ -174,21 +201,25 @@ set_balanced_profile() { fi fi - # CPU frequency to balanced (safely) - if [ -f "$SCRIPT_DIR/../src/frequency/cpu_frequency_manager.py" ]; then - if safe_exec "python3 '$SCRIPT_DIR/../src/frequency/cpu_frequency_manager.py' thermal balanced" 10; then - log "โœ… CPU: balanced frequency set (2.16GHz)" + # CPU frequency to balanced (universal manager) + if [ -f "$CPU_FREQ_MANAGER" ]; then + if safe_exec "python3 '$CPU_FREQ_MANAGER' profile balanced" 10; then + log "โœ… CPU: balanced frequency set" else log "โš ๏ธ CPU: frequency control failed" fi fi - - # GPU default (safely) - if safe_sudo "echo 'default' > /sys/class/drm/card1/device/power_profile" 3; then - log "โœ… GPU: default power mode set" - else - log "โš ๏ธ GPU: default power mode failed" - fi + + # GPU default (safely) - auto-detect GPU + local gpu_card + for card in /sys/class/drm/card[0-9]; do + if [ -f "$card/device/power_profile" 2>/dev/null ]; then + if safe_sudo "echo 'default' > $card/device/power_profile" 3; then + log "โœ… GPU: default power mode set ($(basename "$card"))" + fi + break + fi + done # AI processes slightly lower priority local ai_pids @@ -216,21 +247,25 @@ set_powersave_profile() { fi fi - # CPU frequency to power save (safely) - if [ -f "$SCRIPT_DIR/../src/frequency/cpu_frequency_manager.py" ]; then - if safe_exec "python3 '$SCRIPT_DIR/../src/frequency/cpu_frequency_manager.py' thermal power_save" 10; then - log "โœ… CPU: power save frequency set (1.66GHz)" + # CPU frequency to power save (universal manager) + if [ -f "$CPU_FREQ_MANAGER" ]; then + if safe_exec "python3 '$CPU_FREQ_MANAGER' profile powersave" 10; then + log "โœ… CPU: power save frequency set" else log "โš ๏ธ CPU: frequency control failed" fi fi - - # GPU low power (safely) - if safe_sudo "echo 'low' > /sys/class/drm/card1/device/power_profile" 3; then - log "โœ… GPU: low power mode set" - else - log "โš ๏ธ GPU: low power mode failed" - fi + + # GPU low power (safely) - auto-detect GPU + local gpu_card + for card in /sys/class/drm/card[0-9]; do + if [ -f "$card/device/power_profile" 2>/dev/null ]; then + if safe_sudo "echo 'low' > $card/device/power_profile" 3; then + log "โœ… GPU: low power mode set ($(basename "$card"))" + fi + break + fi + done # AI processes lower priority local ai_pids @@ -248,66 +283,55 @@ set_powersave_profile() { # Emergency safe mode set_emergency_profile() { log "๐Ÿšจ Setting EMERGENCY profile..." - - # ๐Ÿค– SPUSTIT SYSTEM-OPTIMIZER-GUARDIAN AGENTA - if command -v claude >/dev/null 2>&1; then - log "๐Ÿค– LAUNCHING System-Optimizer-Guardian Agent for Emergency" - claude --agent system-optimizer-guardian \ - "EMERGENCY MODE ACTIVATED! Systรฉm detekoval kritickรฉ problรฉmy. - Proveฤ okamลพitou emergency optimalizaci: - 1. Zabij vลกechny problรฉmovรฉ procesy a memory leaks - 2. Vyฤisti high system load a frozen aplikace - 3. Nastav minimรกlnรญ power consumption pro vลกechny komponenty - 4. Zkontroluj a oprav GPU/thermal/CPU throttling - 5. Aktivuj nejstabilnฤ›jลกรญ emergency power profil - 6. Proveฤ system health check a memory cleanup - Pouลพรญvaj emergency skripty z /home/milhy777/ pro kritickรฉ situace. - Fokus na STABILITU a minimรกlnรญ resource usage!" & - log "๐Ÿค– Emergency Agent spuลกtฤ›n na pozadรญ pro komplexnรญ recovery" - else - log "โš ๏ธ Claude agent nedostupnรฝ, spouลกtรญm standardnรญ emergency" - fi - + # CPU frequency to emergency minimum (FIRST - most important) - if [ -f "$SCRIPT_DIR/../src/frequency/cpu_frequency_manager.py" ]; then - if safe_exec "python3 '$SCRIPT_DIR/../src/frequency/cpu_frequency_manager.py' thermal emergency" 10; then - log "โœ… CPU: EMERGENCY frequency set (1.33GHz)" + if [ -f "$CPU_FREQ_MANAGER" ]; then + if safe_exec "python3 '$CPU_FREQ_MANAGER' profile emergency" 10; then + log "โœ… CPU: EMERGENCY frequency set to minimum" else log "โš ๏ธ CPU: emergency frequency control failed" fi fi - + # Kill all related processes first pkill -f "powerprofilesctl" 2>/dev/null || true sleep 1 - + # Emergency AI cleanup - if [ -f "$SCRIPT_DIR/ai_process_manager.sh" ]; then - "$SCRIPT_DIR/ai_process_manager.sh" emergency || true + if [ -f "$AI_MANAGER" ]; then + "$AI_MANAGER" emergency || true fi - + # Minimal power settings if command -v powerprofilesctl >/dev/null 2>&1; then safe_sudo "powerprofilesctl set power-saver" 5 || true fi - - # GPU minimum (safely) - safe_sudo "echo 'low' > /sys/class/drm/card1/device/power_profile" 3 || true - + + # GPU minimum (safely) - auto-detect GPU + local gpu_card + for card in /sys/class/drm/card[0-9]; do + if [ -f "$card/device/power_profile" 2>/dev/null ]; then + if safe_sudo "echo 'low' > $card/device/power_profile" 3; then + log "โœ… GPU: emergency low power mode set ($(basename "$card"))" + fi + break + fi + done + # Clear memory (safe approach) sync # Drop caches only if we have sudo access if [[ "${CI:-false}" != "true" ]] && sudo -n true 2>/dev/null; then sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' 2>/dev/null || true fi - - # Spustit EMERGENCY_CLEANUP fallback - if [ -f "/home/milhy777/EMERGENCY_CLEANUP.sh" ]; then - log "๐Ÿงน Spouลกtรญm emergency cleanup jako fallback" - /home/milhy777/EMERGENCY_CLEANUP.sh & + + # Run EMERGENCY_CLEANUP if available + if [ -f "$EMERGENCY_CLEANUP" ]; then + log "๐Ÿงน Running emergency cleanup" + "$EMERGENCY_CLEANUP" & fi - - log "๐Ÿšจ EMERGENCY MODE - Minimal power to prevent blackscreen" + + log "๐Ÿšจ EMERGENCY MODE - Minimal power to prevent thermal shutdown" echo "๐Ÿšจ EMERGENCY MODE - System stabilized" } diff --git a/scripts/power_gui.sh b/scripts/power_gui.sh index 27bd809..40b499f 100755 --- a/scripts/power_gui.sh +++ b/scripts/power_gui.sh @@ -7,7 +7,8 @@ set -euo pipefail readonly SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" readonly PERFORMANCE_SCRIPT="$SCRIPT_DIR/performance_manager.sh" -readonly AI_SCRIPT="/home/milhy777/ai_process_manager.sh" +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly AI_SCRIPT="$SCRIPT_DIR/ai_process_manager.sh" readonly CONFIG_DIR="$HOME/.config/power-management-gui" readonly CONFIG_FILE="$CONFIG_DIR/settings.conf" diff --git a/scripts/smart_thermal_manager.py b/scripts/smart_thermal_manager.py index 2d73cd3..ae243ed 100755 --- a/scripts/smart_thermal_manager.py +++ b/scripts/smart_thermal_manager.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 """ -Smart Thermal Manager - Upgraded Performance Manager -Kombinuje funkฤnรญ ฤรกsti starรฉho systรฉmu + novรฉ thermal features +Smart Thermal Manager - Universal Edition +Adaptive thermal management with hardware-specific thresholds +Supports Intel (old/new), AMD CPUs with appropriate thermal limits """ import os @@ -11,13 +12,21 @@ import subprocess import threading import psutil +from pathlib import Path from dataclasses import dataclass from typing import Optional, List from enum import Enum +# Add src directory to path for imports +script_dir = Path(__file__).resolve().parent +sys.path.insert(0, str(script_dir.parent / "src")) + +from hardware.hardware_detector import HardwareDetector +from config.power_config import PowerConfig + class PowerMode(Enum): PERFORMANCE = "performance" - BALANCED = "balanced" + BALANCED = "balanced" POWER_SAVE = "power-saver" EMERGENCY = "emergency" @@ -27,22 +36,34 @@ class SystemStatus: cpu_usage: float load_avg: float power_mode: PowerMode - + class SmartThermalManager: def __init__(self): - # Thermal thresholds (based on working old system) - self.comfort_temp = 65 # Below this = performance OK - self.warning_temp = 70 # Your old system worked here - self.critical_temp = 80 # Where old system dropped load - self.emergency_temp = 83 # Shutdown threshold - + # Detect hardware and get CPU-specific thermal limits + self.detector = HardwareDetector() + self.cpu_info = self.detector.cpu_info + + # Load configuration + self.config = PowerConfig() + self.config.set_thermal_config(self.cpu_info.thermal_max_safe) + + # Use adaptive thermal thresholds based on CPU + self.comfort_temp = self.config.thermal.comfort_temp + self.warning_temp = self.config.thermal.warning_temp + self.critical_temp = self.config.thermal.critical_temp + self.emergency_temp = self.config.thermal.emergency_temp + # State tracking self.current_mode = PowerMode.BALANCED self.monitoring = False self.escalation_count = 0 - + # Process tracking for AI workloads self.ai_processes = [] + + self.log(f"โœ… Thermal Manager initialized for {self.cpu_info.model_name}") + self.log(f" Thermal thresholds: {self.comfort_temp}ยฐC / {self.warning_temp}ยฐC / " + f"{self.critical_temp}ยฐC / {self.emergency_temp}ยฐC") def log(self, message): timestamp = time.strftime("%H:%M:%S") diff --git a/src/config/__pycache__/power_config.cpython-311.pyc b/src/config/__pycache__/power_config.cpython-311.pyc new file mode 100644 index 0000000..8d833a1 Binary files /dev/null and b/src/config/__pycache__/power_config.cpython-311.pyc differ diff --git a/src/config/power_config.py b/src/config/power_config.py new file mode 100644 index 0000000..b194b20 --- /dev/null +++ b/src/config/power_config.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python3 +""" +Power Management Configuration System +Handles paths, settings, and hardware-specific configurations +Eliminates hardcoded paths and makes system portable +""" + +import os +import json +from pathlib import Path +from typing import Dict, Any, Optional +from dataclasses import dataclass, asdict + + +@dataclass +class PathConfig: + """Dynamic path configuration""" + # Base paths + install_dir: str + scripts_dir: str + src_dir: str + config_dir: str + log_dir: str + + # Script paths + performance_manager: str + ai_process_manager: str + emergency_cleanup: str + cpu_frequency_manager: str + smart_thermal_manager: str + + # Log files + main_log: str + cpu_log: str + thermal_log: str + + +@dataclass +class ThermalConfig: + """Thermal management configuration""" + comfort_temp: int = 65 + warning_temp: int = 70 + critical_temp: int = 80 + emergency_temp: int = 85 # Will be adjusted based on CPU + + +@dataclass +class FrequencyConfig: + """CPU frequency configuration""" + min_freq_mhz: int + max_freq_mhz: int + performance_freq: int + balanced_freq: int + powersave_freq: int + emergency_freq: int + + +class PowerConfig: + """Central configuration manager""" + + DEFAULT_CONFIG_LOCATIONS = [ + "/etc/power-management/config.json", + "~/.config/power-management/config.json", + "./config/config.json", + ] + + def __init__(self, config_file: Optional[str] = None): + """ + Initialize configuration + + Args: + config_file: Optional path to config file. If None, will search default locations. + """ + self.config_file = self._find_config_file(config_file) + self.paths = self._setup_paths() + self.thermal = ThermalConfig() + self.frequency: Optional[FrequencyConfig] = None + + # Load from file if exists + if self.config_file and Path(self.config_file).exists(): + self.load() + + def _find_config_file(self, config_file: Optional[str]) -> Optional[str]: + """Find configuration file""" + if config_file: + return config_file + + # Search default locations + for location in self.DEFAULT_CONFIG_LOCATIONS: + path = Path(location).expanduser() + if path.exists(): + return str(path) + + # Return first writable location for creating new config + for location in self.DEFAULT_CONFIG_LOCATIONS: + path = Path(location).expanduser() + try: + path.parent.mkdir(parents=True, exist_ok=True) + return str(path) + except (OSError, PermissionError): + continue + + return None + + def _setup_paths(self) -> PathConfig: + """Setup dynamic paths based on script location""" + # Detect installation directory + # Try to find where we're actually installed + current_file = Path(__file__).resolve() + install_dir = current_file.parent.parent.parent # Go up from src/config/ + + # Verify this is correct by checking for key files + if not (install_dir / "scripts").exists(): + # Fallback: use current working directory + install_dir = Path.cwd() + + install_dir = str(install_dir) + + # Setup all paths + scripts_dir = os.path.join(install_dir, "scripts") + src_dir = os.path.join(install_dir, "src") + config_dir = os.path.join(install_dir, "config") + log_dir = os.environ.get("POWER_MGMT_LOG_DIR", "/tmp") + + return PathConfig( + install_dir=install_dir, + scripts_dir=scripts_dir, + src_dir=src_dir, + config_dir=config_dir, + log_dir=log_dir, + # Script paths + performance_manager=os.path.join(scripts_dir, "performance_manager.sh"), + ai_process_manager=os.path.join(scripts_dir, "ai_process_manager.sh"), + emergency_cleanup=os.path.join(scripts_dir, "EMERGENCY_CLEANUP.sh"), + cpu_frequency_manager=os.path.join(src_dir, "frequency", "cpu_frequency_manager.py"), + smart_thermal_manager=os.path.join(scripts_dir, "smart_thermal_manager.py"), + # Log files + main_log=os.path.join(log_dir, "performance_manager.log"), + cpu_log=os.path.join(log_dir, "cpu_frequency_manager.log"), + thermal_log=os.path.join(log_dir, "thermal_manager.log"), + ) + + def set_frequency_config(self, min_freq: int, max_freq: int): + """ + Set frequency configuration based on detected hardware + + Args: + min_freq: Minimum CPU frequency in MHz + max_freq: Maximum CPU frequency in MHz + """ + # Calculate optimal frequencies for each profile + freq_range = max_freq - min_freq + + self.frequency = FrequencyConfig( + min_freq_mhz=min_freq, + max_freq_mhz=max_freq, + performance_freq=max_freq, # 100% for performance + balanced_freq=min_freq + int(freq_range * 0.7), # 70% for balanced + powersave_freq=min_freq + int(freq_range * 0.5), # 50% for powersave + emergency_freq=min_freq, # Minimum for emergency + ) + + def set_thermal_config(self, max_safe_temp: int): + """ + Set thermal configuration based on CPU capabilities + + Args: + max_safe_temp: Maximum safe temperature for CPU + """ + # Set thresholds as percentages of max safe temp + # This ensures we're conservative across all CPUs + self.thermal.comfort_temp = int(max_safe_temp * 0.65) # 65% + self.thermal.warning_temp = int(max_safe_temp * 0.75) # 75% + self.thermal.critical_temp = int(max_safe_temp * 0.85) # 85% + self.thermal.emergency_temp = int(max_safe_temp * 0.95) # 95% + + def save(self) -> bool: + """Save configuration to file""" + if not self.config_file: + return False + + try: + config_data = { + "paths": asdict(self.paths), + "thermal": asdict(self.thermal), + "frequency": asdict(self.frequency) if self.frequency else None, + } + + config_path = Path(self.config_file) + config_path.parent.mkdir(parents=True, exist_ok=True) + + with open(config_path, "w") as f: + json.dump(config_data, f, indent=2) + + return True + except Exception as e: + print(f"Failed to save config: {e}") + return False + + def load(self) -> bool: + """Load configuration from file""" + if not self.config_file or not Path(self.config_file).exists(): + return False + + try: + with open(self.config_file, "r") as f: + config_data = json.load(f) + + # Load paths + if "paths" in config_data: + self.paths = PathConfig(**config_data["paths"]) + + # Load thermal + if "thermal" in config_data: + self.thermal = ThermalConfig(**config_data["thermal"]) + + # Load frequency + if "frequency" in config_data and config_data["frequency"]: + self.frequency = FrequencyConfig(**config_data["frequency"]) + + return True + except Exception as e: + print(f"Failed to load config: {e}") + return False + + def get_script_path(self, script_name: str) -> Optional[str]: + """Get path to a script, checking if it exists""" + path_map = { + "performance_manager": self.paths.performance_manager, + "ai_process_manager": self.paths.ai_process_manager, + "emergency_cleanup": self.paths.emergency_cleanup, + "cpu_frequency_manager": self.paths.cpu_frequency_manager, + "smart_thermal_manager": self.paths.smart_thermal_manager, + } + + script_path = path_map.get(script_name) + if script_path and Path(script_path).exists(): + return script_path + return None + + def print_config(self): + """Print current configuration""" + print("=" * 60) + print("โš™๏ธ POWER MANAGEMENT CONFIGURATION") + print("=" * 60) + print() + + print("๐Ÿ“ PATHS:") + print(f" Install Dir: {self.paths.install_dir}") + print(f" Scripts Dir: {self.paths.scripts_dir}") + print(f" Config File: {self.config_file or 'Not set'}") + print() + + print("๐ŸŒก๏ธ THERMAL CONFIG:") + print(f" Comfort: < {self.thermal.comfort_temp}ยฐC") + print(f" Warning: {self.thermal.warning_temp}ยฐC") + print(f" Critical: {self.thermal.critical_temp}ยฐC") + print(f" Emergency: {self.thermal.emergency_temp}ยฐC") + print() + + if self.frequency: + print("โšก FREQUENCY CONFIG:") + print(f" Range: {self.frequency.min_freq_mhz}-{self.frequency.max_freq_mhz} MHz") + print(f" Performance: {self.frequency.performance_freq} MHz") + print(f" Balanced: {self.frequency.balanced_freq} MHz") + print(f" Powersave: {self.frequency.powersave_freq} MHz") + print(f" Emergency: {self.frequency.emergency_freq} MHz") + print() + + print("=" * 60) + + +def get_default_config() -> PowerConfig: + """Get default configuration instance""" + return PowerConfig() + + +def main(): + """Test configuration system""" + config = PowerConfig() + + # Simulate hardware detection + config.set_frequency_config(min_freq=1200, max_freq=3000) + config.set_thermal_config(max_safe_temp=100) + + # Print configuration + config.print_config() + + # Test save + if config.save(): + print("โœ… Configuration saved successfully") + else: + print("โš ๏ธ Could not save configuration") + + # Test script path lookup + print("\n๐Ÿ“ Script Paths:") + for script in ["performance_manager", "cpu_frequency_manager", "ai_process_manager"]: + path = config.get_script_path(script) + status = "โœ…" if path else "โŒ" + print(f" {status} {script}: {path or 'Not found'}") + + +if __name__ == "__main__": + main() diff --git a/src/frequency/universal_cpu_manager.py b/src/frequency/universal_cpu_manager.py new file mode 100644 index 0000000..56d7856 --- /dev/null +++ b/src/frequency/universal_cpu_manager.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +""" +Universal CPU Frequency Manager +Works across different CPU models: Intel (Core 2, Core i-series), AMD (Phenom, FX, Ryzen) +Automatically detects hardware and uses appropriate frequency control methods +""" + +import os +import sys +import subprocess +import time +from pathlib import Path +from typing import Optional, Dict, List +from dataclasses import dataclass + +# Add parent directory to path to import hardware detector +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from hardware.hardware_detector import ( + HardwareDetector, CPUVendor, CPUGeneration +) +from config.power_config import PowerConfig + + +@dataclass +class FrequencyProfile: + """Frequency profile for different power modes""" + name: str + frequency_mhz: int + description: str + + +class UniversalCPUManager: + """ + Universal CPU Frequency Manager + Supports: + - Intel: Core 2, Nehalem, Sandy Bridge, Ivy Bridge, Haswell, Broadwell, Skylake+ + - AMD: K8, K10, Bulldozer, Zen + - Multiple control methods: cpufreq, MSR, cpupower + """ + + # Intel Core 2 Quad MSR multipliers (for legacy support) + CORE2_QUAD_MULTIPLIERS = { + # Q9550, Q9650, Q9450, etc. + 2833: 0x0615, # 8.5x + 2666: 0x0514, # 8.0x + 2500: 0x0513, # 7.5x + 2333: 0x0512, # 7.0x + 2166: 0x0411, # 6.5x + 2000: 0x0610, # 6.0x + 1833: 0x050F, # 5.5x + 1666: 0x050E, # 5.0x + 1500: 0x050D, # 4.5x + 1333: 0x040C, # 4.0x + 1200: 0x040B, # 3.6x + } + + def __init__(self): + """Initialize universal CPU manager with hardware detection""" + self.is_ci = os.environ.get('CI', 'false').lower() == 'true' + + # Detect hardware + self.detector = HardwareDetector() + self.cpu_info = self.detector.cpu_info + + # Load configuration + self.config = PowerConfig() + self.config.set_frequency_config( + self.cpu_info.min_freq_mhz, + self.cpu_info.max_freq_mhz + ) + self.config.set_thermal_config(self.cpu_info.thermal_max_safe) + + # Setup logging + self.log_file = self.config.paths.cpu_log + + # Determine best control method + self.control_method = self._select_control_method() + + self.log(f"โœ… Initialized for {self.cpu_info.vendor.value.upper()} " + f"{self.cpu_info.generation.value} CPU") + self.log(f" Control method: {self.control_method}") + + def log(self, message: str): + """Log message to file and stdout""" + timestamp = time.strftime("%H:%M:%S") + log_msg = f"{timestamp} - {message}" + print(log_msg) + + try: + with open(self.log_file, "a") as f: + f.write(log_msg + "\n") + except Exception: + pass + + def _select_control_method(self) -> str: + """Select best frequency control method for this hardware""" + if self.is_ci: + return "CI_MODE" + + # Prefer cpufreq for modern systems + if self.cpu_info.supports_cpufreq: + # Modern Intel (Skylake+) uses intel_pstate, others use acpi-cpufreq + if self.cpu_info.generation == CPUGeneration.SKYLAKE_PLUS: + return "cpufreq_intel_pstate" + return "cpufreq" + + # Fallback to MSR for older systems + if self.cpu_info.supports_msr: + return "msr" + + # Last resort: cpupower command + try: + result = subprocess.run(["which", "cpupower"], capture_output=True) + if result.returncode == 0: + return "cpupower" + except Exception: + pass + + return "none" + + def get_frequency_profiles(self) -> List[FrequencyProfile]: + """Get frequency profiles for this CPU""" + if not self.config.frequency: + return [] + + return [ + FrequencyProfile( + "performance", + self.config.frequency.performance_freq, + "Maximum performance - full CPU speed" + ), + FrequencyProfile( + "balanced", + self.config.frequency.balanced_freq, + "Balanced - good performance with moderate power" + ), + FrequencyProfile( + "powersave", + self.config.frequency.powersave_freq, + "Power saving - reduced speed for efficiency" + ), + FrequencyProfile( + "emergency", + self.config.frequency.emergency_freq, + "Emergency - minimum speed to prevent thermal shutdown" + ), + ] + + def set_frequency_cpufreq(self, target_freq: int) -> bool: + """Set frequency using cpufreq subsystem""" + try: + # For intel_pstate, use scaling_max_freq and scaling_min_freq + if self.control_method == "cpufreq_intel_pstate": + for cpu_id in range(self.cpu_info.cores): + base = f"/sys/devices/system/cpu/cpu{cpu_id}/cpufreq" + + # Set both min and max to target (forces frequency) + with open(f"{base}/scaling_min_freq", "w") as f: + f.write(str(target_freq * 1000)) + with open(f"{base}/scaling_max_freq", "w") as f: + f.write(str(target_freq * 1000)) + + self.log(f"โœ… intel_pstate: Set to {target_freq}MHz") + return True + + # For acpi-cpufreq, use userspace governor + else: + for cpu_id in range(self.cpu_info.cores): + base = f"/sys/devices/system/cpu/cpu{cpu_id}/cpufreq" + + # Set governor to userspace + with open(f"{base}/scaling_governor", "w") as f: + f.write("userspace") + + # Set frequency + with open(f"{base}/scaling_setspeed", "w") as f: + f.write(str(target_freq * 1000)) + + self.log(f"โœ… cpufreq: Set to {target_freq}MHz") + return True + + except PermissionError: + self.log("โŒ cpufreq: Permission denied (need root)") + return False + except Exception as e: + self.log(f"โŒ cpufreq failed: {e}") + return False + + def set_frequency_msr(self, target_freq: int) -> bool: + """Set frequency using MSR (for older CPUs)""" + # Only Core 2 Quad has known MSR multipliers + if self.cpu_info.generation != CPUGeneration.CORE2: + self.log("โš ๏ธ MSR: No multiplier table for this CPU generation") + return False + + # Find closest frequency in multiplier table + available_freqs = sorted(self.CORE2_QUAD_MULTIPLIERS.keys()) + closest_freq = min(available_freqs, key=lambda x: abs(x - target_freq)) + + if abs(closest_freq - target_freq) > 200: + self.log(f"โš ๏ธ MSR: Target {target_freq}MHz too far from available frequencies") + return False + + try: + msr_value = self.CORE2_QUAD_MULTIPLIERS[closest_freq] + + # Load MSR module + subprocess.run(["sudo", "modprobe", "msr"], + check=True, capture_output=True, timeout=5) + + # Write to IA32_PERF_CTL register (0x199) + subprocess.run([ + "sudo", "wrmsr", "-a", "0x199", f"0x{msr_value:X}" + ], check=True, capture_output=True, timeout=5) + + self.log(f"โœ… MSR: Set to {closest_freq}MHz (MSR=0x{msr_value:X})") + return True + + except subprocess.TimeoutExpired: + self.log("โŒ MSR: Command timeout") + return False + except Exception as e: + self.log(f"โŒ MSR failed: {e}") + return False + + def set_frequency_cpupower(self, target_freq: int) -> bool: + """Set frequency using cpupower utility""" + try: + result = subprocess.run([ + "sudo", "cpupower", "frequency-set", "-f", f"{target_freq}MHz" + ], capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + self.log(f"โœ… cpupower: Set to {target_freq}MHz") + return True + else: + self.log(f"โŒ cpupower failed: {result.stderr}") + return False + + except Exception as e: + self.log(f"โŒ cpupower error: {e}") + return False + + def set_frequency(self, target_freq: int) -> bool: + """Set CPU frequency using best available method""" + if self.is_ci: + self.log(f"CI Mode: Simulating frequency set to {target_freq}MHz") + return True + + # Clamp to valid range + target_freq = max(self.cpu_info.min_freq_mhz, + min(target_freq, self.cpu_info.max_freq_mhz)) + + # Try selected control method + if self.control_method in ["cpufreq", "cpufreq_intel_pstate"]: + return self.set_frequency_cpufreq(target_freq) + elif self.control_method == "msr": + return self.set_frequency_msr(target_freq) + elif self.control_method == "cpupower": + return self.set_frequency_cpupower(target_freq) + else: + self.log("โŒ No frequency control method available") + return False + + def set_profile(self, profile_name: str) -> bool: + """Set frequency by profile name""" + profiles = {p.name: p for p in self.get_frequency_profiles()} + + if profile_name not in profiles: + self.log(f"โŒ Unknown profile: {profile_name}") + return False + + profile = profiles[profile_name] + self.log(f"๐ŸŽฏ Applying profile: {profile.name} ({profile.description})") + return self.set_frequency(profile.frequency_mhz) + + def get_current_frequency(self) -> Optional[int]: + """Get current CPU frequency in MHz""" + try: + # Try /proc/cpuinfo first + with open("/proc/cpuinfo", "r") as f: + for line in f: + if "cpu MHz" in line: + return int(float(line.split(":")[1].strip())) + except Exception: + pass + + # Try cpufreq + try: + with open("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq", "r") as f: + return int(f.read().strip()) // 1000 + except Exception: + pass + + return None + + def status_report(self): + """Generate status report""" + current_freq = self.get_current_frequency() + + print("=" * 60) + print("โšก UNIVERSAL CPU FREQUENCY MANAGER - STATUS") + print("=" * 60) + print() + print(f"๐Ÿ–ฅ๏ธ CPU: {self.cpu_info.model_name}") + print(f"๐Ÿ“Š Vendor: {self.cpu_info.vendor.value.upper()}") + print(f"๐Ÿ”ง Generation: {self.cpu_info.generation.value}") + print(f"๐Ÿ’พ Cores: {self.cpu_info.cores}") + print() + print(f"โšก Current Frequency: {current_freq or 'Unknown'} MHz") + print(f"๐Ÿ“ˆ Frequency Range: {self.cpu_info.min_freq_mhz}-{self.cpu_info.max_freq_mhz} MHz") + print(f"๐Ÿ”Œ Control Method: {self.control_method}") + print() + print("๐ŸŽฏ Available Profiles:") + for profile in self.get_frequency_profiles(): + print(f" โ€ข {profile.name:12} : {profile.frequency_mhz:4} MHz - {profile.description}") + print() + print("=" * 60) + + +def main(): + """Main CLI interface""" + if len(sys.argv) < 2: + print("Usage: universal_cpu_manager.py {status|set |profile |detect}") + print() + print("Commands:") + print(" status - Show current status") + print(" set - Set frequency in MHz") + print(" profile - Set profile (performance/balanced/powersave/emergency)") + print(" detect - Show hardware detection info") + sys.exit(1) + + manager = UniversalCPUManager() + command = sys.argv[1].lower() + + if command == "status": + manager.status_report() + + elif command == "detect": + print(manager.detector.generate_report()) + + elif command == "set" and len(sys.argv) >= 3: + try: + target_freq = int(sys.argv[2]) + if manager.set_frequency(target_freq): + print(f"โœ… Frequency set to {target_freq}MHz") + time.sleep(1) + manager.status_report() + else: + print(f"โŒ Failed to set frequency") + sys.exit(1) + except ValueError: + print("โŒ Invalid frequency value") + sys.exit(1) + + elif command == "profile" and len(sys.argv) >= 3: + profile_name = sys.argv[2].lower() + if manager.set_profile(profile_name): + print(f"โœ… Profile '{profile_name}' applied") + time.sleep(1) + manager.status_report() + else: + print(f"โŒ Failed to apply profile '{profile_name}'") + sys.exit(1) + + else: + print("โŒ Unknown command or missing arguments") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/src/hardware/__pycache__/hardware_detector.cpython-311.pyc b/src/hardware/__pycache__/hardware_detector.cpython-311.pyc new file mode 100644 index 0000000..b70dc20 Binary files /dev/null and b/src/hardware/__pycache__/hardware_detector.cpython-311.pyc differ diff --git a/src/hardware/hardware_detector.py b/src/hardware/hardware_detector.py new file mode 100644 index 0000000..6022acb --- /dev/null +++ b/src/hardware/hardware_detector.py @@ -0,0 +1,464 @@ +#!/usr/bin/env python3 +""" +Universal Hardware Detector +Detects CPU, GPU, and thermal capabilities across different hardware platforms +Supports: Intel (old/new), AMD, various GPUs (AMD, NVIDIA, Intel) +""" + +import os +import re +import subprocess +from pathlib import Path +from typing import Optional, Dict, List, Tuple +from dataclasses import dataclass +from enum import Enum + + +class CPUVendor(Enum): + INTEL = "intel" + AMD = "amd" + UNKNOWN = "unknown" + + +class CPUGeneration(Enum): + # Intel generations + CORE2 = "core2" # Core 2 Duo/Quad (2006-2011) + NEHALEM = "nehalem" # Core i3/i5/i7 1st gen (2008-2010) + SANDY_BRIDGE = "sandybridge" # 2nd gen (2011) + IVY_BRIDGE = "ivybridge" # 3rd gen (2012) + HASWELL = "haswell" # 4th gen (2013) + BROADWELL = "broadwell" # 5th gen (2014) + SKYLAKE_PLUS = "skylake+" # 6th gen+ (2015+) + + # AMD generations + K8 = "k8" # Athlon 64, Opteron (2003-2008) + K10 = "k10" # Phenom (2007-2012) + BULLDOZER = "bulldozer" # FX series (2011-2017) + ZEN = "zen" # Ryzen (2017+) + + UNKNOWN = "unknown" + + +class GPUVendor(Enum): + AMD = "amd" + NVIDIA = "nvidia" + INTEL = "intel" + UNKNOWN = "unknown" + + +@dataclass +class CPUInfo: + vendor: CPUVendor + model_name: str + generation: CPUGeneration + cores: int + min_freq_mhz: int + max_freq_mhz: int + current_freq_mhz: int + supports_msr: bool + supports_cpufreq: bool + thermal_max_safe: int # Maximum safe temperature in Celsius + + +@dataclass +class GPUInfo: + vendor: GPUVendor + model_name: str + device_path: Optional[str] # e.g., /sys/class/drm/card0 + supports_power_profile: bool + supports_power_cap: bool + + +@dataclass +class ThermalInfo: + zones: List[str] # List of thermal zone paths + current_temp: Optional[int] # Current temperature in Celsius + max_safe_temp: int # Maximum safe temperature + + +class HardwareDetector: + """Universal hardware detector for CPU, GPU, and thermal capabilities""" + + def __init__(self): + self.cpu_info = self._detect_cpu() + self.gpu_info = self._detect_gpu() + self.thermal_info = self._detect_thermal() + + def _detect_cpu(self) -> CPUInfo: + """Detect CPU information and capabilities""" + vendor = CPUVendor.UNKNOWN + model_name = "Unknown CPU" + generation = CPUGeneration.UNKNOWN + cores = 1 + + # Read /proc/cpuinfo + try: + with open("/proc/cpuinfo", "r") as f: + cpuinfo = f.read() + + # Extract model name + model_match = re.search(r"model name\s*:\s*(.+)", cpuinfo) + if model_match: + model_name = model_match.group(1).strip() + + # Detect vendor + if "Intel" in model_name: + vendor = CPUVendor.INTEL + elif "AMD" in model_name: + vendor = CPUVendor.AMD + + # Count cores + cores = len(re.findall(r"^processor\s*:", cpuinfo, re.MULTILINE)) + + # Detect generation + generation = self._detect_cpu_generation(vendor, model_name) + + except Exception as e: + print(f"Warning: Failed to read /proc/cpuinfo: {e}") + + # Detect frequency range + min_freq, max_freq = self._detect_frequency_range() + current_freq = self._get_current_cpu_freq() or max_freq + + # Detect MSR support + supports_msr = self._check_msr_support() + + # Detect cpufreq support + supports_cpufreq = Path("/sys/devices/system/cpu/cpu0/cpufreq").exists() + + # Determine safe thermal max + thermal_max = self._get_thermal_max_safe(vendor, generation) + + return CPUInfo( + vendor=vendor, + model_name=model_name, + generation=generation, + cores=cores, + min_freq_mhz=min_freq, + max_freq_mhz=max_freq, + current_freq_mhz=current_freq, + supports_msr=supports_msr, + supports_cpufreq=supports_cpufreq, + thermal_max_safe=thermal_max + ) + + def _detect_cpu_generation(self, vendor: CPUVendor, model_name: str) -> CPUGeneration: + """Detect CPU generation from model name""" + if vendor == CPUVendor.INTEL: + # Core 2 series + if re.search(r"Core\(TM\)2|Pentium\(R\) Dual", model_name): + return CPUGeneration.CORE2 + # Core i-series by generation + elif "i3-2" in model_name or "i5-2" in model_name or "i7-2" in model_name: + return CPUGeneration.SANDY_BRIDGE + elif "i3-3" in model_name or "i5-3" in model_name or "i7-3" in model_name: + return CPUGeneration.IVY_BRIDGE + elif "i3-4" in model_name or "i5-4" in model_name or "i7-4" in model_name: + return CPUGeneration.HASWELL + elif "i3-5" in model_name or "i5-5" in model_name or "i7-5" in model_name: + return CPUGeneration.BROADWELL + elif re.search(r"i[357]-[6-9]|i[357]-1[0-9]", model_name): + return CPUGeneration.SKYLAKE_PLUS + elif "i3" in model_name or "i5" in model_name or "i7" in model_name: + return CPUGeneration.NEHALEM + + elif vendor == CPUVendor.AMD: + if "Ryzen" in model_name or "EPYC" in model_name: + return CPUGeneration.ZEN + elif "FX" in model_name or "Bulldozer" in model_name: + return CPUGeneration.BULLDOZER + elif "Phenom" in model_name or "Athlon II" in model_name: + return CPUGeneration.K10 + elif "Athlon 64" in model_name or "Opteron" in model_name: + return CPUGeneration.K8 + + return CPUGeneration.UNKNOWN + + def _detect_frequency_range(self) -> Tuple[int, int]: + """Detect CPU frequency range (min, max) in MHz""" + min_freq = 800 # Conservative default + max_freq = 3000 + + # Try cpufreq first + try: + cpuinfo_min = Path("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq") + cpuinfo_max = Path("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq") + + if cpuinfo_min.exists() and cpuinfo_max.exists(): + min_freq = int(cpuinfo_min.read_text().strip()) // 1000 + max_freq = int(cpuinfo_max.read_text().strip()) // 1000 + return min_freq, max_freq + except Exception: + pass + + # Fallback: parse /proc/cpuinfo for current freq and estimate + try: + with open("/proc/cpuinfo", "r") as f: + for line in f: + if "cpu MHz" in line: + current = int(float(line.split(":")[1].strip())) + # Estimate range based on current frequency + max_freq = max(current, 2000) + min_freq = max_freq // 3 # Rough estimate + break + except Exception: + pass + + return min_freq, max_freq + + def _get_current_cpu_freq(self) -> Optional[int]: + """Get current CPU frequency in MHz""" + try: + with open("/proc/cpuinfo", "r") as f: + for line in f: + if "cpu MHz" in line: + return int(float(line.split(":")[1].strip())) + except Exception: + pass + return None + + def _check_msr_support(self) -> bool: + """Check if MSR (Model Specific Registers) are supported""" + # Check if msr module can be loaded + try: + subprocess.run(["modprobe", "msr"], check=False, capture_output=True, timeout=2) + return Path("/dev/cpu/0/msr").exists() + except Exception: + return False + + def _get_thermal_max_safe(self, vendor: CPUVendor, generation: CPUGeneration) -> int: + """Get maximum safe temperature for CPU""" + # Intel thermal limits + if vendor == CPUVendor.INTEL: + if generation == CPUGeneration.CORE2: + return 85 # Core 2 series: ~85ยฐC max + elif generation in [CPUGeneration.NEHALEM, CPUGeneration.SANDY_BRIDGE]: + return 95 # Older Core i: ~95ยฐC + else: + return 100 # Modern Intel: ~100ยฐC + + # AMD thermal limits + elif vendor == CPUVendor.AMD: + if generation in [CPUGeneration.K8, CPUGeneration.K10]: + return 70 # Old AMD: lower limits + elif generation == CPUGeneration.BULLDOZER: + return 75 + else: + return 95 # Modern AMD Ryzen + + return 85 # Conservative default + + def _detect_gpu(self) -> GPUInfo: + """Detect GPU information""" + vendor = GPUVendor.UNKNOWN + model_name = "Unknown GPU" + device_path = None + supports_power_profile = False + supports_power_cap = False + + # Search for GPU devices in /sys/class/drm + drm_path = Path("/sys/class/drm") + if drm_path.exists(): + for card_dir in drm_path.glob("card*"): + # Skip card*-* (these are connectors, not cards) + if "-" in card_dir.name: + continue + + device_dir = card_dir / "device" + if not device_dir.exists(): + continue + + # Try to identify vendor + vendor_id = None + try: + vendor_file = device_dir / "vendor" + if vendor_file.exists(): + vendor_id = vendor_file.read_text().strip() + except Exception: + pass + + # Determine vendor from ID + if vendor_id: + if vendor_id == "0x1002": + vendor = GPUVendor.AMD + elif vendor_id in ["0x10de", "0x10DE"]: + vendor = GPUVendor.NVIDIA + elif vendor_id == "0x8086": + vendor = GPUVendor.INTEL + + # Try to get model name + try: + uevent_file = device_dir / "uevent" + if uevent_file.exists(): + uevent = uevent_file.read_text() + pci_id_match = re.search(r"PCI_ID=([0-9A-Fa-f:]+)", uevent) + if pci_id_match: + model_name = f"GPU {pci_id_match.group(1)}" + except Exception: + pass + + # Check power management capabilities + power_profile = device_dir / "power_profile" + power_cap = device_dir / "hwmon" / "hwmon0" / "power1_cap" + + if power_profile.exists(): + supports_power_profile = True + device_path = str(card_dir) + break # Use first GPU with power_profile support + + if power_cap.exists(): + supports_power_cap = True + if not device_path: + device_path = str(card_dir) + + # Fallback: Try lspci + if vendor == GPUVendor.UNKNOWN: + try: + result = subprocess.run( + ["lspci", "-nn"], + capture_output=True, + text=True, + timeout=2 + ) + for line in result.stdout.splitlines(): + if "VGA" in line or "3D controller" in line: + if "AMD" in line or "ATI" in line: + vendor = GPUVendor.AMD + model_name = line.split(":", 1)[1].strip() + break + elif "NVIDIA" in line: + vendor = GPUVendor.NVIDIA + model_name = line.split(":", 1)[1].strip() + break + elif "Intel" in line: + vendor = GPUVendor.INTEL + model_name = line.split(":", 1)[1].strip() + break + except Exception: + pass + + return GPUInfo( + vendor=vendor, + model_name=model_name, + device_path=device_path, + supports_power_profile=supports_power_profile, + supports_power_cap=supports_power_cap + ) + + def _detect_thermal(self) -> ThermalInfo: + """Detect thermal monitoring capabilities""" + zones = [] + current_temp = None + + # Find all thermal zones + thermal_base = Path("/sys/class/thermal") + if thermal_base.exists(): + for zone in thermal_base.glob("thermal_zone*"): + temp_file = zone / "temp" + if temp_file.exists(): + zones.append(str(temp_file)) + + # Read temperature from first zone + if current_temp is None: + try: + temp_millidegrees = int(temp_file.read_text().strip()) + current_temp = temp_millidegrees // 1000 + except Exception: + pass + + # Use CPU thermal max as system thermal max + max_safe_temp = self.cpu_info.thermal_max_safe + + return ThermalInfo( + zones=zones, + current_temp=current_temp, + max_safe_temp=max_safe_temp + ) + + def generate_report(self) -> str: + """Generate human-readable hardware report""" + report = [] + report.append("=" * 60) + report.append("๐Ÿ” HARDWARE DETECTION REPORT") + report.append("=" * 60) + report.append("") + + # CPU Info + report.append("๐Ÿ–ฅ๏ธ CPU INFORMATION:") + report.append(f" Vendor: {self.cpu_info.vendor.value.upper()}") + report.append(f" Model: {self.cpu_info.model_name}") + report.append(f" Generation: {self.cpu_info.generation.value}") + report.append(f" Cores: {self.cpu_info.cores}") + report.append(f" Frequency: {self.cpu_info.min_freq_mhz}-{self.cpu_info.max_freq_mhz} MHz (current: {self.cpu_info.current_freq_mhz} MHz)") + report.append(f" MSR Support: {'โœ… Yes' if self.cpu_info.supports_msr else 'โŒ No'}") + report.append(f" CPUFreq Support: {'โœ… Yes' if self.cpu_info.supports_cpufreq else 'โŒ No'}") + report.append(f" Thermal Max Safe: {self.cpu_info.thermal_max_safe}ยฐC") + report.append("") + + # GPU Info + report.append("๐ŸŽฎ GPU INFORMATION:") + report.append(f" Vendor: {self.gpu_info.vendor.value.upper()}") + report.append(f" Model: {self.gpu_info.model_name}") + report.append(f" Device Path: {self.gpu_info.device_path or 'Not found'}") + report.append(f" Power Profile Support: {'โœ… Yes' if self.gpu_info.supports_power_profile else 'โŒ No'}") + report.append(f" Power Cap Support: {'โœ… Yes' if self.gpu_info.supports_power_cap else 'โŒ No'}") + report.append("") + + # Thermal Info + report.append("๐ŸŒก๏ธ THERMAL INFORMATION:") + report.append(f" Thermal Zones: {len(self.thermal_info.zones)}") + if self.thermal_info.current_temp: + report.append(f" Current Temperature: {self.thermal_info.current_temp}ยฐC") + report.append(f" Maximum Safe Temp: {self.thermal_info.max_safe_temp}ยฐC") + report.append("") + + report.append("=" * 60) + + return "\n".join(report) + + def is_compatible(self) -> Tuple[bool, List[str]]: + """Check if hardware is compatible with power management""" + compatible = True + issues = [] + + # Check CPU support + if not self.cpu_info.supports_cpufreq and not self.cpu_info.supports_msr: + compatible = False + issues.append("โŒ No CPU frequency control available (neither cpufreq nor MSR)") + elif not self.cpu_info.supports_cpufreq: + issues.append("โš ๏ธ CPUFreq not available, will use MSR (requires root)") + + # Check thermal + if not self.thermal_info.zones: + issues.append("โš ๏ธ No thermal zones found - temperature monitoring disabled") + + # GPU is optional + if not self.gpu_info.supports_power_profile and not self.gpu_info.supports_power_cap: + issues.append("โ„น๏ธ GPU power management not available (optional)") + + if compatible: + issues.insert(0, "โœ… Hardware is compatible with power management") + + return compatible, issues + + +def main(): + """Test hardware detection""" + detector = HardwareDetector() + print(detector.generate_report()) + + compatible, issues = detector.is_compatible() + print("\n๐Ÿ” COMPATIBILITY CHECK:") + for issue in issues: + print(f" {issue}") + + if not compatible: + print("\nโŒ Hardware not fully compatible - some features may not work") + return 1 + else: + print("\nโœ… All systems ready for power management!") + return 0 + + +if __name__ == "__main__": + exit(main()) diff --git a/src/sensors/__pycache__/fan_controller.cpython-311.pyc b/src/sensors/__pycache__/fan_controller.cpython-311.pyc new file mode 100644 index 0000000..4b90593 Binary files /dev/null and b/src/sensors/__pycache__/fan_controller.cpython-311.pyc differ diff --git a/src/sensors/__pycache__/gpu_monitor.cpython-311.pyc b/src/sensors/__pycache__/gpu_monitor.cpython-311.pyc new file mode 100644 index 0000000..144a396 Binary files /dev/null and b/src/sensors/__pycache__/gpu_monitor.cpython-311.pyc differ diff --git a/src/sensors/__pycache__/universal_sensor_detector.cpython-311.pyc b/src/sensors/__pycache__/universal_sensor_detector.cpython-311.pyc new file mode 100644 index 0000000..1a14ffe Binary files /dev/null and b/src/sensors/__pycache__/universal_sensor_detector.cpython-311.pyc differ diff --git a/src/sensors/fan_controller.py b/src/sensors/fan_controller.py new file mode 100644 index 0000000..627f6c1 --- /dev/null +++ b/src/sensors/fan_controller.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python3 +""" +Universal Fan Controller +Controls CPU and GPU fans on all platforms +Supports: PWM control (Linux), NVIDIA GPUs, AMD GPUs +Works on atypical systems including all-in-one PCs +""" + +import os +import subprocess +from pathlib import Path +from typing import List, Optional, Dict +from dataclasses import dataclass +from enum import Enum + + +class FanControlMode(Enum): + AUTO = "auto" + MANUAL = "manual" + + +@dataclass +class FanInfo: + """Fan information""" + name: str + current_speed: Optional[int] # RPM + current_pwm: Optional[int] # 0-255 + current_percent: Optional[int] # 0-100 + pwm_path: Optional[str] + pwm_enable_path: Optional[str] + mode: FanControlMode + + +class UniversalFanController: + """ + Universal fan controller + Controls: + - CPU fans via PWM (sysfs) + - Case fans via PWM + - GPU fans (NVIDIA via nvidia-settings, AMD via sysfs) + """ + + def __init__(self): + self.pwm_fans = self._detect_pwm_fans() + self.gpu_fans = self._detect_gpu_fans() + + def _detect_pwm_fans(self) -> List[Dict]: + """Detect all PWM-controllable fans""" + fans = [] + hwmon_base = Path("/sys/class/hwmon") + + if not hwmon_base.exists(): + return fans + + for hwmon_dir in hwmon_base.glob("hwmon*"): + chip_name = "unknown" + + # Get chip name + name_file = hwmon_dir / "name" + if name_file.exists(): + chip_name = name_file.read_text().strip() + + # Find PWM controls + for pwm_file in hwmon_dir.glob("pwm[0-9]"): + try: + pwm_num = pwm_file.name.replace("pwm", "") + + # Get PWM enable path (for switching modes) + pwm_enable_file = hwmon_dir / f"pwm{pwm_num}_enable" + + # Get fan input (RPM) if available + fan_input_file = hwmon_dir / f"fan{pwm_num}_input" + + # Get current PWM value + current_pwm = int(pwm_file.read_text().strip()) + + # Get current RPM if available + current_rpm = None + if fan_input_file.exists(): + try: + current_rpm = int(fan_input_file.read_text().strip()) + except Exception: + pass + + # Get current mode + mode = FanControlMode.AUTO + if pwm_enable_file.exists(): + try: + enable_value = int(pwm_enable_file.read_text().strip()) + # 0 = full speed, 1 = manual, 2 = auto, 3+ = varies by driver + mode = FanControlMode.MANUAL if enable_value == 1 else FanControlMode.AUTO + except Exception: + pass + + fans.append({ + 'name': f"{chip_name}/pwm{pwm_num}", + 'chip': chip_name, + 'pwm_path': str(pwm_file), + 'pwm_enable_path': str(pwm_enable_file) if pwm_enable_file.exists() else None, + 'fan_input_path': str(fan_input_file) if fan_input_file.exists() else None, + 'current_pwm': current_pwm, + 'current_rpm': current_rpm, + 'mode': mode + }) + + except Exception as e: + print(f"Error detecting PWM fan: {e}") + + return fans + + def _detect_gpu_fans(self) -> List[Dict]: + """Detect GPU fans""" + gpu_fans = [] + + # NVIDIA GPUs + try: + result = subprocess.run( + ["nvidia-smi", "--query-gpu=index,name", "--format=csv,noheader"], + capture_output=True, + text=True, + timeout=3 + ) + + if result.returncode == 0: + for line in result.stdout.strip().split('\n'): + if line: + parts = [p.strip() for p in line.split(',')] + gpu_index = int(parts[0]) + gpu_name = parts[1] if len(parts) > 1 else f"GPU {gpu_index}" + + gpu_fans.append({ + 'type': 'nvidia', + 'index': gpu_index, + 'name': f"{gpu_name} Fan" + }) + except (FileNotFoundError, subprocess.TimeoutExpired, Exception): + pass + + # AMD GPUs (via sysfs) + drm_path = Path("/sys/class/drm") + if drm_path.exists(): + for card_dir in sorted(drm_path.glob("card[0-9]*")): + if "-" in card_dir.name: + continue + + device_dir = card_dir / "device" + vendor_file = device_dir / "vendor" + + if vendor_file.exists(): + vendor_id = vendor_file.read_text().strip() + if vendor_id == "0x1002": # AMD + # Check if PWM control exists + hwmon_dir = device_dir / "hwmon" + if hwmon_dir.exists(): + for hwmon_subdir in hwmon_dir.glob("hwmon*"): + pwm_files = list(hwmon_subdir.glob("pwm[0-9]")) + if pwm_files: + gpu_fans.append({ + 'type': 'amd', + 'name': f"AMD GPU {card_dir.name} Fan", + 'card_path': str(card_dir), + 'pwm_path': str(pwm_files[0]) + }) + + return gpu_fans + + def get_fan_info(self, fan_index: int) -> Optional[FanInfo]: + """Get information about a specific PWM fan""" + if fan_index >= len(self.pwm_fans): + return None + + fan = self.pwm_fans[fan_index] + + return FanInfo( + name=fan['name'], + current_speed=fan['current_rpm'], + current_pwm=fan['current_pwm'], + current_percent=int((fan['current_pwm'] / 255) * 100) if fan['current_pwm'] else None, + pwm_path=fan['pwm_path'], + pwm_enable_path=fan['pwm_enable_path'], + mode=fan['mode'] + ) + + def set_pwm_fan_speed(self, fan_index: int, percent: int) -> bool: + """ + Set PWM fan speed (0-100%) + + Args: + fan_index: Index of fan in self.pwm_fans + percent: Fan speed percentage (0-100) + + Returns: + True if successful, False otherwise + """ + if fan_index >= len(self.pwm_fans): + print(f"Fan index {fan_index} out of range") + return False + + fan = self.pwm_fans[fan_index] + + # Clamp to 0-100% + percent = max(0, min(100, percent)) + + # Convert to PWM value (0-255) + pwm_value = int((percent / 100) * 255) + + try: + # First, set to manual mode if possible + if fan['pwm_enable_path']: + try: + with open(fan['pwm_enable_path'], 'w') as f: + f.write('1') # 1 = manual mode + except PermissionError: + print(f"Permission denied. Try running with sudo.") + return False + + # Set PWM value + with open(fan['pwm_path'], 'w') as f: + f.write(str(pwm_value)) + + print(f"โœ… Set {fan['name']} to {percent}% (PWM {pwm_value})") + return True + + except PermissionError: + print(f"โŒ Permission denied. Run with sudo to control fans.") + return False + except Exception as e: + print(f"โŒ Error setting fan speed: {e}") + return False + + def set_fan_auto(self, fan_index: int) -> bool: + """Set fan to automatic mode""" + if fan_index >= len(self.pwm_fans): + return False + + fan = self.pwm_fans[fan_index] + + if not fan['pwm_enable_path']: + print(f"Fan {fan['name']} doesn't support mode switching") + return False + + try: + with open(fan['pwm_enable_path'], 'w') as f: + f.write('2') # 2 = automatic mode + + print(f"โœ… Set {fan['name']} to automatic mode") + return True + + except PermissionError: + print(f"โŒ Permission denied. Run with sudo.") + return False + except Exception as e: + print(f"โŒ Error: {e}") + return False + + def set_nvidia_gpu_fan(self, gpu_index: int, percent: int) -> bool: + """ + Set NVIDIA GPU fan speed + + Args: + gpu_index: GPU index (0, 1, etc.) + percent: Fan speed percentage (0-100) + + Returns: + True if successful + """ + try: + # Enable manual fan control + subprocess.run([ + "nvidia-settings", + "-a", f"[gpu:{gpu_index}]/GPUFanControlState=1" + ], check=True, capture_output=True, timeout=5) + + # Set fan speed + subprocess.run([ + "nvidia-settings", + "-a", f"[fan:{gpu_index}]/GPUTargetFanSpeed={percent}" + ], check=True, capture_output=True, timeout=5) + + print(f"โœ… NVIDIA GPU {gpu_index} fan set to {percent}%") + return True + + except FileNotFoundError: + print("โŒ nvidia-settings not found. Install it to control NVIDIA GPU fans.") + return False + except subprocess.CalledProcessError as e: + print(f"โŒ Failed to set NVIDIA GPU fan: {e}") + return False + except Exception as e: + print(f"โŒ Error: {e}") + return False + + def set_nvidia_gpu_fan_auto(self, gpu_index: int) -> bool: + """Set NVIDIA GPU fan to automatic mode""" + try: + subprocess.run([ + "nvidia-settings", + "-a", f"[gpu:{gpu_index}]/GPUFanControlState=0" + ], check=True, capture_output=True, timeout=5) + + print(f"โœ… NVIDIA GPU {gpu_index} fan set to automatic") + return True + + except Exception as e: + print(f"โŒ Error: {e}") + return False + + def generate_report(self) -> str: + """Generate fan control report""" + lines = [] + lines.append("=" * 70) + lines.append("๐Ÿ’จ FAN CONTROLLER REPORT") + lines.append("=" * 70) + lines.append("") + + # PWM Fans + if self.pwm_fans: + lines.append(f"๐ŸŒ€ PWM Fans: {len(self.pwm_fans)}") + lines.append("-" * 70) + + for i, fan in enumerate(self.pwm_fans): + current_percent = int((fan['current_pwm'] / 255) * 100) if fan['current_pwm'] else 0 + mode_str = fan['mode'].value + + rpm_str = f"{fan['current_rpm']} RPM" if fan['current_rpm'] else "N/A" + + lines.append(f" [{i}] {fan['name']}") + lines.append(f" Speed: {current_percent}% ({fan['current_pwm']}/255 PWM) - {rpm_str}") + lines.append(f" Mode: {mode_str}") + lines.append(f" Control: {fan['pwm_path']}") + + lines.append("") + + # GPU Fans + if self.gpu_fans: + lines.append(f"๐ŸŽฎ GPU Fans: {len(self.gpu_fans)}") + lines.append("-" * 70) + + for fan in self.gpu_fans: + lines.append(f" โ€ข {fan['name']} ({fan['type'].upper()})") + + lines.append("") + + if not self.pwm_fans and not self.gpu_fans: + lines.append("โŒ No controllable fans detected") + lines.append("") + + lines.append("=" * 70) + return "\n".join(lines) + + +def main(): + """Test fan controller""" + import sys + + controller = UniversalFanController() + + if len(sys.argv) < 2: + print(controller.generate_report()) + print("\nUsage:") + print(" python3 fan_controller.py status - Show fan status") + print(" python3 fan_controller.py set - Set fan speed (0-100%)") + print(" python3 fan_controller.py auto - Set fan to automatic") + print(" python3 fan_controller.py nvidia - Set NVIDIA GPU fan") + return + + command = sys.argv[1] + + if command == "status": + print(controller.generate_report()) + + elif command == "set" and len(sys.argv) >= 4: + fan_index = int(sys.argv[2]) + percent = int(sys.argv[3]) + controller.set_pwm_fan_speed(fan_index, percent) + + elif command == "auto" and len(sys.argv) >= 3: + fan_index = int(sys.argv[2]) + controller.set_fan_auto(fan_index) + + elif command == "nvidia" and len(sys.argv) >= 4: + gpu_index = int(sys.argv[2]) + percent = int(sys.argv[3]) + controller.set_nvidia_gpu_fan(gpu_index, percent) + + else: + print("Unknown command or missing arguments") + + +if __name__ == "__main__": + main() diff --git a/src/sensors/gpu_monitor.py b/src/sensors/gpu_monitor.py new file mode 100644 index 0000000..04aa28d --- /dev/null +++ b/src/sensors/gpu_monitor.py @@ -0,0 +1,439 @@ +#!/usr/bin/env python3 +""" +Universal GPU Monitor +Monitors GPU temperature, fan speed, power, utilization +Supports: NVIDIA (nvidia-smi), AMD (sysfs), Intel (sysfs) +Works on atypical systems and all-in-one PCs +""" + +import os +import re +import subprocess +from pathlib import Path +from typing import Optional, List, Dict +from dataclasses import dataclass +from enum import Enum + + +class GPUVendor(Enum): + NVIDIA = "nvidia" + AMD = "amd" + INTEL = "intel" + UNKNOWN = "unknown" + + +@dataclass +class GPUMetrics: + """GPU metrics data""" + vendor: GPUVendor + name: str + temperature: Optional[int] # Celsius + fan_speed: Optional[int] # Percentage (0-100) + fan_rpm: Optional[int] # RPM + power_usage: Optional[int] # Watts + power_limit: Optional[int] # Watts + utilization: Optional[int] # Percentage + memory_used: Optional[int] # MB + memory_total: Optional[int] # MB + device_path: Optional[str] # sysfs path + + +class UniversalGPUMonitor: + """ + Universal GPU monitor supporting multiple vendors + Handles atypical systems, all-in-one PCs, multiple GPUs + """ + + def __init__(self): + self.gpus = self._detect_all_gpus() + self.primary_gpu = self.gpus[0] if self.gpus else None + + def _detect_all_gpus(self) -> List[Dict]: + """Detect all GPUs in the system""" + gpus = [] + + # Try NVIDIA + nvidia_gpus = self._detect_nvidia_gpus() + gpus.extend(nvidia_gpus) + + # Try AMD + amd_gpus = self._detect_amd_gpus() + gpus.extend(amd_gpus) + + # Try Intel + intel_gpus = self._detect_intel_gpus() + gpus.extend(intel_gpus) + + return gpus + + def _detect_nvidia_gpus(self) -> List[Dict]: + """Detect NVIDIA GPUs using nvidia-smi""" + gpus = [] + try: + # Check if nvidia-smi is available + result = subprocess.run( + ["nvidia-smi", "--query-gpu=index,name,uuid", "--format=csv,noheader"], + capture_output=True, + text=True, + timeout=5 + ) + + if result.returncode == 0: + for line in result.stdout.strip().split('\n'): + if line: + parts = [p.strip() for p in line.split(',')] + if len(parts) >= 2: + gpus.append({ + 'vendor': GPUVendor.NVIDIA, + 'index': int(parts[0]), + 'name': parts[1], + 'uuid': parts[2] if len(parts) > 2 else None + }) + except (FileNotFoundError, subprocess.TimeoutExpired, Exception): + pass + + return gpus + + def _detect_amd_gpus(self) -> List[Dict]: + """Detect AMD GPUs via sysfs""" + gpus = [] + drm_path = Path("/sys/class/drm") + + if drm_path.exists(): + for card_dir in sorted(drm_path.glob("card[0-9]*")): + # Skip card*-* (connectors) + if "-" in card_dir.name: + continue + + device_dir = card_dir / "device" + if not device_dir.exists(): + continue + + # Check vendor ID + vendor_file = device_dir / "vendor" + if vendor_file.exists(): + vendor_id = vendor_file.read_text().strip() + if vendor_id == "0x1002": # AMD + # Get device name + name = "AMD GPU" + try: + # Try to get GPU name from uevent + uevent = (device_dir / "uevent").read_text() + pci_id_match = re.search(r"PCI_ID=([0-9A-Fa-f:]+)", uevent) + if pci_id_match: + name = f"AMD GPU {pci_id_match.group(1)}" + except Exception: + pass + + gpus.append({ + 'vendor': GPUVendor.AMD, + 'index': len(gpus), + 'name': name, + 'device_path': str(card_dir) + }) + + return gpus + + def _detect_intel_gpus(self) -> List[Dict]: + """Detect Intel integrated GPUs via sysfs""" + gpus = [] + drm_path = Path("/sys/class/drm") + + if drm_path.exists(): + for card_dir in sorted(drm_path.glob("card[0-9]*")): + if "-" in card_dir.name: + continue + + device_dir = card_dir / "device" + if not device_dir.exists(): + continue + + vendor_file = device_dir / "vendor" + if vendor_file.exists(): + vendor_id = vendor_file.read_text().strip() + if vendor_id == "0x8086": # Intel + name = "Intel Integrated Graphics" + + gpus.append({ + 'vendor': GPUVendor.INTEL, + 'index': len(gpus), + 'name': name, + 'device_path': str(card_dir) + }) + + return gpus + + def get_nvidia_metrics(self, gpu_index: int = 0) -> Optional[GPUMetrics]: + """Get metrics for NVIDIA GPU""" + try: + # Query multiple metrics at once + query = "temperature.gpu,fan.speed,power.draw,power.limit,utilization.gpu,memory.used,memory.total" + result = subprocess.run( + ["nvidia-smi", f"--id={gpu_index}", + f"--query-gpu={query}", + "--format=csv,noheader,nounits"], + capture_output=True, + text=True, + timeout=5 + ) + + if result.returncode != 0: + return None + + # Parse output + values = [v.strip() for v in result.stdout.strip().split(',')] + + # Get GPU name + name_result = subprocess.run( + ["nvidia-smi", f"--id={gpu_index}", "--query-gpu=name", "--format=csv,noheader"], + capture_output=True, + text=True, + timeout=3 + ) + name = name_result.stdout.strip() if name_result.returncode == 0 else "NVIDIA GPU" + + return GPUMetrics( + vendor=GPUVendor.NVIDIA, + name=name, + temperature=int(float(values[0])) if values[0] != 'N/A' else None, + fan_speed=int(float(values[1])) if values[1] != 'N/A' else None, + fan_rpm=None, # nvidia-smi doesn't provide RPM directly + power_usage=int(float(values[2])) if values[2] != 'N/A' else None, + power_limit=int(float(values[3])) if values[3] != 'N/A' else None, + utilization=int(float(values[4])) if values[4] != 'N/A' else None, + memory_used=int(float(values[5])) if values[5] != 'N/A' else None, + memory_total=int(float(values[6])) if values[6] != 'N/A' else None, + device_path=None + ) + + except Exception as e: + print(f"NVIDIA metrics error: {e}") + return None + + def get_amd_metrics(self, device_path: str) -> Optional[GPUMetrics]: + """Get metrics for AMD GPU via sysfs""" + try: + card_path = Path(device_path) + device_dir = card_path / "device" + hwmon_dir = device_dir / "hwmon" + + name = "AMD GPU" + temperature = None + fan_speed = None + fan_rpm = None + power_usage = None + + # Find hwmon directory + hwmon_path = None + if hwmon_dir.exists(): + hwmon_subdirs = list(hwmon_dir.glob("hwmon*")) + if hwmon_subdirs: + hwmon_path = hwmon_subdirs[0] + + if hwmon_path: + # Temperature (look for edge temperature) + temp_inputs = list(hwmon_path.glob("temp*_input")) + for temp_file in temp_inputs: + label_file = temp_file.parent / temp_file.name.replace("_input", "_label") + if label_file.exists(): + label = label_file.read_text().strip() + if "edge" in label.lower() or "junction" in label.lower(): + temp_milli = int(temp_file.read_text().strip()) + temperature = temp_milli // 1000 + break + + # If no labeled temp found, use first temp sensor + if temperature is None and temp_inputs: + temp_milli = int(temp_inputs[0].read_text().strip()) + temperature = temp_milli // 1000 + + # Fan speed (PWM = 0-255, convert to percentage) + pwm_files = list(hwmon_path.glob("pwm[0-9]")) + if pwm_files: + pwm_value = int(pwm_files[0].read_text().strip()) + fan_speed = int((pwm_value / 255) * 100) + + # Fan RPM + fan_input_files = list(hwmon_path.glob("fan*_input")) + if fan_input_files: + fan_rpm = int(fan_input_files[0].read_text().strip()) + + # Power usage + power_files = list(hwmon_path.glob("power*_average")) + if power_files: + power_micro = int(power_files[0].read_text().strip()) + power_usage = power_micro // 1000000 # Convert to watts + + return GPUMetrics( + vendor=GPUVendor.AMD, + name=name, + temperature=temperature, + fan_speed=fan_speed, + fan_rpm=fan_rpm, + power_usage=power_usage, + power_limit=None, + utilization=None, # AMD doesn't expose this easily + memory_used=None, + memory_total=None, + device_path=device_path + ) + + except Exception as e: + print(f"AMD metrics error: {e}") + return None + + def get_intel_metrics(self, device_path: str) -> Optional[GPUMetrics]: + """Get metrics for Intel iGPU via sysfs""" + try: + card_path = Path(device_path) + device_dir = card_path / "device" + hwmon_dir = device_dir / "hwmon" + + name = "Intel Integrated Graphics" + temperature = None + power_usage = None + + # Find hwmon directory + hwmon_path = None + if hwmon_dir.exists(): + hwmon_subdirs = list(hwmon_dir.glob("hwmon*")) + if hwmon_subdirs: + hwmon_path = hwmon_subdirs[0] + + if hwmon_path: + # Temperature + temp_inputs = list(hwmon_path.glob("temp*_input")) + if temp_inputs: + temp_milli = int(temp_inputs[0].read_text().strip()) + temperature = temp_milli // 1000 + + # Power + power_files = list(hwmon_path.glob("power*_average")) + if power_files: + power_micro = int(power_files[0].read_text().strip()) + power_usage = power_micro // 1000000 + + return GPUMetrics( + vendor=GPUVendor.INTEL, + name=name, + temperature=temperature, + fan_speed=None, # Intel iGPU typically shares CPU fan + fan_rpm=None, + power_usage=power_usage, + power_limit=None, + utilization=None, + memory_used=None, + memory_total=None, + device_path=device_path + ) + + except Exception as e: + print(f"Intel metrics error: {e}") + return None + + def get_metrics(self, gpu_index: int = 0) -> Optional[GPUMetrics]: + """Get metrics for specified GPU""" + if gpu_index >= len(self.gpus): + return None + + gpu = self.gpus[gpu_index] + vendor = gpu['vendor'] + + if vendor == GPUVendor.NVIDIA: + return self.get_nvidia_metrics(gpu.get('index', 0)) + elif vendor == GPUVendor.AMD: + return self.get_amd_metrics(gpu['device_path']) + elif vendor == GPUVendor.INTEL: + return self.get_intel_metrics(gpu['device_path']) + + return None + + def get_all_metrics(self) -> List[GPUMetrics]: + """Get metrics for all GPUs""" + metrics = [] + for i in range(len(self.gpus)): + m = self.get_metrics(i) + if m: + metrics.append(m) + return metrics + + def print_metrics(self, metrics: GPUMetrics): + """Print GPU metrics in human-readable format""" + print(f"๐ŸŽฎ {metrics.name} ({metrics.vendor.value.upper()})") + print(f" ๐ŸŒก๏ธ Temperature: {metrics.temperature}ยฐC" if metrics.temperature else " ๐ŸŒก๏ธ Temperature: N/A") + + if metrics.fan_speed is not None: + print(f" ๐Ÿ’จ Fan Speed: {metrics.fan_speed}%", end="") + if metrics.fan_rpm: + print(f" ({metrics.fan_rpm} RPM)") + else: + print() + + if metrics.power_usage is not None: + power_str = f" โšก Power: {metrics.power_usage}W" + if metrics.power_limit: + power_str += f" / {metrics.power_limit}W" + print(power_str) + + if metrics.utilization is not None: + print(f" ๐Ÿ“Š Utilization: {metrics.utilization}%") + + if metrics.memory_used is not None and metrics.memory_total is not None: + print(f" ๐Ÿ’พ Memory: {metrics.memory_used}MB / {metrics.memory_total}MB") + + def generate_report(self) -> str: + """Generate comprehensive GPU report""" + lines = [] + lines.append("=" * 60) + lines.append("๐ŸŽฎ GPU MONITORING REPORT") + lines.append("=" * 60) + lines.append("") + + if not self.gpus: + lines.append("โŒ No GPUs detected") + lines.append("") + lines.append("=" * 60) + return "\n".join(lines) + + lines.append(f"๐Ÿ“Š Detected GPUs: {len(self.gpus)}") + lines.append("") + + for i, gpu in enumerate(self.gpus): + lines.append(f"GPU #{i}: {gpu['name']} ({gpu['vendor'].value.upper()})") + + metrics = self.get_metrics(i) + if metrics: + if metrics.temperature is not None: + lines.append(f" ๐ŸŒก๏ธ Temperature: {metrics.temperature}ยฐC") + if metrics.fan_speed is not None: + fan_line = f" ๐Ÿ’จ Fan: {metrics.fan_speed}%" + if metrics.fan_rpm: + fan_line += f" ({metrics.fan_rpm} RPM)" + lines.append(fan_line) + if metrics.power_usage is not None: + power_line = f" โšก Power: {metrics.power_usage}W" + if metrics.power_limit: + power_line += f" / {metrics.power_limit}W" + lines.append(power_line) + if metrics.utilization is not None: + lines.append(f" ๐Ÿ“Š Utilization: {metrics.utilization}%") + if metrics.memory_used is not None and metrics.memory_total is not None: + mem_pct = int((metrics.memory_used / metrics.memory_total) * 100) + lines.append(f" ๐Ÿ’พ Memory: {metrics.memory_used}MB / {metrics.memory_total}MB ({mem_pct}%)") + else: + lines.append(" โš ๏ธ Unable to read metrics") + + lines.append("") + + lines.append("=" * 60) + return "\n".join(lines) + + +def main(): + """Test GPU monitoring""" + monitor = UniversalGPUMonitor() + print(monitor.generate_report()) + + +if __name__ == "__main__": + main() diff --git a/src/sensors/universal_sensor_detector.py b/src/sensors/universal_sensor_detector.py new file mode 100644 index 0000000..5804bcb --- /dev/null +++ b/src/sensors/universal_sensor_detector.py @@ -0,0 +1,478 @@ +#!/usr/bin/env python3 +""" +Universal Sensor Detector +Detects ALL possible sensors in the system +Works on atypical motherboards, all-in-one PCs, exotic configurations +Finds: CPU temps, GPU temps, motherboard sensors, fan sensors, voltage, power +""" + +import os +import re +import subprocess +from pathlib import Path +from typing import List, Dict, Optional +from dataclasses import dataclass +from enum import Enum + + +class SensorType(Enum): + TEMPERATURE = "temperature" + FAN = "fan" + VOLTAGE = "voltage" + POWER = "power" + CURRENT = "current" + ENERGY = "energy" + HUMIDITY = "humidity" + UNKNOWN = "unknown" + + +@dataclass +class Sensor: + """Individual sensor data""" + name: str + type: SensorType + value: Optional[float] + unit: str + path: Optional[str] + chip: str + label: str + + +class UniversalSensorDetector: + """ + Universal sensor detector + Finds sensors from: + - lm-sensors (sensors command) + - sysfs (/sys/class/hwmon, /sys/class/thermal) + - ACPI (/sys/class/power_supply) + - GPU-specific (nvidia-smi, AMD/Intel sysfs) + """ + + def __init__(self): + self.sensors = [] + self._detect_all_sensors() + + def _detect_all_sensors(self): + """Detect all available sensors""" + # Method 1: lm-sensors (most comprehensive) + self._detect_lm_sensors() + + # Method 2: sysfs hwmon + self._detect_sysfs_hwmon() + + # Method 3: thermal zones + self._detect_thermal_zones() + + # Method 4: ACPI power + self._detect_acpi_sensors() + + # Remove duplicates + self._deduplicate_sensors() + + def _detect_lm_sensors(self): + """Detect sensors using lm-sensors (sensors command)""" + try: + result = subprocess.run( + ["sensors", "-A"], # -A shows all sensors + capture_output=True, + text=True, + timeout=5 + ) + + if result.returncode != 0: + return + + current_chip = "unknown" + for line in result.stdout.split('\n'): + line = line.strip() + if not line: + continue + + # Chip name (e.g., "coretemp-isa-0000") + if not line.startswith(' ') and ':' not in line: + current_chip = line + continue + + # Sensor reading (e.g., "Core 0: +45.0ยฐC") + if ':' in line: + parts = line.split(':', 1) + label = parts[0].strip() + value_str = parts[1].strip() + + # Parse value + sensor = self._parse_sensor_line(label, value_str, current_chip) + if sensor: + self.sensors.append(sensor) + + except (FileNotFoundError, subprocess.TimeoutExpired, Exception) as e: + # lm-sensors not available, continue with other methods + pass + + def _parse_sensor_line(self, label: str, value_str: str, chip: str) -> Optional[Sensor]: + """Parse a sensor line from lm-sensors output""" + # Temperature + if 'ยฐC' in value_str or 'C' in value_str: + match = re.search(r'([+-]?\d+\.?\d*)\s*ยฐ?C', value_str) + if match: + return Sensor( + name=f"{chip}/{label}", + type=SensorType.TEMPERATURE, + value=float(match.group(1)), + unit="ยฐC", + path=None, + chip=chip, + label=label + ) + + # Fan (RPM) + if 'RPM' in value_str or 'rpm' in value_str: + match = re.search(r'(\d+)\s*RPM', value_str, re.IGNORECASE) + if match: + return Sensor( + name=f"{chip}/{label}", + type=SensorType.FAN, + value=float(match.group(1)), + unit="RPM", + path=None, + chip=chip, + label=label + ) + + # Voltage + if 'V' in value_str and 'ยฐ' not in value_str: + match = re.search(r'([+-]?\d+\.?\d*)\s*V', value_str) + if match: + return Sensor( + name=f"{chip}/{label}", + type=SensorType.VOLTAGE, + value=float(match.group(1)), + unit="V", + path=None, + chip=chip, + label=label + ) + + # Power + if 'W' in value_str: + match = re.search(r'(\d+\.?\d*)\s*W', value_str) + if match: + return Sensor( + name=f"{chip}/{label}", + type=SensorType.POWER, + value=float(match.group(1)), + unit="W", + path=None, + chip=chip, + label=label + ) + + return None + + def _detect_sysfs_hwmon(self): + """Detect sensors via sysfs hwmon""" + hwmon_base = Path("/sys/class/hwmon") + if not hwmon_base.exists(): + return + + for hwmon_dir in hwmon_base.glob("hwmon*"): + chip_name = "unknown" + + # Get chip name + name_file = hwmon_dir / "name" + if name_file.exists(): + chip_name = name_file.read_text().strip() + + # Temperature sensors + for temp_file in hwmon_dir.glob("temp*_input"): + try: + label = temp_file.stem.replace("_input", "") + label_file = hwmon_dir / f"{temp_file.stem.replace('_input', '_label')}" + + if label_file.exists(): + label = label_file.read_text().strip() + + temp_milli = int(temp_file.read_text().strip()) + temp_celsius = temp_milli / 1000.0 + + self.sensors.append(Sensor( + name=f"{chip_name}/{label}", + type=SensorType.TEMPERATURE, + value=temp_celsius, + unit="ยฐC", + path=str(temp_file), + chip=chip_name, + label=label + )) + except Exception: + pass + + # Fan sensors + for fan_file in hwmon_dir.glob("fan*_input"): + try: + label = fan_file.stem.replace("_input", "") + label_file = hwmon_dir / f"{fan_file.stem.replace('_input', '_label')}" + + if label_file.exists(): + label = label_file.read_text().strip() + + rpm = int(fan_file.read_text().strip()) + + self.sensors.append(Sensor( + name=f"{chip_name}/{label}", + type=SensorType.FAN, + value=float(rpm), + unit="RPM", + path=str(fan_file), + chip=chip_name, + label=label + )) + except Exception: + pass + + # Voltage sensors + for in_file in hwmon_dir.glob("in*_input"): + try: + label = in_file.stem.replace("_input", "") + label_file = hwmon_dir / f"{in_file.stem.replace('_input', '_label')}" + + if label_file.exists(): + label = label_file.read_text().strip() + + voltage_mv = int(in_file.read_text().strip()) + voltage = voltage_mv / 1000.0 + + self.sensors.append(Sensor( + name=f"{chip_name}/{label}", + type=SensorType.VOLTAGE, + value=voltage, + unit="V", + path=str(in_file), + chip=chip_name, + label=label + )) + except Exception: + pass + + # Power sensors + for power_file in hwmon_dir.glob("power*_average"): + try: + label = power_file.stem.replace("_average", "") + label_file = hwmon_dir / f"{label}_label" + + if label_file.exists(): + label = label_file.read_text().strip() + + power_micro = int(power_file.read_text().strip()) + power = power_micro / 1000000.0 + + self.sensors.append(Sensor( + name=f"{chip_name}/{label}", + type=SensorType.POWER, + value=power, + unit="W", + path=str(power_file), + chip=chip_name, + label=label + )) + except Exception: + pass + + def _detect_thermal_zones(self): + """Detect thermal zones""" + thermal_base = Path("/sys/class/thermal") + if not thermal_base.exists(): + return + + for zone_dir in thermal_base.glob("thermal_zone*"): + try: + zone_name = zone_dir.name + + # Get zone type (label) + type_file = zone_dir / "type" + label = type_file.read_text().strip() if type_file.exists() else zone_name + + # Get temperature + temp_file = zone_dir / "temp" + if temp_file.exists(): + temp_milli = int(temp_file.read_text().strip()) + temp_celsius = temp_milli / 1000.0 + + self.sensors.append(Sensor( + name=f"thermal/{label}", + type=SensorType.TEMPERATURE, + value=temp_celsius, + unit="ยฐC", + path=str(temp_file), + chip="thermal_zone", + label=label + )) + except Exception: + pass + + def _detect_acpi_sensors(self): + """Detect ACPI sensors (battery, AC adapter)""" + power_base = Path("/sys/class/power_supply") + if not power_base.exists(): + return + + for supply_dir in power_base.iterdir(): + try: + supply_name = supply_dir.name + + # Battery voltage + voltage_file = supply_dir / "voltage_now" + if voltage_file.exists(): + voltage_micro = int(voltage_file.read_text().strip()) + voltage = voltage_micro / 1000000.0 + + self.sensors.append(Sensor( + name=f"power/{supply_name}/voltage", + type=SensorType.VOLTAGE, + value=voltage, + unit="V", + path=str(voltage_file), + chip="acpi", + label=f"{supply_name} voltage" + )) + + # Battery current + current_file = supply_dir / "current_now" + if current_file.exists(): + current_micro = int(current_file.read_text().strip()) + current = current_micro / 1000000.0 + + self.sensors.append(Sensor( + name=f"power/{supply_name}/current", + type=SensorType.CURRENT, + value=current, + unit="A", + path=str(current_file), + chip="acpi", + label=f"{supply_name} current" + )) + + # Battery power + power_file = supply_dir / "power_now" + if power_file.exists(): + power_micro = int(power_file.read_text().strip()) + power = power_micro / 1000000.0 + + self.sensors.append(Sensor( + name=f"power/{supply_name}/power", + type=SensorType.POWER, + value=power, + unit="W", + path=str(power_file), + chip="acpi", + label=f"{supply_name} power" + )) + + # Battery energy + energy_file = supply_dir / "energy_now" + if energy_file.exists(): + energy_micro = int(energy_file.read_text().strip()) + energy = energy_micro / 1000000.0 + + self.sensors.append(Sensor( + name=f"power/{supply_name}/energy", + type=SensorType.ENERGY, + value=energy, + unit="Wh", + path=str(energy_file), + chip="acpi", + label=f"{supply_name} energy" + )) + + except Exception: + pass + + def _deduplicate_sensors(self): + """Remove duplicate sensors""" + seen = set() + unique_sensors = [] + + for sensor in self.sensors: + # Create unique key + key = (sensor.chip, sensor.label, sensor.type) + if key not in seen: + seen.add(key) + unique_sensors.append(sensor) + + self.sensors = unique_sensors + + def get_sensors_by_type(self, sensor_type: SensorType) -> List[Sensor]: + """Get all sensors of a specific type""" + return [s for s in self.sensors if s.type == sensor_type] + + def get_temperature_sensors(self) -> List[Sensor]: + """Get all temperature sensors""" + return self.get_sensors_by_type(SensorType.TEMPERATURE) + + def get_fan_sensors(self) -> List[Sensor]: + """Get all fan sensors""" + return self.get_sensors_by_type(SensorType.FAN) + + def generate_report(self) -> str: + """Generate comprehensive sensor report""" + lines = [] + lines.append("=" * 70) + lines.append("๐Ÿ” UNIVERSAL SENSOR DETECTION REPORT") + lines.append("=" * 70) + lines.append("") + lines.append(f"๐Ÿ“Š Total Sensors Detected: {len(self.sensors)}") + lines.append("") + + # Group by type + for sensor_type in SensorType: + sensors = self.get_sensors_by_type(sensor_type) + if not sensors: + continue + + icon = { + SensorType.TEMPERATURE: "๐ŸŒก๏ธ", + SensorType.FAN: "๐Ÿ’จ", + SensorType.VOLTAGE: "โšก", + SensorType.POWER: "๐Ÿ”Œ", + SensorType.CURRENT: "โšก", + SensorType.ENERGY: "๐Ÿ”‹", + }.get(sensor_type, "๐Ÿ“Š") + + lines.append(f"{icon} {sensor_type.value.upper()} SENSORS ({len(sensors)})") + lines.append("-" * 70) + + for sensor in sensors: + value_str = f"{sensor.value:.1f} {sensor.unit}" if sensor.value is not None else "N/A" + lines.append(f" โ€ข {sensor.label:30} : {value_str:15} [{sensor.chip}]") + + lines.append("") + + lines.append("=" * 70) + return "\n".join(lines) + + def get_summary(self) -> Dict: + """Get summary statistics""" + return { + 'total_sensors': len(self.sensors), + 'temperature_sensors': len(self.get_temperature_sensors()), + 'fan_sensors': len(self.get_fan_sensors()), + 'voltage_sensors': len(self.get_sensors_by_type(SensorType.VOLTAGE)), + 'power_sensors': len(self.get_sensors_by_type(SensorType.POWER)), + } + + +def main(): + """Test universal sensor detection""" + print("๐Ÿ” Detecting all system sensors...") + print() + + detector = UniversalSensorDetector() + print(detector.generate_report()) + + print("\n๐Ÿ“Š Summary:") + summary = detector.get_summary() + for key, value in summary.items(): + print(f" {key}: {value}") + + +if __name__ == "__main__": + main() diff --git a/src/services/__pycache__/monitoring_service.cpython-311.pyc b/src/services/__pycache__/monitoring_service.cpython-311.pyc new file mode 100644 index 0000000..c374f70 Binary files /dev/null and b/src/services/__pycache__/monitoring_service.cpython-311.pyc differ diff --git a/src/services/monitoring_service.py b/src/services/monitoring_service.py new file mode 100644 index 0000000..5091ee9 --- /dev/null +++ b/src/services/monitoring_service.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python3 +""" +Power Management Monitoring Service +Real-time monitoring of all system sensors, GPU, fans +Professional monitoring daemon with alerts and auto-adjustment +""" + +import os +import sys +import time +import signal +import json +from pathlib import Path +from typing import Dict, List, Optional +from dataclasses import dataclass, asdict +from datetime import datetime +from threading import Thread, Event + +# Add parent directories to path +service_dir = Path(__file__).resolve().parent +sys.path.insert(0, str(service_dir.parent)) + +from sensors.gpu_monitor import UniversalGPUMonitor +from sensors.universal_sensor_detector import UniversalSensorDetector, SensorType +from sensors.fan_controller import UniversalFanController +from hardware.hardware_detector import HardwareDetector +from config.power_config import PowerConfig + + +@dataclass +class MonitoringSnapshot: + """Single monitoring snapshot""" + timestamp: str + cpu_temp: Optional[float] + gpu_temp: Optional[float] + cpu_fan_rpm: Optional[int] + gpu_fan_rpm: Optional[int] + gpu_power: Optional[int] + cpu_power: Optional[float] + voltages: Dict[str, float] + alerts: List[str] + + +class MonitoringService: + """ + Comprehensive monitoring service + Monitors: CPU, GPU, fans, voltages, power + Features: Auto fan control, thermal alerts, data logging + """ + + def __init__(self, interval: int = 5, log_dir: str = "/tmp"): + self.interval = interval + self.log_dir = Path(log_dir) + self.log_file = self.log_dir / "power_monitoring.log" + self.json_log = self.log_dir / "power_monitoring.json" + + # Initialize detectors + self.hw_detector = HardwareDetector() + self.sensor_detector = UniversalSensorDetector() + self.gpu_monitor = UniversalGPUMonitor() + self.fan_controller = UniversalFanController() + + # Configuration + self.config = PowerConfig() + self.config.set_thermal_config(self.hw_detector.cpu_info.thermal_max_safe) + + # Thermal thresholds + self.cpu_temp_warning = self.config.thermal.warning_temp + self.cpu_temp_critical = self.config.thermal.critical_temp + self.cpu_temp_emergency = self.config.thermal.emergency_temp + + # GPU thresholds (typically higher than CPU) + self.gpu_temp_warning = 75 + self.gpu_temp_critical = 85 + self.gpu_temp_emergency = 95 + + # State + self.running = False + self.stop_event = Event() + self.snapshots = [] + self.max_snapshots = 100 # Keep last 100 snapshots in memory + + # Auto fan control + self.auto_fan_control = True + self.fan_speed_map = { + 'low': 30, # < warning temp + 'medium': 50, # warning to critical + 'high': 75, # critical to emergency + 'max': 100 # emergency + } + + def log(self, message: str): + """Log message to file and stdout""" + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + log_msg = f"[{timestamp}] {message}" + print(log_msg) + + try: + with open(self.log_file, 'a') as f: + f.write(log_msg + "\n") + except Exception: + pass + + def collect_snapshot(self) -> MonitoringSnapshot: + """Collect single monitoring snapshot""" + alerts = [] + + # Get CPU temperature + cpu_temp = None + temp_sensors = self.sensor_detector.get_temperature_sensors() + if temp_sensors: + # Find CPU temp (usually coretemp or k10temp) + for sensor in temp_sensors: + if any(x in sensor.chip.lower() for x in ['coretemp', 'k10temp', 'cpu']): + if 'package' in sensor.label.lower() or 'tctl' in sensor.label.lower(): + cpu_temp = sensor.value + break + + # Fallback to first temp sensor + if cpu_temp is None and temp_sensors: + cpu_temp = temp_sensors[0].value + + # Check CPU temp alerts + if cpu_temp: + if cpu_temp >= self.cpu_temp_emergency: + alerts.append(f"๐Ÿšจ CPU EMERGENCY: {cpu_temp}ยฐC (limit: {self.cpu_temp_emergency}ยฐC)") + elif cpu_temp >= self.cpu_temp_critical: + alerts.append(f"โš ๏ธ CPU CRITICAL: {cpu_temp}ยฐC") + elif cpu_temp >= self.cpu_temp_warning: + alerts.append(f"โšก CPU WARNING: {cpu_temp}ยฐC") + + # Get GPU metrics + gpu_temp = None + gpu_power = None + gpu_fan_rpm = None + + gpu_metrics_list = self.gpu_monitor.get_all_metrics() + if gpu_metrics_list: + gpu_metrics = gpu_metrics_list[0] # Primary GPU + gpu_temp = gpu_metrics.temperature + gpu_power = gpu_metrics.power_usage + gpu_fan_rpm = gpu_metrics.fan_rpm + + # Check GPU temp alerts + if gpu_temp: + if gpu_temp >= self.gpu_temp_emergency: + alerts.append(f"๐Ÿšจ GPU EMERGENCY: {gpu_temp}ยฐC (limit: {self.gpu_temp_emergency}ยฐC)") + elif gpu_temp >= self.gpu_temp_critical: + alerts.append(f"โš ๏ธ GPU CRITICAL: {gpu_temp}ยฐC") + elif gpu_temp >= self.gpu_temp_warning: + alerts.append(f"โšก GPU WARNING: {gpu_temp}ยฐC") + + # Get fan speeds + cpu_fan_rpm = None + fan_sensors = self.sensor_detector.get_fan_sensors() + if fan_sensors: + # Find CPU fan + for sensor in fan_sensors: + if 'cpu' in sensor.label.lower() or 'fan1' in sensor.label.lower(): + cpu_fan_rpm = int(sensor.value) if sensor.value else None + break + + # Fallback to first fan + if cpu_fan_rpm is None and fan_sensors: + cpu_fan_rpm = int(fan_sensors[0].value) if fan_sensors[0].value else None + + # Get voltages + voltages = {} + voltage_sensors = self.sensor_detector.get_sensors_by_type(SensorType.VOLTAGE) + for sensor in voltage_sensors[:5]: # Limit to first 5 + voltages[sensor.label] = sensor.value + + # Get CPU power if available + cpu_power = None + power_sensors = self.sensor_detector.get_sensors_by_type(SensorType.POWER) + for sensor in power_sensors: + if 'package' in sensor.label.lower() or 'cpu' in sensor.label.lower(): + cpu_power = sensor.value + break + + return MonitoringSnapshot( + timestamp=datetime.now().isoformat(), + cpu_temp=cpu_temp, + gpu_temp=gpu_temp, + cpu_fan_rpm=cpu_fan_rpm, + gpu_fan_rpm=gpu_fan_rpm, + gpu_power=gpu_power, + cpu_power=cpu_power, + voltages=voltages, + alerts=alerts + ) + + def adjust_fans_based_on_temp(self, snapshot: MonitoringSnapshot): + """Auto-adjust fans based on temperatures""" + if not self.auto_fan_control: + return + + # Determine required fan speed based on max temp + max_temp = 0 + if snapshot.cpu_temp: + max_temp = max(max_temp, snapshot.cpu_temp) + if snapshot.gpu_temp: + max_temp = max(max_temp, snapshot.gpu_temp) + + if max_temp == 0: + return # No temp data + + # Determine fan speed tier + if max_temp >= self.cpu_temp_emergency or (snapshot.gpu_temp and snapshot.gpu_temp >= self.gpu_temp_emergency): + target_speed = self.fan_speed_map['max'] + self.log(f"๐Ÿšจ EMERGENCY: Setting fans to {target_speed}%") + elif max_temp >= self.cpu_temp_critical or (snapshot.gpu_temp and snapshot.gpu_temp >= self.gpu_temp_critical): + target_speed = self.fan_speed_map['high'] + self.log(f"โš ๏ธ CRITICAL: Setting fans to {target_speed}%") + elif max_temp >= self.cpu_temp_warning or (snapshot.gpu_temp and snapshot.gpu_temp >= self.gpu_temp_warning): + target_speed = self.fan_speed_map['medium'] + else: + target_speed = self.fan_speed_map['low'] + + # Set all controllable fans + for i in range(len(self.fan_controller.pwm_fans)): + try: + self.fan_controller.set_pwm_fan_speed(i, target_speed) + except Exception as e: + self.log(f"Fan control error: {e}") + + def save_snapshot_to_json(self, snapshot: MonitoringSnapshot): + """Save snapshot to JSON log""" + try: + # Load existing data + data = [] + if self.json_log.exists(): + with open(self.json_log, 'r') as f: + data = json.load(f) + + # Append new snapshot + data.append(asdict(snapshot)) + + # Keep only last 1000 snapshots + if len(data) > 1000: + data = data[-1000:] + + # Save + with open(self.json_log, 'w') as f: + json.dump(data, f, indent=2) + + except Exception as e: + self.log(f"JSON log error: {e}") + + def monitoring_loop(self): + """Main monitoring loop""" + self.log("๐Ÿš€ Monitoring service started") + self.log(f" CPU: {self.hw_detector.cpu_info.model_name}") + self.log(f" Thermal limits: {self.cpu_temp_warning}ยฐC / {self.cpu_temp_critical}ยฐC / {self.cpu_temp_emergency}ยฐC") + self.log(f" Interval: {self.interval}s") + self.log(f" Auto fan control: {'โœ… Enabled' if self.auto_fan_control else 'โŒ Disabled'}") + + while not self.stop_event.is_set(): + try: + # Collect snapshot + snapshot = self.collect_snapshot() + + # Store in memory + self.snapshots.append(snapshot) + if len(self.snapshots) > self.max_snapshots: + self.snapshots.pop(0) + + # Log to JSON + self.save_snapshot_to_json(snapshot) + + # Display current status + status_parts = [] + if snapshot.cpu_temp: + status_parts.append(f"CPU: {snapshot.cpu_temp:.1f}ยฐC") + if snapshot.gpu_temp: + status_parts.append(f"GPU: {snapshot.gpu_temp:.1f}ยฐC") + if snapshot.cpu_fan_rpm: + status_parts.append(f"Fan: {snapshot.cpu_fan_rpm}RPM") + + status = " | ".join(status_parts) + self.log(f"๐Ÿ“Š {status}") + + # Log alerts + for alert in snapshot.alerts: + self.log(alert) + + # Auto-adjust fans + self.adjust_fans_based_on_temp(snapshot) + + # Sleep + self.stop_event.wait(self.interval) + + except Exception as e: + self.log(f"โŒ Monitoring error: {e}") + self.stop_event.wait(self.interval) + + def start(self): + """Start monitoring service""" + if self.running: + print("Service already running") + return + + self.running = True + self.stop_event.clear() + + # Start monitoring thread + self.monitor_thread = Thread(target=self.monitoring_loop, daemon=True) + self.monitor_thread.start() + + def stop(self): + """Stop monitoring service""" + if not self.running: + return + + self.log("๐Ÿ›‘ Stopping monitoring service...") + self.stop_event.set() + self.monitor_thread.join(timeout=5) + self.running = False + + def get_current_status(self) -> Optional[MonitoringSnapshot]: + """Get most recent snapshot""" + return self.snapshots[-1] if self.snapshots else None + + +def signal_handler(signum, frame): + """Handle shutdown signals""" + print("\n๐Ÿ›‘ Received shutdown signal") + sys.exit(0) + + +def main(): + """Run monitoring service""" + import argparse + + parser = argparse.ArgumentParser(description="Power Management Monitoring Service") + parser.add_argument("--interval", type=int, default=5, help="Monitoring interval in seconds") + parser.add_argument("--no-auto-fan", action="store_true", help="Disable automatic fan control") + parser.add_argument("--log-dir", type=str, default="/tmp", help="Log directory") + args = parser.parse_args() + + # Set up signal handlers + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGINT, signal_handler) + + # Create service + service = MonitoringService( + interval=args.interval, + log_dir=args.log_dir + ) + + if args.no_auto_fan: + service.auto_fan_control = False + + # Start service + service.start() + + # Keep running + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + service.stop() + + +if __name__ == "__main__": + main() diff --git a/tests/test_sensors.sh b/tests/test_sensors.sh new file mode 100755 index 0000000..51d0c19 --- /dev/null +++ b/tests/test_sensors.sh @@ -0,0 +1,508 @@ +#!/bin/bash + +#============================================================================== +# Comprehensive Test Suite for Sensor Monitoring & Fan Control +# Tests all new modules with detailed error reporting +#============================================================================== + +set -euo pipefail + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Counters +PASSED=0 +FAILED=0 +WARNINGS=0 + +# Test results +declare -a FAILED_TESTS +declare -a WARNINGS_LIST + +#============================================================================== +# Helper Functions +#============================================================================== + +print_header() { + echo -e "${BLUE}============================================${NC}" + echo -e "${BLUE} $1${NC}" + echo -e "${BLUE}============================================${NC}" + echo "" +} + +test_start() { + echo -n "Testing: $1... " +} + +test_pass() { + echo -e "${GREEN}โœ… PASS${NC}" + ((PASSED++)) +} + +test_fail() { + echo -e "${RED}โŒ FAIL${NC}" + ((FAILED++)) + FAILED_TESTS+=("$1") +} + +test_warn() { + echo -e "${YELLOW}โš ๏ธ WARNING${NC} - $1" + ((WARNINGS++)) + WARNINGS_LIST+=("$1") +} + +#============================================================================== +# Path Detection +#============================================================================== + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +INSTALL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +SRC_DIR="$INSTALL_DIR/src" + +#============================================================================== +# Python Module Tests +#============================================================================== + +test_python_syntax() { + local file="$1" + local desc="$2" + + test_start "$desc - syntax check" + + if python3 -m py_compile "$file" 2>/dev/null; then + test_pass + return 0 + else + test_fail "$desc - syntax" + return 1 + fi +} + +test_python_import() { + local file="$1" + local module="$2" + local desc="$3" + + test_start "$desc - import test" + + # Set PYTHONPATH + export PYTHONPATH="$SRC_DIR:$PYTHONPATH" + + if python3 -c "$module" 2>/dev/null; then + test_pass + return 0 + else + test_fail "$desc - import" + return 1 + fi +} + +test_python_execution() { + local file="$1" + local args="$2" + local desc="$3" + + test_start "$desc - execution test" + + export PYTHONPATH="$SRC_DIR:$PYTHONPATH" + + # Run with timeout + if timeout 5 python3 "$file" $args >/dev/null 2>&1; then + test_pass + return 0 + else + # Check exit code + exit_code=$? + if [ $exit_code -eq 124 ]; then + test_fail "$desc - timeout" + else + test_warn "$desc - exit code $exit_code (may be expected in CI)" + fi + return 1 + fi +} + +#============================================================================== +# Main Tests +#============================================================================== + +print_header "SENSOR MONITORING & FAN CONTROL TEST SUITE" + +echo -e "${YELLOW}๐Ÿ“ Installation Directory: $INSTALL_DIR${NC}" +echo -e "${YELLOW}๐Ÿ Python: $(python3 --version)${NC}" +echo "" + +#============================================================================== +# Test 1: File Existence +#============================================================================== + +print_header "File Existence Tests" + +test_start "GPU monitor exists" +if [ -f "$SRC_DIR/sensors/gpu_monitor.py" ]; then + test_pass +else + test_fail "GPU monitor exists" +fi + +test_start "Sensor detector exists" +if [ -f "$SRC_DIR/sensors/universal_sensor_detector.py" ]; then + test_pass +else + test_fail "Sensor detector exists" +fi + +test_start "Fan controller exists" +if [ -f "$SRC_DIR/sensors/fan_controller.py" ]; then + test_pass +else + test_fail "Fan controller exists" +fi + +test_start "Monitoring service exists" +if [ -f "$SRC_DIR/services/monitoring_service.py" ]; then + test_pass +else + test_fail "Monitoring service exists" +fi + +echo "" + +#============================================================================== +# Test 2: Python Syntax +#============================================================================== + +print_header "Python Syntax Tests" + +test_python_syntax "$SRC_DIR/sensors/gpu_monitor.py" "GPU monitor" +test_python_syntax "$SRC_DIR/sensors/universal_sensor_detector.py" "Sensor detector" +test_python_syntax "$SRC_DIR/sensors/fan_controller.py" "Fan controller" +test_python_syntax "$SRC_DIR/services/monitoring_service.py" "Monitoring service" + +echo "" + +#============================================================================== +# Test 3: Python Imports +#============================================================================== + +print_header "Python Import Tests" + +test_python_import "$SRC_DIR/sensors/gpu_monitor.py" \ + "from sensors.gpu_monitor import UniversalGPUMonitor" \ + "GPU monitor" + +test_python_import "$SRC_DIR/sensors/universal_sensor_detector.py" \ + "from sensors.universal_sensor_detector import UniversalSensorDetector" \ + "Sensor detector" + +test_python_import "$SRC_DIR/sensors/fan_controller.py" \ + "from sensors.fan_controller import UniversalFanController" \ + "Fan controller" + +test_python_import "$SRC_DIR/services/monitoring_service.py" \ + "from services.monitoring_service import MonitoringService" \ + "Monitoring service" + +echo "" + +#============================================================================== +# Test 4: Module Execution +#============================================================================== + +print_header "Module Execution Tests" + +echo -e "${YELLOW}Note: Some tests may show warnings in CI environment (no hardware)${NC}" +echo "" + +test_start "GPU monitor execution" +export PYTHONPATH="$SRC_DIR:$PYTHONPATH" +if timeout 5 python3 "$SRC_DIR/sensors/gpu_monitor.py" >/dev/null 2>&1; then + test_pass +else + test_warn "GPU monitor - no GPU in CI (expected)" +fi + +test_start "Sensor detector execution" +if timeout 5 python3 "$SRC_DIR/sensors/universal_sensor_detector.py" >/dev/null 2>&1; then + test_pass +else + test_warn "Sensor detector - no sensors in CI (expected)" +fi + +test_start "Fan controller execution" +if timeout 5 python3 "$SRC_DIR/sensors/fan_controller.py" >/dev/null 2>&1; then + test_pass +else + test_warn "Fan controller - no fans in CI (expected)" +fi + +echo "" + +#============================================================================== +# Test 5: Integration Tests +#============================================================================== + +print_header "Integration Tests" + +test_start "Hardware detector + GPU monitor integration" +export PYTHONPATH="$SRC_DIR:$PYTHONPATH" +if python3 -c " +from hardware.hardware_detector import HardwareDetector +from sensors.gpu_monitor import UniversalGPUMonitor +hd = HardwareDetector() +gm = UniversalGPUMonitor() +" 2>/dev/null; then + test_pass +else + test_fail "Hardware detector + GPU monitor integration" +fi + +test_start "Config + Sensor detector integration" +if python3 -c " +from config.power_config import PowerConfig +from sensors.universal_sensor_detector import UniversalSensorDetector +cfg = PowerConfig() +sd = UniversalSensorDetector() +" 2>/dev/null; then + test_pass +else + test_fail "Config + Sensor detector integration" +fi + +test_start "Fan controller + Sensor detector integration" +if python3 -c " +from sensors.fan_controller import UniversalFanController +from sensors.universal_sensor_detector import UniversalSensorDetector +fc = UniversalFanController() +sd = UniversalSensorDetector() +" 2>/dev/null; then + test_pass +else + test_fail "Fan controller + Sensor detector integration" +fi + +echo "" + +#============================================================================== +# Test 6: Dependency Tests +#============================================================================== + +print_header "Dependency Tests" + +test_start "Python version >= 3.6" +python_version=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') +if python3 -c 'import sys; exit(0 if sys.version_info >= (3, 6) else 1)'; then + test_pass +else + test_fail "Python version >= 3.6" +fi + +test_start "psutil module available" +if python3 -c 'import psutil' 2>/dev/null; then + test_pass +else + test_warn "psutil not installed (pip install psutil)" +fi + +test_start "pathlib available" +if python3 -c 'from pathlib import Path' 2>/dev/null; then + test_pass +else + test_fail "pathlib not available" +fi + +test_start "dataclasses available" +if python3 -c 'from dataclasses import dataclass' 2>/dev/null; then + test_pass +else + test_fail "dataclasses not available" +fi + +echo "" + +#============================================================================== +# Test 7: Hardware Detection (Real Hardware) +#============================================================================== + +print_header "Hardware Detection Tests (may fail in CI)" + +test_start "CPU detection" +export PYTHONPATH="$SRC_DIR:$PYTHONPATH" +if python3 -c " +from hardware.hardware_detector import HardwareDetector +hd = HardwareDetector() +assert hd.cpu_info.cores > 0 +" 2>/dev/null; then + test_pass +else + test_warn "CPU detection (expected in CI)" +fi + +test_start "Sensor detection count" +if python3 -c " +from sensors.universal_sensor_detector import UniversalSensorDetector +sd = UniversalSensorDetector() +# In CI, may have 0 sensors +assert sd.sensors is not None +" 2>/dev/null; then + test_pass +else + test_fail "Sensor detection" +fi + +echo "" + +#============================================================================== +# Test 8: Error Handling +#============================================================================== + +print_header "Error Handling Tests" + +test_start "GPU monitor handles no GPU" +if python3 -c " +from sensors.gpu_monitor import UniversalGPUMonitor +gm = UniversalGPUMonitor() +# Should not crash even with no GPU +metrics = gm.get_metrics(0) +" 2>/dev/null; then + test_pass +else + test_fail "GPU monitor error handling" +fi + +test_start "Fan controller handles no fans" +if python3 -c " +from sensors.fan_controller import UniversalFanController +fc = UniversalFanController() +# Should not crash even with no fans +info = fc.get_fan_info(999) # Non-existent fan +assert info is None +" 2>/dev/null; then + test_pass +else + test_fail "Fan controller error handling" +fi + +test_start "Sensor detector handles empty system" +if python3 -c " +from sensors.universal_sensor_detector import UniversalSensorDetector +sd = UniversalSensorDetector() +# Should work even with no sensors +temps = sd.get_temperature_sensors() +assert isinstance(temps, list) +" 2>/dev/null; then + test_pass +else + test_fail "Sensor detector error handling" +fi + +echo "" + +#============================================================================== +# Test 9: Documentation Tests +#============================================================================== + +print_header "Documentation Tests" + +test_start "SENSOR_MONITORING.md exists" +if [ -f "$INSTALL_DIR/docs/SENSOR_MONITORING.md" ]; then + test_pass +else + test_fail "SENSOR_MONITORING.md exists" +fi + +test_start "UNIVERSAL_HARDWARE.md exists" +if [ -f "$INSTALL_DIR/docs/UNIVERSAL_HARDWARE.md" ]; then + test_pass +else + test_fail "UNIVERSAL_HARDWARE.md exists" +fi + +test_start "README.md mentions v3.1" +if grep -q "3.1" "$INSTALL_DIR/README.md" 2>/dev/null; then + test_pass +else + test_fail "README.md mentions v3.1" +fi + +echo "" + +#============================================================================== +# Test 10: Compatibility Tests +#============================================================================== + +print_header "Compatibility Tests" + +test_start "Works without nvidia-smi" +if python3 -c " +from sensors.gpu_monitor import UniversalGPUMonitor +gm = UniversalGPUMonitor() +# Should work without nvidia-smi +" 2>/dev/null; then + test_pass +else + test_fail "Works without nvidia-smi" +fi + +test_start "Works without lm-sensors" +if python3 -c " +from sensors.universal_sensor_detector import UniversalSensorDetector +sd = UniversalSensorDetector() +# Should work without sensors command +" 2>/dev/null; then + test_pass +else + test_fail "Works without lm-sensors" +fi + +test_start "Works without root access" +if python3 -c " +from sensors.fan_controller import UniversalFanController +fc = UniversalFanController() +# Should detect but not control without root +" 2>/dev/null; then + test_pass +else + test_fail "Works without root access" +fi + +echo "" + +#============================================================================== +# Results Summary +#============================================================================== + +print_header "TEST RESULTS SUMMARY" + +echo -e "โœ… Passed: ${GREEN}$PASSED${NC}" +echo -e "โŒ Failed: ${RED}$FAILED${NC}" +echo -e "โš ๏ธ Warnings: ${YELLOW}$WARNINGS${NC}" +echo "" + +if [ $FAILED -eq 0 ]; then + if [ $WARNINGS -eq 0 ]; then + echo -e "${GREEN}๐ŸŽ‰ ALL TESTS PASSED!${NC}" + echo -e "${GREEN}โœ… System is ready for production use!${NC}" + exit 0 + else + echo -e "${YELLOW}โœ… Tests passed with warnings (expected in CI)${NC}" + echo -e "${YELLOW}Warnings:${NC}" + for warn in "${WARNINGS_LIST[@]}"; do + echo -e " ${YELLOW}โš ๏ธ $warn${NC}" + done + exit 0 + fi +else + echo -e "${RED}โŒ SOME TESTS FAILED${NC}" + echo -e "${RED}Failed tests:${NC}" + for test in "${FAILED_TESTS[@]}"; do + echo -e " ${RED}โŒ $test${NC}" + done + echo "" + echo -e "${YELLOW}Please fix the issues before deploying${NC}" + exit 1 +fi diff --git a/tests/test_universal_system.sh b/tests/test_universal_system.sh new file mode 100755 index 0000000..2c71d38 --- /dev/null +++ b/tests/test_universal_system.sh @@ -0,0 +1,314 @@ +#!/bin/bash + +#============================================================================== +# Universal System Test Suite +# Tests hardware detection, configuration, and universal managers +#============================================================================== + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Counters +PASSED=0 +FAILED=0 +SKIPPED=0 + +# Test results +declare -a FAILED_TESTS + +#============================================================================== +# Helper Functions +#============================================================================== + +print_header() { + echo -e "${BLUE}============================================${NC}" + echo -e "${BLUE} Universal System Test Suite${NC}" + echo -e "${BLUE}============================================${NC}" + echo "" +} + +test_start() { + echo -n "Testing: $1... " +} + +test_pass() { + echo -e "${GREEN}โœ… PASS${NC}" + ((PASSED++)) +} + +test_fail() { + echo -e "${RED}โŒ FAIL${NC}" + ((FAILED++)) + FAILED_TESTS+=("$1") +} + +test_skip() { + echo -e "${YELLOW}โญ๏ธ SKIP${NC} - $1" + ((SKIPPED++)) +} + +#============================================================================== +# Path Detection +#============================================================================== + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +INSTALL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +SRC_DIR="$INSTALL_DIR/src" + +#============================================================================== +# Test Cases +#============================================================================== + +test_file_exists() { + local file="$1" + local desc="$2" + + test_start "$desc" + + if [ -f "$file" ]; then + test_pass + return 0 + else + test_fail "$desc" + return 1 + fi +} + +test_python_import() { + local module="$1" + local desc="$2" + + test_start "$desc" + + if python3 -c "import sys; sys.path.insert(0, '$SRC_DIR'); $module" 2>/dev/null; then + test_pass + return 0 + else + test_fail "$desc" + return 1 + fi +} + +test_python_script() { + local script="$1" + local args="$2" + local desc="$3" + + test_start "$desc" + + if python3 "$script" $args >/dev/null 2>&1; then + test_pass + return 0 + else + test_fail "$desc" + return 1 + fi +} + +#============================================================================== +# Main Tests +#============================================================================== + +print_header + +echo -e "${YELLOW}๐Ÿ“ Testing File Structure...${NC}" +echo "" + +# Test core files +test_file_exists "$SRC_DIR/hardware/hardware_detector.py" "Hardware detector exists" +test_file_exists "$SRC_DIR/config/power_config.py" "Power config exists" +test_file_exists "$SRC_DIR/frequency/universal_cpu_manager.py" "Universal CPU manager exists" +test_file_exists "$SRC_DIR/frequency/cpu_frequency_manager.py" "Legacy CPU manager exists" +test_file_exists "$INSTALL_DIR/scripts/performance_manager.sh" "Performance manager exists" +test_file_exists "$INSTALL_DIR/scripts/ai_process_manager.sh" "AI process manager exists" +test_file_exists "$INSTALL_DIR/scripts/smart_thermal_manager.py" "Smart thermal manager exists" +test_file_exists "$INSTALL_DIR/daemons/custom-power-profiles-daemon.py" "Custom daemon exists" + +echo "" +echo -e "${YELLOW}๐Ÿ Testing Python Modules...${NC}" +echo "" + +# Test Python imports +test_python_import "from hardware.hardware_detector import HardwareDetector" "Hardware detector import" +test_python_import "from config.power_config import PowerConfig" "Power config import" + +echo "" +echo -e "${YELLOW}๐Ÿ” Testing Hardware Detection...${NC}" +echo "" + +# Test hardware detection +test_start "Hardware detector execution" +if python3 "$SRC_DIR/hardware/hardware_detector.py" >/dev/null 2>&1; then + test_pass +else + test_fail "Hardware detector execution" +fi + +# Run hardware detection and show results +echo "" +echo -e "${BLUE}Hardware Detection Results:${NC}" +python3 "$SRC_DIR/hardware/hardware_detector.py" 2>/dev/null | head -20 || echo " (Detection failed - may need root access)" +echo "" + +echo -e "${YELLOW}โš™๏ธ Testing Configuration System...${NC}" +echo "" + +test_start "Configuration system execution" +if python3 "$SRC_DIR/config/power_config.py" >/dev/null 2>&1; then + test_pass +else + test_fail "Configuration system execution" +fi + +echo "" +echo -e "${YELLOW}โšก Testing Universal CPU Manager...${NC}" +echo "" + +test_start "Universal CPU manager status" +if python3 "$SRC_DIR/frequency/universal_cpu_manager.py" status >/dev/null 2>&1; then + test_pass +else + test_fail "Universal CPU manager status" +fi + +test_start "Universal CPU manager detect" +if python3 "$SRC_DIR/frequency/universal_cpu_manager.py" detect >/dev/null 2>&1; then + test_pass +else + test_fail "Universal CPU manager detect" +fi + +echo "" +echo -e "${BLUE}Universal CPU Manager Status:${NC}" +python3 "$SRC_DIR/frequency/universal_cpu_manager.py" status 2>/dev/null || echo " (May need root for full functionality)" +echo "" + +echo -e "${YELLOW}๐ŸŒก๏ธ Testing Thermal Manager...${NC}" +echo "" + +test_start "Smart thermal manager initialization" +if python3 "$INSTALL_DIR/scripts/smart_thermal_manager.py" >/dev/null 2>&1; then + test_pass +else + # Thermal manager might not exit cleanly in test mode + test_skip "requires interactive mode" +fi + +echo "" +echo -e "${YELLOW}๐Ÿ”ง Testing Shell Scripts...${NC}" +echo "" + +test_start "Performance manager help" +if bash "$INSTALL_DIR/scripts/performance_manager.sh" >/dev/null 2>&1; then + test_pass +else + test_fail "Performance manager help" +fi + +test_start "AI process manager help" +if bash "$INSTALL_DIR/scripts/ai_process_manager.sh" >/dev/null 2>&1; then + test_pass +else + # AI manager might exit with error when no args provided + test_skip "requires arguments" +fi + +echo "" +echo -e "${YELLOW}๐Ÿ” Testing Dynamic Path Resolution...${NC}" +echo "" + +test_start "Performance manager finds CPU manager" +if grep -q "universal_cpu_manager.py" "$INSTALL_DIR/scripts/performance_manager.sh"; then + test_pass +else + test_fail "Performance manager finds CPU manager" +fi + +test_start "AI manager finds performance manager" +if grep -q "POWER_MANAGER_PATH=\"\$SCRIPT_DIR/performance_manager.sh\"" "$INSTALL_DIR/scripts/ai_process_manager.sh"; then + test_pass +else + test_fail "AI manager finds performance manager" +fi + +test_start "Daemon uses PowerConfig" +if grep -q "from config.power_config import PowerConfig" "$INSTALL_DIR/daemons/custom-power-profiles-daemon.py"; then + test_pass +else + test_fail "Daemon uses PowerConfig" +fi + +echo "" +echo -e "${YELLOW}๐Ÿšซ Testing Hardcoded Path Removal...${NC}" +echo "" + +test_start "No /home/milhy777 in daemon" +if ! grep -q "/home/milhy777" "$INSTALL_DIR/daemons/custom-power-profiles-daemon.py"; then + test_pass +else + test_fail "No /home/milhy777 in daemon" +fi + +test_start "No /home/milhy777 in performance_manager" +if ! grep -q "/home/milhy777" "$INSTALL_DIR/scripts/performance_manager.sh"; then + test_pass +else + test_fail "No /home/milhy777 in performance_manager" +fi + +test_start "No /home/milhy777 in ai_process_manager" +if ! grep -q "/home/milhy777" "$INSTALL_DIR/scripts/ai_process_manager.sh"; then + test_pass +else + test_fail "No /home/milhy777 in ai_process_manager" +fi + +test_start "No hardcoded 'claude --agent' calls" +if ! grep -q "claude --agent" "$INSTALL_DIR/scripts/performance_manager.sh"; then + test_pass +else + test_fail "No hardcoded 'claude --agent' calls" +fi + +echo "" +echo -e "${YELLOW}๐ŸŽฎ Testing GPU Detection...${NC}" +echo "" + +test_start "GPU auto-detection in performance_manager" +if grep -q "for card in /sys/class/drm/card\[0-9\]" "$INSTALL_DIR/scripts/performance_manager.sh"; then + test_pass +else + test_fail "GPU auto-detection in performance_manager" +fi + +#============================================================================== +# Results Summary +#============================================================================== + +echo "" +echo -e "${BLUE}============================================${NC}" +echo -e "${BLUE} Test Results Summary${NC}" +echo -e "${BLUE}============================================${NC}" +echo "" +echo -e "โœ… Passed: ${GREEN}$PASSED${NC}" +echo -e "โŒ Failed: ${RED}$FAILED${NC}" +echo -e "โญ๏ธ Skipped: ${YELLOW}$SKIPPED${NC}" +echo "" + +if [ $FAILED -eq 0 ]; then + echo -e "${GREEN}๐ŸŽ‰ All tests passed!${NC}" + echo -e "${GREEN}โœ… Universal system is ready!${NC}" + exit 0 +else + echo -e "${RED}โŒ Some tests failed:${NC}" + for test in "${FAILED_TESTS[@]}"; do + echo -e " - $test" + done + exit 1 +fi