Final repository

This commit is contained in:
2025-08-26 13:07:59 -07:00
parent 4732549791
commit 6d9a27f1e8
38 changed files with 6600 additions and 1792 deletions

View File

@@ -5,16 +5,25 @@ Shows when latency spikes occur with different resolutions and devices
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QGroupBox, QTabWidget, QFrame, QSplitter
QGroupBox, QTabWidget, QFrame, QSplitter, QScrollArea
)
from PySide6.QtCore import Qt, QTimer, Signal, Slot
from PySide6.QtGui import QPainter, QPen, QBrush, QColor, QFont
from PySide6.QtGui import QPainter, QPen, QBrush, QColor, QFont, QLinearGradient
import numpy as np
from collections import deque
from typing import Dict, List, Any
import time
# Try to import psutil for system monitoring, use fallback if not available
try:
import psutil
PSUTIL_AVAILABLE = True
except ImportError:
PSUTIL_AVAILABLE = False
print("⚠️ psutil not available - system monitoring will use fallback values")
class RealTimeGraph(QWidget):
"""Custom widget for drawing real-time graphs"""
"""Custom widget for drawing real-time graphs with enhanced styling"""
def __init__(self, title: str = "Graph", y_label: str = "Value", max_points: int = 300):
super().__init__()
@@ -29,20 +38,75 @@ class RealTimeGraph(QWidget):
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
# Enhanced styling colors
self.bg_color = QColor(18, 18, 18) # Very dark background
self.grid_color = QColor(40, 40, 45) # Subtle grid
self.line_color = QColor(0, 230, 255) # Bright cyan
self.spike_color = QColor(255, 77, 77) # Bright red for spikes
self.cpu_color = QColor(120, 180, 255) # Light blue for CPU
self.gpu_color = QColor(255, 165, 0) # Orange for GPU
self.text_color = QColor(220, 220, 220) # Light gray text
self.accent_color = QColor(255, 215, 0) # Gold accent
# Auto-scaling
self.y_min = 0
self.y_max = 100
self.auto_scale = True
# Performance counters
self.spike_count = 0
self.device_switches = 0
self.resolution_changes = 0
self.setMinimumSize(400, 200)
self.setStyleSheet("""
QWidget {
background-color: #121212;
border: 1px solid #2a2a2a;
border-radius: 8px;
}
""")
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)
# Update counters
if is_spike:
self.spike_count += 1
if len(self.device_markers) > 1 and device != list(self.device_markers)[-2]:
self.device_switches += 1
if is_res_change:
self.resolution_changes += 1
# Auto-scale Y axis with better algorithm
if self.auto_scale and self.y_data:
data_max = max(self.y_data)
data_min = min(self.y_data)
if data_max > data_min:
padding = (data_max - data_min) * 0.15
self.y_max = data_max + padding
self.y_min = max(0, data_min - padding * 0.5)
else:
self.y_max = data_max + 10 if data_max > 0 else 100
self.y_min = 0
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.spike_count = 0
self.device_switches = 0
self.resolution_changes = 0
self.update()
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"""
@@ -71,133 +135,479 @@ class RealTimeGraph(QWidget):
self.update()
def paintEvent(self, event):
"""Override paint event to draw the graph"""
"""Override paint event to draw the graph with enhanced styling"""
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
margin = 50
graph_width = width - 2 * margin
graph_height = height - 2 * margin
# Background
painter.fillRect(self.rect(), QColor(30, 30, 30))
# Enhanced background with subtle gradient
gradient = QLinearGradient(0, 0, 0, height)
gradient.setColorAt(0, QColor(25, 25, 30))
gradient.setColorAt(1, QColor(15, 15, 20))
painter.fillRect(self.rect(), QBrush(gradient))
# Title
painter.setPen(QColor(255, 255, 255))
painter.setFont(QFont("Arial", 12, QFont.Bold))
painter.drawText(10, 20, self.title)
# Title with glow effect
painter.setPen(self.accent_color)
painter.setFont(QFont("Segoe UI", 13, QFont.Bold))
title_rect = painter.fontMetrics().boundingRect(self.title)
painter.drawText(15, 25, 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)
# Enhanced axes with better styling
painter.setPen(QPen(self.text_color, 2))
painter.drawLine(margin, margin, margin, height - margin) # Y-axis
painter.drawLine(margin, height - margin, width - margin, height - margin) # X-axis
# 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)
# Enhanced grid with subtle styling
painter.setPen(QPen(self.grid_color, 1, Qt.DotLine))
# Horizontal grid lines
for i in range(1, 5):
y = margin + (graph_height * i / 4)
painter.drawLine(margin + 5, y, width - margin - 5, y)
# Vertical grid lines
for i in range(1, 10):
x = margin + (graph_width * i / 9)
painter.drawLine(x, margin + 5, x, height - margin - 5)
# Y-axis labels
painter.setPen(QColor(200, 200, 200))
painter.setFont(QFont("Arial", 8))
# Enhanced Y-axis labels with better formatting
painter.setPen(self.text_color)
painter.setFont(QFont("Segoe UI", 9))
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}")
y_pos = margin + (graph_height * i / 4)
if y_val >= 1000:
label = f"{y_val/1000:.1f}k"
elif y_val >= 1:
label = f"{y_val:.1f}"
else:
label = f"{y_val:.2f}"
painter.drawText(5, y_pos + 4, label)
# X-axis label
# Enhanced Y-axis label with rotation
painter.save()
painter.translate(15, height // 2)
painter.setPen(self.text_color)
painter.setFont(QFont("Segoe UI", 10))
painter.translate(20, height // 2)
painter.rotate(-90)
painter.drawText(-len(self.y_label) * 3, 0, self.y_label)
painter.drawText(-len(self.y_label) * 4, 0, self.y_label)
painter.restore()
# Data points
# Enhanced data visualization
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
# Prepare point coordinates
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
x_screen = margin + (x_val - x_min) / x_range * graph_width
y_screen = height - 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))
# Draw main line with enhanced styling
if len(points) >= 2:
painter.setPen(QPen(self.line_color, 2))
painter.setPen(QPen(self.line_color, 3))
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))
# Add subtle glow effect to the line
painter.setPen(QPen(QColor(self.line_color.red(), self.line_color.green(), self.line_color.blue(), 60), 6))
for i in range(len(points) - 1):
x1, y1 = points[i]
x2, y2 = points[i + 1]
painter.drawLine(x1, y1, x2, y2)
# Enhanced spike markers
painter.setPen(QPen(self.spike_color, 2))
painter.setBrush(QBrush(self.spike_color))
for x, y in spike_points:
painter.drawEllipse(x - 3, y - 3, 6, 6)
painter.drawEllipse(x - 4, y - 4, 8, 8)
# Add spike indicator line
painter.drawLine(x, y - 10, x, y + 10)
# Enhanced device change indicators
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)
painter.setPen(QPen(color, 3))
painter.setBrush(QBrush(QColor(color.red(), color.green(), color.blue(), 100)))
painter.drawRect(x - 3, margin, 6, graph_height)
# Add device label
painter.setPen(color)
painter.setFont(QFont("Segoe UI", 8, QFont.Bold))
painter.drawText(x - 10, margin - 5, device)
# Enhanced resolution change indicators
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)
painter.setPen(QPen(QColor(255, 193, 7), 2)) # Amber color
painter.drawLine(x, margin, x, height - margin)
# Add resolution change marker
painter.setBrush(QBrush(QColor(255, 193, 7)))
painter.drawEllipse(x - 3, margin - 5, 6, 6)
class PerformanceGraphsWidget(QWidget):
"""Enhanced performance graphs widget with real-time data visualization"""
# Define signals for better integration
performance_data_updated = Signal(dict)
spike_detected = Signal(dict)
device_switched = Signal(str)
def __init__(self):
super().__init__()
self.setup_ui()
# Enhanced timer setup
self.update_timer = QTimer()
self.update_timer.timeout.connect(self.update_graphs)
self.system_timer = QTimer()
self.system_timer.timeout.connect(self.update_system_metrics)
try:
self.update_timer.start(1000)
self.update_timer.start(500) # Update graphs every 500ms for smoother animation
self.system_timer.start(1000) # Update system metrics every second
except Exception as e:
print(f"❌ Error starting performance graph timer: {e}")
self.start_time = None
print(f"❌ Error starting performance graph timers: {e}")
# Enhanced data tracking
self.start_time = time.time() if time else None
self.latest_data = {}
self.cpu_usage_history = deque(maxlen=300)
self.ram_usage_history = deque(maxlen=300) # Add missing ram_usage_history
self.frame_counter = 0
self.spike_threshold = 100.0 # Default spike threshold in ms
self.previous_device = "CPU" # Track device changes
# Performance statistics
self.latency_stats = {
'avg': 0.0,
'max': 0.0,
'min': float('inf'),
'spike_count': 0
}
def __del__(self):
"""Clean up timers when widget is destroyed"""
try:
if hasattr(self, 'system_timer') and self.system_timer:
self.system_timer.stop()
self.system_timer.deleteLater()
if hasattr(self, 'update_timer') and self.update_timer:
self.update_timer.stop()
self.update_timer.deleteLater()
except:
pass
def closeEvent(self, event):
"""Handle widget close event"""
try:
if hasattr(self, 'system_timer') and self.system_timer:
self.system_timer.stop()
if hasattr(self, 'update_timer') and self.update_timer:
self.update_timer.stop()
except:
pass
super().closeEvent(event)
self.ram_usage_history = deque(maxlen=300)
self.spike_threshold = 100 # ms threshold for latency spikes
self.previous_device = "CPU"
self.frame_counter = 0
# Performance statistics
self.latency_stats = {
'avg': 0.0,
'max': 0.0,
'min': float('inf'),
'spike_count': 0
}
self.setStyleSheet("""
QWidget {
background-color: #121212;
color: #ffffff;
}
QLabel {
color: #ffffff;
background: transparent;
}
QFrame {
background-color: #1a1a1a;
border: 1px solid #333333;
border-radius: 8px;
margin: 2px;
}
""")
def setup_ui(self):
layout = QVBoxLayout(self)
# Create main layout
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(5, 5, 5, 5)
main_layout.setSpacing(0)
# Create scroll area
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
scroll_area.setStyleSheet("""
QScrollArea {
border: none;
background-color: #121212;
}
QScrollBar:vertical {
background-color: #2C2C2C;
width: 12px;
border-radius: 6px;
}
QScrollBar::handle:vertical {
background-color: #555555;
border-radius: 6px;
min-height: 20px;
}
QScrollBar::handle:vertical:hover {
background-color: #777777;
}
QScrollBar:horizontal {
background-color: #2C2C2C;
height: 12px;
border-radius: 6px;
}
QScrollBar::handle:horizontal {
background-color: #555555;
border-radius: 6px;
min-width: 20px;
}
QScrollBar::handle:horizontal:hover {
background-color: #777777;
}
""")
# Create scrollable content widget
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(10, 10, 10, 10)
content_layout.setSpacing(8)
# Enhanced title section
title_frame = QFrame()
title_layout = QVBoxLayout(title_frame)
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)
title_label.setStyleSheet("""
font-size: 18px;
font-weight: bold;
color: #FFD700;
margin: 8px;
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
stop:0 #FFD700, stop:1 #FFA500);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
""")
title_layout.addWidget(title_label)
# Enhanced system stats
stats_layout = QHBoxLayout()
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)
self.cpu_ram_stats.setStyleSheet("""
color: #00FFFF;
font-weight: bold;
font-size: 14px;
margin: 4px 8px;
padding: 4px 8px;
background-color: rgba(0, 255, 255, 0.1);
border-radius: 4px;
""")
stats_layout.addWidget(self.cpu_ram_stats)
# Add current model display
self.current_model_stats = QLabel("Model: Loading...")
self.current_model_stats.setStyleSheet("""
color: #FFD700;
font-weight: bold;
font-size: 14px;
margin: 4px 8px;
padding: 4px 8px;
background-color: rgba(255, 215, 0, 0.1);
border-radius: 4px;
""")
stats_layout.addWidget(self.current_model_stats)
title_layout.addLayout(stats_layout)
title_frame.setLayout(title_layout)
content_layout.addWidget(title_frame)
# Enhanced splitter for graphs - set minimum sizes to avoid cluttering
splitter = QSplitter(Qt.Vertical)
# Latency graph
splitter.setStyleSheet("""
QSplitter::handle {
background-color: #333333;
height: 3px;
}
QSplitter::handle:hover {
background-color: #555555;
}
""")
# Enhanced Latency graph
latency_frame = QFrame()
latency_frame.setMinimumHeight(250) # Set minimum height to prevent cluttering
latency_frame.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 rgba(30, 30, 35, 255),
stop:1 rgba(20, 20, 25, 255));
border: 2px solid #00FFFF;
border-radius: 10px;
}
""")
latency_layout = QVBoxLayout(latency_frame)
self.latency_graph = RealTimeGraph(
"Inference Latency Over Time",
"Latency (ms)",
max_points=300
)
self.latency_graph.setMinimumHeight(200) # Ensure minimum display height
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)
self.latency_stats_label = QLabel("Avg: 0ms | Max: 0ms | Spikes: 0")
self.latency_stats_label.setStyleSheet("""
color: #00FFFF;
font-weight: bold;
font-size: 12px;
padding: 4px 8px;
background-color: rgba(0, 255, 255, 0.15);
border-radius: 4px;
margin: 4px;
""")
latency_info.addWidget(self.latency_stats_label)
latency_info.addStretch()
latency_layout.addLayout(latency_info)
latency_frame.setLayout(latency_layout)
splitter.addWidget(latency_frame)
# FPS graph
# Enhanced FPS graph
fps_frame = QFrame()
fps_frame.setMinimumHeight(250) # Set minimum height to prevent cluttering
fps_frame.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 rgba(30, 35, 30, 255),
stop:1 rgba(20, 25, 20, 255));
border: 2px solid #00FF00;
border-radius: 10px;
}
""")
fps_layout = QVBoxLayout(fps_frame)
self.fps_graph = RealTimeGraph(
"FPS & Resolution Impact",
"FPS",
max_points=300
)
self.fps_graph.setMinimumHeight(200) # Ensure minimum display height
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;
font-size: 12px;
padding: 4px 8px;
background-color: rgba(0, 255, 0, 0.15);
border-radius: 4px;
margin: 4px;
""")
fps_info.addWidget(self.fps_stats)
fps_info.addStretch()
fps_layout.addLayout(fps_info)
fps_frame.setLayout(fps_layout)
splitter.addWidget(fps_frame)
# Enhanced Device switching & resolution changes graph
device_frame = QFrame()
device_frame.setMinimumHeight(220) # Set minimum height to prevent cluttering
device_frame.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 rgba(35, 30, 30, 255),
stop:1 rgba(25, 20, 20, 255));
border: 2px solid #FFB300;
border-radius: 10px;
}
""")
device_layout = QVBoxLayout(device_frame)
self.device_graph = RealTimeGraph(
"Device Switching & Resolution Changes",
"Events",
max_points=300
)
self.device_graph.setMinimumHeight(170) # Ensure minimum display height
device_layout.addWidget(self.device_graph)
self.device_legend = QLabel(
"<span style='color:#FF4444;'>●</span> CPU Spikes: 0 | "
"<span style='color:#FFA500;'>●</span> GPU Spikes: 0 | "
"<span style='color:#78B4FF;'>●</span> Switches: 0 | "
"<span style='color:#FFC107;'>●</span> Res Changes: 0"
)
self.device_legend.setStyleSheet("""
color: #FFB300;
font-size: 12px;
font-weight: bold;
margin: 4px 8px;
padding: 4px 8px;
background-color: rgba(255, 179, 0, 0.15);
border-radius: 4px;
""")
device_layout.addWidget(self.device_legend)
device_frame.setLayout(device_layout)
splitter.addWidget(device_frame)
# Set splitter proportions with minimum space for each section
splitter.setSizes([300, 300, 250]) # Increased minimum sizes
splitter.setChildrenCollapsible(False) # Prevent collapsing sections
content_layout.addWidget(splitter)
content_widget.setLayout(content_layout)
# Set minimum size for content widget to ensure scrolling when needed
content_widget.setMinimumSize(400, 850) # Minimum width and height
# Add content widget to scroll area
scroll_area.setWidget(content_widget)
# Add scroll area to main layout
main_layout.addWidget(scroll_area)
self.setLayout(main_layout)
fps_layout = QVBoxLayout(fps_frame)
self.fps_graph = RealTimeGraph(
"FPS & Resolution Impact",
@@ -222,33 +632,267 @@ class PerformanceGraphsWidget(QWidget):
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)
# Add scroll area to main layout
main_layout.addWidget(scroll_area)
self.setLayout(main_layout)
@Slot()
def update_system_metrics(self):
"""Update system CPU and RAM usage"""
try:
# Check if the widget is still valid and not being destroyed
if not self or not hasattr(self, 'isVisible') or not self.isVisible():
return
# Check if widgets still exist before updating
if not hasattr(self, 'cpu_ram_stats') or not self.cpu_ram_stats:
return
if not hasattr(self, 'device_graph') or not self.device_graph:
return
# Check if the RealTimeGraph objects are still valid
try:
if hasattr(self.device_graph, 'add_data_point'):
# Test if the object is still valid by accessing a simple property
_ = self.device_graph.objectName()
else:
return
except RuntimeError:
# Object has been deleted
return
if PSUTIL_AVAILABLE:
cpu_percent = psutil.cpu_percent(interval=None)
memory = psutil.virtual_memory()
ram_percent = memory.percent
else:
# Fallback values when psutil is not available
cpu_percent = 0.0
ram_percent = 0.0
if hasattr(self, 'cpu_usage_history'):
self.cpu_usage_history.append(cpu_percent)
if hasattr(self, 'ram_usage_history'):
self.ram_usage_history.append(ram_percent)
# Update display
try:
if PSUTIL_AVAILABLE:
self.cpu_ram_stats.setText(f"CPU: {cpu_percent:.1f}% | RAM: {ram_percent:.1f}%")
else:
self.cpu_ram_stats.setText("CPU: -- | RAM: -- (monitoring unavailable)")
except RuntimeError:
# Widget has been deleted
return
# Add CPU usage to device graph as background metric
try:
current_time = time.time() - self.start_time if self.start_time else 0
self.device_graph.add_data_point(current_time, cpu_percent, device="System")
except RuntimeError:
# Graph has been deleted
return
except Exception as e:
print(f"❌ Error updating system metrics: {e}")
# Fallback in case of any error
try:
if hasattr(self, 'cpu_ram_stats') and self.cpu_ram_stats:
self.cpu_ram_stats.setText("CPU: -- | RAM: -- (error)")
except:
pass
@Slot()
def update_graphs(self):
# Placeholder for updating graphs with new data
pass
"""Update graphs with latest data"""
if not self.latest_data:
return
try:
chart_data = self.latest_data.get('chart_data', {})
latency_stats = self.latest_data.get('latency_stats', {})
current_metrics = self.latest_data.get('current_metrics', {})
if not chart_data.get('timestamps'):
return
# Get the latest data point
timestamps = chart_data.get('timestamps', [])
if not timestamps:
return
latest_timestamp = timestamps[-1]
current_time = time.time() - self.start_time if self.start_time else latest_timestamp
# Update latency graph
if 'inference_latency' in chart_data:
latency_values = chart_data['inference_latency']
if latency_values:
latest_latency = latency_values[-1]
is_spike = latest_latency > self.spike_threshold
device = current_metrics.get('device', 'CPU')
self.latency_graph.add_data_point(
current_time,
latest_latency,
is_spike=is_spike,
device=device
)
# Update latency statistics
self.latency_stats['max'] = max(self.latency_stats['max'], latest_latency)
self.latency_stats['min'] = min(self.latency_stats['min'], latest_latency)
if is_spike:
self.latency_stats['spike_count'] += 1
# Emit spike signal
self.spike_detected.emit({
'latency': latest_latency,
'timestamp': current_time,
'device': device
})
# Calculate running average
if hasattr(self.latency_graph, 'y_data') and self.latency_graph.y_data:
self.latency_stats['avg'] = sum(self.latency_graph.y_data) / len(self.latency_graph.y_data)
# Update FPS graph
if 'fps' in chart_data:
fps_values = chart_data['fps']
if fps_values:
latest_fps = fps_values[-1]
device = current_metrics.get('device', 'CPU')
resolution = current_metrics.get('resolution', 'Unknown')
# Check for device switch
device_switched = device != self.previous_device
if device_switched:
self.device_switched.emit(device)
self.previous_device = device
self.fps_graph.add_data_point(
current_time,
latest_fps,
device=device,
is_res_change=False # Will be set by resolution change detection
)
# Update FPS stats display with model name
model_name = current_metrics.get('model', 'Unknown')
self.fps_stats.setText(f"Current FPS: {latest_fps:.1f} | Resolution: {resolution} | Device: {device} | Model: {model_name}")
# Update device switching graph
device_usage = chart_data.get('device_usage', [])
if device_usage:
latest_usage = device_usage[-1]
device = current_metrics.get('device', 'CPU')
self.device_graph.add_data_point(
current_time,
latest_usage * 100, # Convert to percentage
device=device
)
# Update statistics displays
self.latency_stats_label.setText(
f"Avg: {self.latency_stats['avg']:.1f}ms | "
f"Max: {self.latency_stats['max']:.1f}ms | "
f"Spikes: {self.latency_stats['spike_count']}"
)
# Update device legend
self.device_legend.setText(
f"<span style='color:#FF4444;'>●</span> CPU Spikes: {self.latency_graph.spike_count} | "
f"<span style='color:#FFA500;'>●</span> GPU Spikes: {self.device_graph.spike_count} | "
f"<span style='color:#78B4FF;'>●</span> Switches: {self.device_graph.device_switches} | "
f"<span style='color:#FFC107;'>●</span> Res Changes: {self.device_graph.resolution_changes}"
)
# Update current model display
model_name = current_metrics.get('model', 'Unknown')
device = current_metrics.get('device', 'Unknown')
if hasattr(self, 'current_model_stats'):
self.current_model_stats.setText(f"Model: {model_name} | Device: {device}")
self.frame_counter += 1
except Exception as e:
print(f"❌ Error updating performance graphs: {e}")
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}")
# Initialize start time if not set
if self.start_time is None:
self.start_time = time.time()
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
# Emit signal for other components
self.performance_data_updated.emit(analytics_data)
# Immediately update graphs on new data
self.update_graphs()
except Exception as e:
print(f"❌ Error updating performance data: {e}")
def clear_all_graphs(self):
"""Clear all graph data"""
try:
self.latency_graph.clear_data()
self.fps_graph.clear_data()
self.device_graph.clear_data()
# Reset statistics
self.latency_stats = {
'avg': 0.0,
'max': 0.0,
'min': float('inf'),
'spike_count': 0
}
self.frame_counter = 0
self.start_time = time.time()
# Update displays
self.latency_stats_label.setText("Avg: 0ms | Max: 0ms | Spikes: 0")
self.fps_stats.setText("Current FPS: 0 | Resolution: - | Device: -")
self.device_legend.setText(
"<span style='color:#FF4444;'>●</span> CPU Spikes: 0 | "
"<span style='color:#FFA500;'>●</span> GPU Spikes: 0 | "
"<span style='color:#78B4FF;'>●</span> Switches: 0 | "
"<span style='color:#FFC107;'>●</span> Res Changes: 0"
)
except Exception as e:
print(f"❌ Error clearing graphs: {e}")
def set_spike_threshold(self, threshold: float):
"""Set the threshold for detecting latency spikes"""
self.spike_threshold = threshold
def get_performance_summary(self) -> Dict[str, Any]:
"""Get a summary of current performance metrics"""
return {
'latency_stats': self.latency_stats.copy(),
'frame_count': self.frame_counter,
'cpu_usage': list(self.cpu_usage_history),
'ram_usage': list(self.ram_usage_history),
'current_device': self.previous_device
}