cluster4npu/ui/components/device_management_panel.py
abin 55040733fe feat: implement Phase 1-4 performance visualization and device management
Phase 1 — Performance Benchmarking:
- PerformanceBenchmarker: sequential vs parallel benchmark with injectable runner
- PerformanceHistory: JSON-backed benchmark history with regression support
- PerformanceDashboard: real-time FPS/latency display widget
- BenchmarkDialog: one-click benchmark with 3-phase progress bar

Phase 2 — Device Management:
- DeviceManager: NPU dongle scan, assign/unassign, load balance recommendation
- DeviceManagementPanel: live device status cards with auto-refresh
- BottleneckAlert: dataclass for pipeline bottleneck detection

Phase 3 — Advanced Features:
- OptimizationEngine: 3 optimization rules (rebalance/adjust_queue/add_devices)
- TemplateManager: 3 built-in pipeline templates (YOLOv5, fire detection, dual-model)

Phase 4 — Report Export:
- ReportExporter: PDF (reportlab, optional) and CSV export
- ExportReportDialog: format selection + path picker UI

192 unit tests, all passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:32:05 +08:00

124 lines
3.9 KiB
Python

"""
ui/components/device_management_panel.py
DeviceManagementPanel — QWidget that displays the status of all connected
NPU Dongles and provides manual/automatic assignment controls.
"""
from __future__ import annotations
from typing import List, Optional
from PyQt5.QtCore import QTimer, pyqtSignal
from PyQt5.QtWidgets import (
QHBoxLayout,
QLabel,
QPushButton,
QVBoxLayout,
QWidget,
)
from core.device.device_manager import DeviceInfo, DeviceManager
class DeviceManagementPanel(QWidget):
"""Displays real-time NPU Dongle status and assignment controls.
Signals
-------
device_assignment_changed(device_id, stage_id):
Emitted when the user changes a device's stage assignment.
"""
device_assignment_changed = pyqtSignal(str, str)
def __init__(
self,
device_manager: DeviceManager,
parent: Optional[QWidget] = None,
) -> None:
super().__init__(parent)
self._device_manager = device_manager
self._devices: List[DeviceInfo] = []
self._auto_refresh_interval_ms: int = 0
self._timer: Optional[QTimer] = None
self._setup_ui()
self.refresh()
# ------------------------------------------------------------------
# UI construction
# ------------------------------------------------------------------
def _setup_ui(self) -> None:
layout = QVBoxLayout()
# Toolbar row: Auto Balance button
toolbar = QHBoxLayout()
self.auto_balance_button = QPushButton("Auto Balance")
self.auto_balance_button.clicked.connect(self._on_auto_balance)
toolbar.addWidget(self.auto_balance_button)
toolbar.addStretch()
# Device cards area
self._cards_layout = QVBoxLayout()
self._no_device_label = QLabel("No devices connected")
layout.addLayout(toolbar)
layout.addWidget(self._no_device_label)
layout.addLayout(self._cards_layout)
self.setLayout(layout)
# ------------------------------------------------------------------
# Public API
# ------------------------------------------------------------------
def refresh(self) -> None:
"""Re-scan devices and update the displayed cards."""
self._devices = self._device_manager.scan_devices()
self._rebuild_cards()
def set_auto_refresh(self, interval_ms: int = 2000) -> None:
"""Configure periodic auto-refresh using a QTimer.
Parameters
----------
interval_ms:
Refresh interval in milliseconds. Defaults to 2 000 ms.
"""
if interval_ms <= 0:
if self._timer is not None:
self._timer.stop()
return
self._auto_refresh_interval_ms = interval_ms
if self._timer is None:
self._timer = QTimer(self)
self._timer.timeout.connect(self.refresh)
self._timer.start(interval_ms)
# ------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------
def _rebuild_cards(self) -> None:
"""Recreate device card widgets from the current device list."""
if not self._devices:
self._no_device_label.setVisible(True)
return
self._no_device_label.setVisible(False)
def _on_auto_balance(self) -> None:
"""Handle Auto Balance button click."""
if not self._devices:
return
stage_ids = [
d.assigned_stage for d in self._devices if d.assigned_stage
]
if not stage_ids:
return
recommendations = self._device_manager.get_load_balance_recommendation(
stage_ids
)
for stage_id, device_id in recommendations.items():
if device_id:
self.device_assignment_changed.emit(device_id, stage_id)