Files
Traffic-Intersection-Monito…/qt_app_pyside1/ui/performance_graphs.py

255 lines
11 KiB
Python

"""
Real-time performance graphs for inference latency analysis
Shows when latency spikes occur with different resolutions and devices
"""
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QGroupBox, QTabWidget, QFrame, QSplitter
)
from PySide6.QtCore import Qt, QTimer, Signal, Slot
from PySide6.QtGui import QPainter, QPen, QBrush, QColor, QFont
import numpy as np
from collections import deque
from typing import Dict, List, Any
class RealTimeGraph(QWidget):
"""Custom widget for drawing real-time graphs"""
def __init__(self, title: str = "Graph", y_label: str = "Value", max_points: int = 300):
super().__init__()
self.title = title
self.y_label = y_label
self.max_points = max_points
# Data storage
self.x_data = deque(maxlen=max_points)
self.y_data = deque(maxlen=max_points)
self.spike_markers = deque(maxlen=max_points) # Mark spikes
self.device_markers = deque(maxlen=max_points) # Mark device changes
self.resolution_markers = deque(maxlen=max_points) # Mark resolution changes
# Graph settings
self.margin = 40
self.grid_color = QColor(60, 60, 60)
self.line_color = QColor(0, 255, 255) # Cyan
self.spike_color = QColor(255, 0, 0) # Red for spikes
self.cpu_color = QColor(100, 150, 255) # Blue for CPU
self.gpu_color = QColor(255, 150, 100) # Orange for GPU
# Auto-scaling
self.y_min = 0
self.y_max = 100
self.auto_scale = True
self.setMinimumSize(400, 200)
def add_data_point(self, x: float, y: float, is_spike: bool = False, device: str = "CPU", is_res_change: bool = False):
"""Add a new data point to the graph"""
self.x_data.append(x)
self.y_data.append(y)
self.spike_markers.append(is_spike)
self.device_markers.append(device)
self.resolution_markers.append(is_res_change)
# Auto-scale Y axis
if self.auto_scale and self.y_data:
data_max = max(self.y_data)
data_min = min(self.y_data)
padding = (data_max - data_min) * 0.1
self.y_max = data_max + padding if data_max > 0 else 100
self.y_min = max(0, data_min - padding)
self.update()
def clear_data(self):
"""Clear the graph data"""
self.x_data.clear()
self.y_data.clear()
self.spike_markers.clear()
self.device_markers.clear()
self.resolution_markers.clear()
self.update()
def paintEvent(self, event):
"""Override paint event to draw the graph"""
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
width = self.width()
height = self.height()
graph_width = width - 2 * self.margin
graph_height = height - 2 * self.margin
# Background
painter.fillRect(self.rect(), QColor(30, 30, 30))
# Title
painter.setPen(QColor(255, 255, 255))
painter.setFont(QFont("Arial", 12, QFont.Bold))
painter.drawText(10, 20, self.title)
# Axes
painter.setPen(QPen(QColor(200, 200, 200), 2))
painter.drawLine(self.margin, self.margin, self.margin, height - self.margin)
painter.drawLine(self.margin, height - self.margin, width - self.margin, height - self.margin)
# Grid
painter.setPen(QPen(self.grid_color, 1))
for i in range(5):
y = self.margin + (graph_height * i / 4)
painter.drawLine(self.margin, y, width - self.margin, y)
for i in range(10):
x = self.margin + (graph_width * i / 9)
painter.drawLine(x, self.margin, x, height - self.margin)
# Y-axis labels
painter.setPen(QColor(200, 200, 200))
painter.setFont(QFont("Arial", 8))
for i in range(5):
y_val = self.y_min + (self.y_max - self.y_min) * (4 - i) / 4
y_pos = self.margin + (graph_height * i / 4)
painter.drawText(5, y_pos + 5, f"{y_val:.1f}")
# X-axis label
painter.save()
painter.translate(15, height // 2)
painter.rotate(-90)
painter.drawText(-len(self.y_label) * 3, 0, self.y_label)
painter.restore()
# Data points
if len(self.x_data) >= 2 and len(self.y_data) >= 2:
points = []
spike_points = []
device_changes = []
res_changes = []
x_min = min(self.x_data) if self.x_data else 0
x_max = max(self.x_data) if self.x_data else 1
x_range = x_max - x_min if x_max > x_min else 1
for i, (x_val, y_val, is_spike, device, is_res_change) in enumerate(zip(
self.x_data, self.y_data, self.spike_markers, self.device_markers, self.resolution_markers
)):
x_screen = self.margin + (x_val - x_min) / x_range * graph_width
y_screen = height - self.margin - (y_val - self.y_min) / (self.y_max - self.y_min) * graph_height
points.append((x_screen, y_screen))
if is_spike:
spike_points.append((x_screen, y_screen))
if i > 0 and device != list(self.device_markers)[i-1]:
device_changes.append((x_screen, y_screen, device))
if is_res_change:
res_changes.append((x_screen, y_screen))
if len(points) >= 2:
painter.setPen(QPen(self.line_color, 2))
for i in range(len(points) - 1):
x1, y1 = points[i]
x2, y2 = points[i + 1]
painter.drawLine(x1, y1, x2, y2)
painter.setPen(QPen(self.spike_color, 3))
painter.setBrush(QBrush(self.spike_color))
for x, y in spike_points:
painter.drawEllipse(x - 3, y - 3, 6, 6)
for x, y, device in device_changes:
color = self.gpu_color if device == "GPU" else self.cpu_color
painter.setPen(QPen(color, 2))
painter.setBrush(QBrush(color))
painter.drawRect(x - 2, self.margin, 4, graph_height)
for x, y in res_changes:
painter.setPen(QPen(QColor(255, 167, 38), 2)) # Orange for resolution change
painter.drawLine(x, self.margin, x, height - self.margin)
class PerformanceGraphsWidget(QWidget):
def __init__(self):
super().__init__()
self.setup_ui()
self.update_timer = QTimer()
self.update_timer.timeout.connect(self.update_graphs)
try:
self.update_timer.start(1000)
except Exception as e:
print(f"❌ Error starting performance graph timer: {e}")
self.start_time = None
self.latest_data = {}
self.cpu_usage_history = deque(maxlen=300)
self.ram_usage_history = deque(maxlen=300)
def setup_ui(self):
layout = QVBoxLayout(self)
title_label = QLabel("🔥 Real-Time Inference Performance & Latency Spike Analysis")
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #FFD700; margin: 10px;")
layout.addWidget(title_label)
self.cpu_ram_stats = QLabel("CPU: 0% | RAM: 0%")
self.cpu_ram_stats.setStyleSheet("color: #FFD700; font-weight: bold; font-size: 14px; margin: 8px;")
layout.addWidget(self.cpu_ram_stats)
splitter = QSplitter(Qt.Vertical)
# Latency graph
latency_frame = QFrame()
latency_layout = QVBoxLayout(latency_frame)
self.latency_graph = RealTimeGraph(
"Inference Latency Over Time",
"Latency (ms)",
max_points=300
)
latency_layout.addWidget(self.latency_graph)
latency_info = QHBoxLayout()
self.latency_stats = QLabel("Avg: 0ms | Max: 0ms | Spikes: 0")
self.latency_stats.setStyleSheet("color: #00FFFF; font-weight: bold;")
latency_info.addWidget(self.latency_stats)
latency_info.addStretch()
latency_layout.addLayout(latency_info)
latency_frame.setLayout(latency_layout)
splitter.addWidget(latency_frame)
# FPS graph
fps_frame = QFrame()
fps_layout = QVBoxLayout(fps_frame)
self.fps_graph = RealTimeGraph(
"FPS & Resolution Impact",
"FPS",
max_points=300
)
fps_layout.addWidget(self.fps_graph)
fps_info = QHBoxLayout()
self.fps_stats = QLabel("Current FPS: 0 | Resolution: - | Device: -")
self.fps_stats.setStyleSheet("color: #00FF00; font-weight: bold;")
fps_info.addWidget(self.fps_stats)
fps_info.addStretch()
fps_layout.addLayout(fps_info)
fps_frame.setLayout(fps_layout)
splitter.addWidget(fps_frame)
# Device switching & resolution changes graph
device_frame = QFrame()
device_layout = QVBoxLayout(device_frame)
self.device_graph = RealTimeGraph(
"Device Switching & Resolution Changes",
"-",
max_points=300
)
device_layout.addWidget(self.device_graph)
self.device_legend = QLabel("<span style='color:#ff4444;'>CPU Spikes</span>: 0 | <span style='color:#ff5722;'>GPU Spikes</span>: 0 | <span style='color:#2196f3;'>Switches</span>: 0 | <span style='color:#ffa726;'>Res Changes</span>: 0")
self.device_legend.setStyleSheet("color: #ffb300; font-size: 13px; font-weight: bold; margin: 2px 0 0 8px;")
device_layout.addWidget(self.device_legend)
device_frame.setLayout(device_layout)
splitter.addWidget(device_frame)
layout.addWidget(splitter)
self.setLayout(layout)
def update_graphs(self):
# Placeholder for updating graphs with new data
pass
def update_performance_data(self, analytics_data: Dict[str, Any]):
"""Update graphs with new analytics data, including system metrics"""
try:
print(f"[PERF DEBUG] update_performance_data called with: {analytics_data}")
chart_data = analytics_data.get('real_time_data', {})
latency_stats = analytics_data.get('latency_statistics', {})
current_metrics = analytics_data.get('current_metrics', {})
system_metrics = analytics_data.get('system_metrics', {})
if not chart_data.get('timestamps'):
print("[PERF DEBUG] No timestamps in chart_data")
return
self.latest_data = {
'chart_data': chart_data,
'latency_stats': latency_stats,
'current_metrics': current_metrics,
'system_metrics': system_metrics
}
self.update_graphs() # Immediately update graphs on new data
except Exception as e:
print(f"❌ Error updating performance data: {e}")