Final repository
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user