255 lines
11 KiB
Python
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}")
|