cluster4npu/ui/components/performance_dashboard.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

98 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
ui/components/performance_dashboard.py
PerformanceDashboard — 顯示即時 FPS 與延遲數值的 QWidget。
使用 pyqtgraph 繪製折線圖(如可用),否則降級為純 QLabel 顯示數值,
避免 import error 導致應用崩潰。
"""
from typing import Any, Dict, Optional
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget
try:
import pyqtgraph as pg # type: ignore
_PYQTGRAPH_AVAILABLE = True
except ImportError:
_PYQTGRAPH_AVAILABLE = False
# TODO: Phase 2 - 當 pyqtgraph 可用時,改用折線圖顯示歷史 FPS/Latency
class PerformanceDashboard(QWidget):
"""即時效能儀錶板元件。
顯示當前 FPS、平均延遲與 p95 延遲。
接受 update_stats(stats) 推送的數據並更新 QLabel 顯示值。
"""
update_requested = pyqtSignal(dict)
def __init__(self, parent: Optional[QWidget] = None) -> None:
super().__init__(parent)
# 內部狀態
self.current_fps: float = 0.0
self.current_avg_latency_ms: float = 0.0
self.current_p95_latency_ms: float = 0.0
self.display_window_seconds: int = 60
# UI 元件(動態值 label前綴由靜態 label 負責)
self.fps_label = QLabel("0.0")
self.avg_latency_label = QLabel("0.0")
self.p95_latency_label = QLabel("0.0")
self._setup_ui()
def _setup_ui(self) -> None:
layout = QVBoxLayout()
fps_row = QHBoxLayout()
fps_row.addWidget(QLabel("FPS:"))
fps_row.addWidget(self.fps_label)
avg_row = QHBoxLayout()
avg_row.addWidget(QLabel("Avg Latency:"))
avg_row.addWidget(self.avg_latency_label)
p95_row = QHBoxLayout()
p95_row.addWidget(QLabel("P95 Latency:"))
p95_row.addWidget(self.p95_latency_label)
layout.addLayout(fps_row)
layout.addLayout(avg_row)
layout.addLayout(p95_row)
self.setLayout(layout)
def update_stats(self, stats: Dict[str, Any]) -> None:
"""接收效能數據並更新顯示。
Args:
stats: 包含 "fps""avg_latency_ms""p95_latency_ms" 的字典。
"""
self.current_fps = float(stats.get("fps", 0.0))
self.current_avg_latency_ms = float(stats.get("avg_latency_ms", 0.0))
self.current_p95_latency_ms = float(stats.get("p95_latency_ms", 0.0))
self.fps_label.setText(f"{self.current_fps:.1f} FPS")
self.avg_latency_label.setText(f"{self.current_avg_latency_ms:.1f} ms")
self.p95_latency_label.setText(f"{self.current_p95_latency_ms:.1f} ms")
def reset(self) -> None:
"""清空所有顯示值回到初始狀態0"""
self.current_fps = 0.0
self.current_avg_latency_ms = 0.0
self.current_p95_latency_ms = 0.0
self.fps_label.setText("0.0 FPS")
self.avg_latency_label.setText("0.0 ms")
self.p95_latency_label.setText("0.0 ms")
def set_display_window(self, seconds: int = 60) -> None:
"""設定圖表顯示的時間視窗(秒)。
Args:
seconds: 要顯示的歷史時間範圍,預設 60 秒。
"""
self.display_window_seconds = seconds