forked from masonhuang/cluster4npu
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>
98 lines
3.2 KiB
Python
98 lines
3.2 KiB
Python
"""
|
||
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
|