Download gpu_monitor_desktop.py from meccatronis/gpu_monitoring_system: direct link, hf CLI and curl.
- Browser
- Download file 27.1 kB
-
https://huggingface.co/meccatronis/gpu_monitoring_system/resolve/main/gpu_monitor_desktop.py
- Command line
-
hf download hf://meccatronis/gpu_monitoring_system/gpu_monitor_desktop.py
-
curl -L -o gpu_monitor_desktop.py https://huggingface.co/meccatronis/gpu_monitoring_system/resolve/main/gpu_monitor_desktop.py
27.1 kB
| #!/usr/bin/env python3 | |
| """ | |
| Enhanced GPU Desktop Monitor | |
| Provides multiple display modes for GPU monitoring: | |
| - Overlay: Floating transparent window | |
| - System Tray: Minimal tray icon with tooltips | |
| - Dashboard: Full-featured desktop application | |
| """ | |
| import sys | |
| import os | |
| import time | |
| import json | |
| import logging | |
| import threading | |
| from typing import Dict, List, Optional, Tuple | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from PyQt5.QtWidgets import ( | |
| QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, | |
| QSystemTrayIcon, QMenu, QAction, QMainWindow, QTabWidget, QGridLayout, | |
| QGroupBox, QProgressBar, QMessageBox, QComboBox, QCheckBox, QSpinBox, | |
| QDoubleSpinBox, QFrame, QScrollArea | |
| ) | |
| from PyQt5.QtCore import QTimer, Qt, QPoint, QThread, pyqtSignal, QObject | |
| from PyQt5.QtGui import QIcon, QFont, QColor, QPainter, QPen, QPixmap | |
| import matplotlib.pyplot as plt | |
| from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas | |
| import numpy as np | |
| from gpu_monitoring import GPUManager, GPUStatus | |
| from gpu_fan_controller import FanController, FanMode, ProfileType | |
| logger = logging.getLogger(__name__) | |
| class GPUDataThread(QThread): | |
| """Background thread for collecting GPU data.""" | |
| data_updated = pyqtSignal(dict) | |
| error_occurred = pyqtSignal(str) | |
| def __init__(self, gpu_manager: GPUManager, update_interval: float = 1.0): | |
| super().__init__() | |
| self.gpu_manager = gpu_manager | |
| self.update_interval = update_interval | |
| self.running = False | |
| def run(self): | |
| """Main thread loop.""" | |
| self.running = True | |
| while self.running: | |
| try: | |
| status = self.gpu_manager.get_status() | |
| self.data_updated.emit(status) | |
| time.sleep(self.update_interval) | |
| except Exception as e: | |
| self.error_occurred.emit(str(e)) | |
| time.sleep(1) # Wait before retrying | |
| def stop(self): | |
| """Stop the thread.""" | |
| self.running = False | |
| self.wait() | |
| class FanControlThread(QThread): | |
| """Background thread for fan control.""" | |
| status_updated = pyqtSignal(object) | |
| def __init__(self, fan_controller: FanController, update_interval: float = 2.0): | |
| super().__init__() | |
| self.fan_controller = fan_controller | |
| self.update_interval = update_interval | |
| self.running = False | |
| def run(self): | |
| """Main thread loop.""" | |
| self.running = True | |
| while self.running: | |
| try: | |
| status = self.fan_controller.get_status() | |
| if status: | |
| self.status_updated.emit(status) | |
| time.sleep(self.update_interval) | |
| except Exception as e: | |
| logger.error(f"Fan control thread error: {e}") | |
| time.sleep(1) | |
| def stop(self): | |
| """Stop the thread.""" | |
| self.running = False | |
| self.wait() | |
| class GPUOverlayWindow(QWidget): | |
| """Floating overlay window for GPU monitoring.""" | |
| def __init__(self, config: Dict): | |
| super().__init__() | |
| self.config = config | |
| self.gpu_manager = GPUManager() | |
| self.fan_controller = FanController() | |
| self.init_ui() | |
| self.init_data_collection() | |
| # Window properties | |
| self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.Tool) | |
| self.setAttribute(Qt.WA_TranslucentBackground) | |
| self.setFixedSize(200, 160) | |
| # Drag functionality | |
| self.old_pos = None | |
| # Start monitoring | |
| self.gpu_manager.initialize() | |
| self.fan_controller.initialize() | |
| def init_ui(self): | |
| """Initialize the overlay UI.""" | |
| # Main layout | |
| layout = QVBoxLayout() | |
| layout.setContentsMargins(10, 10, 10, 10) | |
| layout.setSpacing(5) | |
| # Title | |
| self.lbl_title = QLabel("GPU Monitor") | |
| self.lbl_title.setStyleSheet(""" | |
| QLabel { | |
| color: #3498db; | |
| font-size: 14px; | |
| font-weight: bold; | |
| border-bottom: 1px solid #3498db; | |
| padding-bottom: 5px; | |
| } | |
| """) | |
| layout.addWidget(self.lbl_title) | |
| # GPU Info | |
| self.lbl_gpu = QLabel("GPU: --") | |
| self.lbl_gpu.setStyleSheet("color: #ecf0f1; font-size: 12px;") | |
| layout.addWidget(self.lbl_gpu) | |
| # Temperature | |
| temp_layout = QHBoxLayout() | |
| self.lbl_temp_label = QLabel("Temp:") | |
| self.lbl_temp_label.setStyleSheet("color: #ecf0f1; font-size: 12px;") | |
| self.lbl_temp_value = QLabel("--°C") | |
| self.lbl_temp_value.setStyleSheet("color: #e74c3c; font-size: 12px; font-weight: bold;") | |
| temp_layout.addWidget(self.lbl_temp_label) | |
| temp_layout.addStretch() | |
| temp_layout.addWidget(self.lbl_temp_value) | |
| layout.addLayout(temp_layout) | |
| # Load | |
| load_layout = QHBoxLayout() | |
| self.lbl_load_label = QLabel("Load:") | |
| self.lbl_load_label.setStyleSheet("color: #ecf0f1; font-size: 12px;") | |
| self.lbl_load_value = QLabel("--%") | |
| self.lbl_load_value.setStyleSheet("color: #f1c40f; font-size: 12px; font-weight: bold;") | |
| load_layout.addWidget(self.lbl_load_label) | |
| load_layout.addStretch() | |
| load_layout.addWidget(self.lbl_load_value) | |
| layout.addLayout(load_layout) | |
| # Fan | |
| fan_layout = QHBoxLayout() | |
| self.lbl_fan_label = QLabel("Fan:") | |
| self.lbl_fan_label.setStyleSheet("color: #ecf0f1; font-size: 12px;") | |
| self.lbl_fan_value = QLabel("-- RPM") | |
| self.lbl_fan_value.setStyleSheet("color: #2ecc71; font-size: 12px; font-weight: bold;") | |
| fan_layout.addWidget(self.lbl_fan_label) | |
| fan_layout.addStretch() | |
| fan_layout.addWidget(self.lbl_fan_value) | |
| layout.addLayout(fan_layout) | |
| # Power | |
| power_layout = QHBoxLayout() | |
| self.lbl_power_label = QLabel("Power:") | |
| self.lbl_power_label.setStyleSheet("color: #ecf0f1; font-size: 12px;") | |
| self.lbl_power_value = QLabel("-- W") | |
| self.lbl_power_value.setStyleSheet("color: #9b59b6; font-size: 12px; font-weight: bold;") | |
| power_layout.addWidget(self.lbl_power_label) | |
| power_layout.addStretch() | |
| power_layout.addWidget(self.lbl_power_value) | |
| layout.addLayout(power_layout) | |
| # Memory | |
| mem_layout = QHBoxLayout() | |
| self.lbl_mem_label = QLabel("VRAM:") | |
| self.lbl_mem_label.setStyleSheet("color: #ecf0f1; font-size: 12px;") | |
| self.lbl_mem_value = QLabel("-- MB") | |
| self.lbl_mem_value.setStyleSheet("color: #1abc9c; font-size: 12px; font-weight: bold;") | |
| mem_layout.addWidget(self.lbl_mem_label) | |
| mem_layout.addStretch() | |
| mem_layout.addWidget(self.lbl_mem_value) | |
| layout.addLayout(mem_layout) | |
| # Close button | |
| self.btn_close = QPushButton("×") | |
| self.btn_close.setFixedSize(20, 20) | |
| self.btn_close.setStyleSheet(""" | |
| QPushButton { | |
| background-color: transparent; | |
| color: #e74c3c; | |
| font-size: 18px; | |
| font-weight: bold; | |
| border: none; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| QPushButton:hover { | |
| color: #ff0000; | |
| } | |
| """) | |
| self.btn_close.clicked.connect(self.close) | |
| # Add close button to layout | |
| close_layout = QHBoxLayout() | |
| close_layout.addStretch() | |
| close_layout.addWidget(self.btn_close) | |
| layout.addLayout(close_layout) | |
| self.setLayout(layout) | |
| # Apply styling | |
| self.setStyleSheet(""" | |
| QWidget { | |
| background-color: rgba(0, 0, 0, 200); | |
| border-radius: 8px; | |
| border: 1px solid #3498db; | |
| } | |
| """) | |
| def init_data_collection(self): | |
| """Initialize data collection threads.""" | |
| # GPU data thread | |
| self.gpu_thread = GPUDataThread(self.gpu_manager, self.config['update_interval']) | |
| self.gpu_thread.data_updated.connect(self.update_gpu_data) | |
| self.gpu_thread.error_occurred.connect(self.handle_error) | |
| self.gpu_thread.start() | |
| # Fan control thread | |
| self.fan_thread = FanControlThread(self.fan_controller, 2.0) | |
| self.fan_thread.status_updated.connect(self.update_fan_data) | |
| self.fan_thread.start() | |
| def update_gpu_data(self, status_dict: Dict[str, Optional[GPUStatus]]): | |
| """Update GPU data display.""" | |
| for gpu_name, gpu_status in status_dict.items(): | |
| if gpu_status: | |
| # Update GPU name | |
| self.lbl_gpu.setText(f"GPU: {gpu_name}") | |
| # Update temperature | |
| temp_color = self.get_temp_color(gpu_status.temperature) | |
| self.lbl_temp_value.setText(f"{gpu_status.temperature:.1f}°C") | |
| self.lbl_temp_value.setStyleSheet(f"color: {temp_color}; font-size: 12px; font-weight: bold;") | |
| # Update load | |
| load_color = self.get_load_color(gpu_status.load) | |
| self.lbl_load_value.setText(f"{gpu_status.load:.1f}%") | |
| self.lbl_load_value.setStyleSheet(f"color: {load_color}; font-size: 12px; font-weight: bold;") | |
| # Update fan | |
| fan_rpm = self.calculate_fan_rpm(gpu_status.fan_pwm) | |
| self.lbl_fan_value.setText(f"{fan_rpm} RPM ({gpu_status.fan_pwm}%)") | |
| # Update power | |
| self.lbl_power_value.setText(f"{gpu_status.power_draw:.1f} W") | |
| # Update memory | |
| self.lbl_mem_value.setText(f"{gpu_status.memory_used}/{gpu_status.memory_total} MB") | |
| break # Only show first GPU | |
| def update_fan_data(self, fan_status): | |
| """Update fan control data.""" | |
| # Fan status updates can be handled here if needed | |
| pass | |
| def handle_error(self, error_msg: str): | |
| """Handle data collection errors.""" | |
| logger.error(f"Data collection error: {error_msg}") | |
| # Could show error notification here | |
| def get_temp_color(self, temperature: float) -> str: | |
| """Get color based on temperature.""" | |
| if temperature < 60: | |
| return "#2ecc71" # Green | |
| elif temperature < 75: | |
| return "#f1c40f" # Yellow | |
| else: | |
| return "#e74c3c" # Red | |
| def get_load_color(self, load: float) -> str: | |
| """Get color based on load.""" | |
| if load < 50: | |
| return "#2ecc71" # Green | |
| elif load < 80: | |
| return "#f1c40f" # Yellow | |
| else: | |
| return "#e74c3c" # Red | |
| def calculate_fan_rpm(self, pwm: int) -> int: | |
| """Calculate approximate RPM from PWM.""" | |
| return int((pwm / 255) * 4000) | |
| def mousePressEvent(self, event): | |
| """Handle mouse press for dragging.""" | |
| if event.button() == Qt.LeftButton: | |
| self.old_pos = event.globalPos() | |
| def mouseMoveEvent(self, event): | |
| """Handle mouse move for dragging.""" | |
| if self.old_pos is not None: | |
| delta = QPoint(event.globalPos() - self.old_pos) | |
| self.move(self.x() + delta.x(), self.y() + delta.y()) | |
| self.old_pos = event.globalPos() | |
| def mouseReleaseEvent(self, event): | |
| """Handle mouse release.""" | |
| self.old_pos = None | |
| def closeEvent(self, event): | |
| """Clean up threads on close.""" | |
| self.gpu_thread.stop() | |
| self.fan_thread.stop() | |
| super().closeEvent(event) | |
| class SystemTrayMonitor(QSystemTrayIcon): | |
| """System tray icon for GPU monitoring.""" | |
| def __init__(self, config: Dict): | |
| super().__init__() | |
| self.config = config | |
| self.gpu_manager = GPUManager() | |
| self.fan_controller = FanController() | |
| self.init_ui() | |
| self.init_data_collection() | |
| # Start monitoring | |
| self.gpu_manager.initialize() | |
| self.fan_controller.initialize() | |
| def init_ui(self): | |
| """Initialize system tray UI.""" | |
| # Create icon | |
| self.setIcon(QIcon.fromTheme("video-display")) | |
| # Create menu | |
| menu = QMenu() | |
| # Status actions | |
| self.action_status = QAction("Status: --", self) | |
| self.action_status.setEnabled(False) | |
| menu.addAction(self.action_status) | |
| menu.addSeparator() | |
| # Profile actions | |
| self.profile_group = QAction("Profiles", self) | |
| menu.addAction(self.profile_group) | |
| self.action_silent = QAction("Silent", self) | |
| self.action_silent.triggered.connect(lambda: self.set_profile("silent")) | |
| menu.addAction(self.action_silent) | |
| self.action_balanced = QAction("Balanced", self) | |
| self.action_balanced.triggered.connect(lambda: self.set_profile("balanced")) | |
| menu.addAction(self.action_balanced) | |
| self.action_performance = QAction("Performance", self) | |
| self.action_performance.triggered.connect(lambda: self.set_profile("performance")) | |
| menu.addAction(self.action_performance) | |
| menu.addSeparator() | |
| # Open dashboard | |
| self.action_dashboard = QAction("Open Dashboard", self) | |
| self.action_dashboard.triggered.connect(self.open_dashboard) | |
| menu.addAction(self.action_dashboard) | |
| menu.addSeparator() | |
| # Exit action | |
| self.action_exit = QAction("Exit", self) | |
| self.action_exit.triggered.connect(QApplication.instance().quit) | |
| menu.addAction(self.action_exit) | |
| self.setContextMenu(menu) | |
| self.activated.connect(self.on_tray_activated) | |
| self.show() | |
| def init_data_collection(self): | |
| """Initialize data collection threads.""" | |
| # GPU data thread | |
| self.gpu_thread = GPUDataThread(self.gpu_manager, self.config['update_interval']) | |
| self.gpu_thread.data_updated.connect(self.update_tray_tooltip) | |
| self.gpu_thread.start() | |
| def update_tray_tooltip(self, status_dict: Dict[str, Optional[GPUStatus]]): | |
| """Update system tray tooltip.""" | |
| tooltip = "GPU Monitor\n" | |
| for gpu_name, gpu_status in status_dict.items(): | |
| if gpu_status: | |
| tooltip += f"\n{gpu_name}:" | |
| tooltip += f"\n Temp: {gpu_status.temperature:.1f}°C" | |
| tooltip += f"\n Load: {gpu_status.load:.1f}%" | |
| tooltip += f"\n Fan: {self.calculate_fan_rpm(gpu_status.fan_pwm)} RPM" | |
| tooltip += f"\n Power: {gpu_status.power_draw:.1f} W" | |
| tooltip += f"\n VRAM: {gpu_status.memory_used}/{gpu_status.memory_total} MB" | |
| break | |
| self.setToolTip(tooltip) | |
| # Update menu status | |
| if gpu_status: | |
| self.action_status.setText(f"Status: {gpu_status.temperature:.1f}°C, {gpu_status.load:.1f}%") | |
| def on_tray_activated(self, reason): | |
| """Handle tray icon activation.""" | |
| if reason == QSystemTrayIcon.Trigger: | |
| # Show/hide dashboard | |
| pass | |
| def set_profile(self, profile_name: str): | |
| """Set fan profile.""" | |
| if self.fan_controller.set_profile(profile_name): | |
| QMessageBox.information(None, "Profile Changed", f"Switched to {profile_name} profile") | |
| def open_dashboard(self): | |
| """Open the main dashboard window.""" | |
| # This would need to be implemented to show the main window | |
| pass | |
| def calculate_fan_rpm(self, pwm: int) -> int: | |
| """Calculate approximate RPM from PWM.""" | |
| return int((pwm / 255) * 4000) | |
| def closeEvent(self, event): | |
| """Clean up threads on close.""" | |
| self.gpu_thread.stop() | |
| super().closeEvent(event) | |
| class DashboardWindow(QMainWindow): | |
| """Full-featured dashboard window.""" | |
| def __init__(self, config: Dict): | |
| super().__init__() | |
| self.config = config | |
| self.gpu_manager = GPUManager() | |
| self.fan_controller = FanController() | |
| self.init_ui() | |
| self.init_data_collection() | |
| # Start monitoring | |
| self.gpu_manager.initialize() | |
| self.fan_controller.initialize() | |
| self.setWindowTitle("GPU Monitoring Dashboard") | |
| self.setGeometry(100, 100, 800, 600) | |
| def init_ui(self): | |
| """Initialize dashboard UI.""" | |
| # Main widget and layout | |
| main_widget = QWidget() | |
| main_layout = QVBoxLayout() | |
| # Tab widget | |
| self.tabs = QTabWidget() | |
| # Overview tab | |
| self.overview_tab = self.create_overview_tab() | |
| self.tabs.addTab(self.overview_tab, "Overview") | |
| # Charts tab | |
| self.charts_tab = self.create_charts_tab() | |
| self.tabs.addTab(self.charts_tab, "Charts") | |
| # Fan Control tab | |
| self.fan_tab = self.create_fan_tab() | |
| self.tabs.addTab(self.fan_tab, "Fan Control") | |
| # Settings tab | |
| self.settings_tab = self.create_settings_tab() | |
| self.tabs.addTab(self.settings_tab, "Settings") | |
| main_layout.addWidget(self.tabs) | |
| main_widget.setLayout(main_layout) | |
| self.setCentralWidget(main_widget) | |
| def create_overview_tab(self): | |
| """Create the overview tab.""" | |
| widget = QWidget() | |
| layout = QGridLayout() | |
| # GPU Information Group | |
| gpu_group = QGroupBox("GPU Information") | |
| gpu_layout = QGridLayout() | |
| self.lbl_gpu_name = QLabel("--") | |
| self.lbl_gpu_name.setStyleSheet("font-weight: bold; font-size: 16px;") | |
| gpu_layout.addWidget(QLabel("GPU:"), 0, 0) | |
| gpu_layout.addWidget(self.lbl_gpu_name, 0, 1) | |
| self.lbl_temp = QLabel("-- °C") | |
| self.lbl_temp.setStyleSheet("font-size: 14px; color: #e74c3c;") | |
| gpu_layout.addWidget(QLabel("Temperature:"), 1, 0) | |
| gpu_layout.addWidget(self.lbl_temp, 1, 1) | |
| self.lbl_load = QLabel("-- %") | |
| self.lbl_load.setStyleSheet("font-size: 14px; color: #f1c40f;") | |
| gpu_layout.addWidget(QLabel("Load:"), 2, 0) | |
| gpu_layout.addWidget(self.lbl_load, 2, 1) | |
| self.lbl_power = QLabel("-- W") | |
| self.lbl_power.setStyleSheet("font-size: 14px; color: #9b59b6;") | |
| gpu_layout.addWidget(QLabel("Power:"), 3, 0) | |
| gpu_layout.addWidget(self.lbl_power, 3, 1) | |
| self.lbl_vram = QLabel("-- MB") | |
| self.lbl_vram.setStyleSheet("font-size: 14px; color: #1abc9c;") | |
| gpu_layout.addWidget(QLabel("VRAM:"), 4, 0) | |
| gpu_layout.addWidget(self.lbl_vram, 4, 1) | |
| gpu_group.setLayout(gpu_layout) | |
| layout.addWidget(gpu_group, 0, 0) | |
| # Fan Information Group | |
| fan_group = QGroupBox("Fan Control") | |
| fan_layout = QGridLayout() | |
| self.lbl_fan_mode = QLabel("--") | |
| fan_layout.addWidget(QLabel("Mode:"), 0, 0) | |
| fan_layout.addWidget(self.lbl_fan_mode, 0, 1) | |
| self.lbl_fan_profile = QLabel("--") | |
| fan_layout.addWidget(QLabel("Profile:"), 1, 0) | |
| fan_layout.addWidget(self.lbl_fan_profile, 1, 1) | |
| self.lbl_fan_speed = QLabel("-- RPM") | |
| fan_layout.addWidget(QLabel("Speed:"), 2, 0) | |
| fan_layout.addWidget(self.lbl_fan_speed, 2, 1) | |
| self.lbl_fan_pwm = QLabel("-- %") | |
| fan_layout.addWidget(QLabel("PWM:"), 3, 0) | |
| fan_layout.addWidget(self.lbl_fan_pwm, 3, 1) | |
| fan_group.setLayout(fan_layout) | |
| layout.addWidget(fan_group, 0, 1) | |
| widget.setLayout(layout) | |
| return widget | |
| def create_charts_tab(self): | |
| """Create the charts tab.""" | |
| widget = QWidget() | |
| layout = QVBoxLayout() | |
| # Create matplotlib figure | |
| self.figure, self.axes = plt.subplots(2, 2, figsize=(10, 8)) | |
| self.canvas = FigureCanvas(self.figure) | |
| layout.addWidget(self.canvas) | |
| widget.setLayout(layout) | |
| return widget | |
| def create_fan_tab(self): | |
| """Create the fan control tab.""" | |
| widget = QWidget() | |
| layout = QVBoxLayout() | |
| # Profile selection | |
| profile_layout = QHBoxLayout() | |
| profile_layout.addWidget(QLabel("Select Profile:")) | |
| self.combo_profile = QComboBox() | |
| self.combo_profile.addItems(["Silent", "Balanced", "Performance"]) | |
| self.combo_profile.currentTextChanged.connect(self.on_profile_changed) | |
| profile_layout.addWidget(self.combo_profile) | |
| layout.addLayout(profile_layout) | |
| # Manual control | |
| manual_group = QGroupBox("Manual Control") | |
| manual_layout = QHBoxLayout() | |
| self.spin_manual_pwm = QSpinBox() | |
| self.spin_manual_pwm.setRange(0, 255) | |
| self.spin_manual_pwm.setValue(0) | |
| manual_layout.addWidget(QLabel("Manual PWM:")) | |
| manual_layout.addWidget(self.spin_manual_pwm) | |
| self.btn_set_manual = QPushButton("Set Manual") | |
| self.btn_set_manual.clicked.connect(self.on_set_manual) | |
| manual_layout.addWidget(self.btn_set_manual) | |
| manual_group.setLayout(manual_layout) | |
| layout.addWidget(manual_group) | |
| widget.setLayout(layout) | |
| return widget | |
| def create_settings_tab(self): | |
| """Create the settings tab.""" | |
| widget = QWidget() | |
| layout = QVBoxLayout() | |
| # Update interval | |
| interval_layout = QHBoxLayout() | |
| interval_layout.addWidget(QLabel("Update Interval (seconds):")) | |
| self.spin_interval = QDoubleSpinBox() | |
| self.spin_interval.setRange(0.1, 10.0) | |
| self.spin_interval.setValue(self.config['update_interval']) | |
| self.spin_interval.valueChanged.connect(self.on_interval_changed) | |
| interval_layout.addWidget(self.spin_interval) | |
| layout.addLayout(interval_layout) | |
| # Display options | |
| display_group = QGroupBox("Display Options") | |
| display_layout = QVBoxLayout() | |
| self.chk_show_temp = QCheckBox("Show Temperature") | |
| self.chk_show_temp.setChecked(self.config['show_temperature']) | |
| display_layout.addWidget(self.chk_show_temp) | |
| self.chk_show_load = QCheckBox("Show Load") | |
| self.chk_show_load.setChecked(self.config['show_gpu_load']) | |
| display_layout.addWidget(self.chk_show_load) | |
| self.chk_show_fan = QCheckBox("Show Fan Speed") | |
| self.chk_show_fan.setChecked(self.config['show_fan_speed']) | |
| display_layout.addWidget(self.chk_show_fan) | |
| display_group.setLayout(display_layout) | |
| layout.addWidget(display_group) | |
| widget.setLayout(layout) | |
| return widget | |
| def init_data_collection(self): | |
| """Initialize data collection threads.""" | |
| # GPU data thread | |
| self.gpu_thread = GPUDataThread(self.gpu_manager, self.config['update_interval']) | |
| self.gpu_thread.data_updated.connect(self.update_dashboard) | |
| self.gpu_thread.start() | |
| # Fan control thread | |
| self.fan_thread = FanControlThread(self.fan_controller, 2.0) | |
| self.fan_thread.status_updated.connect(self.update_fan_status) | |
| self.fan_thread.start() | |
| def update_dashboard(self, status_dict: Dict[str, Optional[GPUStatus]]): | |
| """Update dashboard display.""" | |
| for gpu_name, gpu_status in status_dict.items(): | |
| if gpu_status: | |
| # Update GPU info | |
| self.lbl_gpu_name.setText(gpu_name) | |
| self.lbl_temp.setText(f"{gpu_status.temperature:.1f} °C") | |
| self.lbl_load.setText(f"{gpu_status.load:.1f} %") | |
| self.lbl_power.setText(f"{gpu_status.power_draw:.1f} W") | |
| self.lbl_vram.setText(f"{gpu_status.memory_used}/{gpu_status.memory_total} MB") | |
| break | |
| def update_fan_status(self, fan_status): | |
| """Update fan status display.""" | |
| if fan_status: | |
| self.lbl_fan_mode.setText(fan_status.mode.value) | |
| self.lbl_fan_profile.setText(fan_status.profile) | |
| self.lbl_fan_speed.setText(f"{self.calculate_fan_rpm(fan_status.current_pwm)} RPM") | |
| self.lbl_fan_pwm.setText(f"{fan_status.current_pwm}%") | |
| def on_profile_changed(self, profile_name: str): | |
| """Handle profile change.""" | |
| self.fan_controller.set_profile(profile_name.lower()) | |
| def on_set_manual(self): | |
| """Handle manual PWM setting.""" | |
| pwm = self.spin_manual_pwm.value() | |
| self.fan_controller.set_manual_pwm(pwm) | |
| def on_interval_changed(self, value: float): | |
| """Handle update interval change.""" | |
| self.config['update_interval'] = value | |
| self.gpu_thread.update_interval = value | |
| def calculate_fan_rpm(self, pwm: int) -> int: | |
| """Calculate approximate RPM from PWM.""" | |
| return int((pwm / 255) * 4000) | |
| def closeEvent(self, event): | |
| """Clean up threads on close.""" | |
| self.gpu_thread.stop() | |
| self.fan_thread.stop() | |
| super().closeEvent(event) | |
| class GPUApplication: | |
| """Main application class.""" | |
| def __init__(self): | |
| self.config = self.load_config() | |
| self.app = QApplication(sys.argv) | |
| # Set application style | |
| self.app.setStyle("Fusion") | |
| # Create appropriate window based on mode | |
| if self.config['display_mode'] == 'overlay': | |
| self.window = GPUOverlayWindow(self.config) | |
| elif self.config['display_mode'] == 'tray': | |
| self.window = SystemTrayMonitor(self.config) | |
| else: # dashboard | |
| self.window = DashboardWindow(self.config) | |
| def load_config(self) -> Dict: | |
| """Load configuration from file.""" | |
| config_path = Path("config/monitoring.json") | |
| if config_path.exists(): | |
| with open(config_path, 'r') as f: | |
| return json.load(f) | |
| else: | |
| # Return default config | |
| return { | |
| "update_interval": 1.0, | |
| "display_mode": "overlay", | |
| "show_gpu_load": True, | |
| "show_temperature": True, | |
| "show_fan_speed": True, | |
| "show_power": True, | |
| "show_vram": True | |
| } | |
| def run(self): | |
| """Run the application.""" | |
| if hasattr(self.window, 'show'): | |
| self.window.show() | |
| sys.exit(self.app.exec_()) | |
| if __name__ == "__main__": | |
| app = GPUApplication() | |
| app.run() |