Optimize memory management and cleanup processes in MainWindow and TemplateCache
This commit is contained in:
@@ -602,8 +602,9 @@ class TemplateCache:
|
|||||||
"""
|
"""
|
||||||
Cache for template images to avoid reloading them repeatedly.
|
Cache for template images to avoid reloading them repeatedly.
|
||||||
Uses weak references to allow garbage collection when templates are no longer needed.
|
Uses weak references to allow garbage collection when templates are no longer needed.
|
||||||
|
Optimized for low memory usage.
|
||||||
"""
|
"""
|
||||||
def __init__(self, max_size=50):
|
def __init__(self, max_size=20): # Reduced from 50 to 20 for memory efficiency
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
self._max_size = max_size
|
self._max_size = max_size
|
||||||
self._access_times = {}
|
self._access_times = {}
|
||||||
@@ -628,14 +629,17 @@ class TemplateCache:
|
|||||||
if not self._access_times:
|
if not self._access_times:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Remove oldest 25% of entries
|
# Remove oldest 40% of entries (more aggressive cleanup)
|
||||||
sorted_entries = sorted(self._access_times.items(), key=lambda x: x[1])
|
sorted_entries = sorted(self._access_times.items(), key=lambda x: x[1])
|
||||||
entries_to_remove = len(sorted_entries) // 4
|
entries_to_remove = max(1, len(sorted_entries) * 2 // 5) # Remove at least 1, or 40%
|
||||||
|
|
||||||
for path, _ in sorted_entries[:entries_to_remove]:
|
for path, _ in sorted_entries[:entries_to_remove]:
|
||||||
self._cache.pop(path, None)
|
self._cache.pop(path, None)
|
||||||
self._access_times.pop(path, None)
|
self._access_times.pop(path, None)
|
||||||
|
|
||||||
|
# Force garbage collection after cleanup
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Clear all cached templates."""
|
"""Clear all cached templates."""
|
||||||
self._cache.clear()
|
self._cache.clear()
|
||||||
@@ -1316,11 +1320,24 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
loop_time = time.time() - loop_start_time
|
loop_time = time.time() - loop_start_time
|
||||||
self._update_performance_stats(loop_time)
|
self._update_performance_stats(loop_time)
|
||||||
|
|
||||||
# Periodic cleanup
|
# Periodic cleanup and memory management
|
||||||
frame_count += 1
|
frame_count += 1
|
||||||
if frame_count % 100 == 0: # Every 100 frames
|
current_time = time.time()
|
||||||
|
|
||||||
|
# More frequent memory cleanup
|
||||||
|
if frame_count % 50 == 0: # Every 50 frames (reduced from 100)
|
||||||
self._periodic_cleanup()
|
self._periodic_cleanup()
|
||||||
|
|
||||||
|
# Garbage collection every 30 seconds
|
||||||
|
if current_time - last_gc_time > 30:
|
||||||
|
gc.collect()
|
||||||
|
last_gc_time = current_time
|
||||||
|
|
||||||
|
# Clear screenshot cache periodically to prevent memory buildup
|
||||||
|
if (self._last_screenshot is not None and
|
||||||
|
current_time - self._last_screenshot_time > 2.0): # Clear after 2 seconds
|
||||||
|
self._last_screenshot = None
|
||||||
|
|
||||||
# Dynamic sleep based on performance
|
# Dynamic sleep based on performance
|
||||||
sleep_time = max(0.05, 0.2 - loop_time) if not step_executed else 0.1
|
sleep_time = max(0.05, 0.2 - loop_time) if not step_executed else 0.1
|
||||||
time.sleep(sleep_time)
|
time.sleep(sleep_time)
|
||||||
@@ -1753,16 +1770,22 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
# Memory optimization attributes
|
# Memory optimization attributes
|
||||||
self._last_screenshot = None
|
self._last_screenshot = None
|
||||||
self._last_screenshot_time = 0
|
self._last_screenshot_time = 0
|
||||||
self._screenshot_cache_duration = 0.05 # Cache screenshot for 50ms
|
self._screenshot_cache_duration = 0.03 # Reduced from 0.05 to 0.03 for faster memory freeing
|
||||||
self._processing_step = False
|
self._processing_step = False
|
||||||
self._step_cooldown = {} # Cooldown tracking for steps
|
self._step_cooldown = {} # Cooldown tracking for steps
|
||||||
|
|
||||||
# Performance monitoring
|
# Performance monitoring
|
||||||
self._loop_times = []
|
self._loop_times = []
|
||||||
self._max_loop_time_samples = 10
|
self._max_loop_time_samples = 5 # Reduced from 10 to 5 for memory efficiency
|
||||||
self._last_warning_messages = {} # Track last warning messages to prevent duplicates
|
self._last_warning_messages = {} # Track last warning messages to prevent duplicates
|
||||||
self._warning_cooldown = 30 # Seconds between duplicate warnings
|
self._warning_cooldown = 30 # Seconds between duplicate warnings
|
||||||
|
|
||||||
|
# Memory management
|
||||||
|
self._memory_cleanup_timer = QtCore.QTimer()
|
||||||
|
self._memory_cleanup_timer.timeout.connect(self._periodic_memory_cleanup)
|
||||||
|
self._memory_cleanup_timer.start(300000) # Cleanup every 5 minutes
|
||||||
|
self._last_memory_cleanup = time.time()
|
||||||
|
|
||||||
# Resource usage tracking for max/average values
|
# Resource usage tracking for max/average values
|
||||||
self._resource_history = {
|
self._resource_history = {
|
||||||
'memory_mb': [],
|
'memory_mb': [],
|
||||||
@@ -1773,7 +1796,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
'gpu_utilization': [],
|
'gpu_utilization': [],
|
||||||
'loop_times': []
|
'loop_times': []
|
||||||
}
|
}
|
||||||
self._max_resource_samples = 50 # Keep last 50 samples for statistics
|
self._max_resource_samples = 30 # Reduced from 50 to 30 for memory efficiency
|
||||||
|
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
self.load_scenarios()
|
self.load_scenarios()
|
||||||
@@ -2053,7 +2076,10 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
else:
|
else:
|
||||||
system_gpu_text = "GPU: Not detected\nClick for info"
|
system_gpu_text = "GPU: Not detected\nClick for info"
|
||||||
system_gpu_color = colors['text_light']
|
system_gpu_color = colors['text_light']
|
||||||
self.system_gpu_label.mousePressEvent = lambda event: self._show_gpu_info()
|
# Clear any existing event handler first to prevent memory leaks
|
||||||
|
self.system_gpu_label.mousePressEvent = None
|
||||||
|
# Use a proper connection instead of lambda to prevent circular reference
|
||||||
|
self.system_gpu_label.mousePressEvent = self._on_gpu_label_clicked
|
||||||
self.system_gpu_label.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor))
|
self.system_gpu_label.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor))
|
||||||
|
|
||||||
self.system_gpu_label.setText(system_gpu_text)
|
self.system_gpu_label.setText(system_gpu_text)
|
||||||
@@ -2086,7 +2112,10 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
else:
|
else:
|
||||||
program_ram_text = "RAM: N/A\npsutil needed"
|
program_ram_text = "RAM: N/A\npsutil needed"
|
||||||
program_ram_color = colors['warning']
|
program_ram_color = colors['warning']
|
||||||
self.program_ram_label.mousePressEvent = lambda event: self._show_psutil_info()
|
# Clear any existing event handler first to prevent memory leaks
|
||||||
|
self.program_ram_label.mousePressEvent = None
|
||||||
|
# Use a proper connection instead of lambda to prevent circular reference
|
||||||
|
self.program_ram_label.mousePressEvent = self._on_ram_label_clicked
|
||||||
self.program_ram_label.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor))
|
self.program_ram_label.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor))
|
||||||
|
|
||||||
self.program_ram_label.setText(program_ram_text)
|
self.program_ram_label.setText(program_ram_text)
|
||||||
@@ -2151,6 +2180,12 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.monitor_timer.stop()
|
self.monitor_timer.stop()
|
||||||
if hasattr(self, 'theme_monitor_timer') and self.theme_monitor_timer:
|
if hasattr(self, 'theme_monitor_timer') and self.theme_monitor_timer:
|
||||||
self.theme_monitor_timer.stop()
|
self.theme_monitor_timer.stop()
|
||||||
|
if hasattr(self, '_memory_cleanup_timer') and self._memory_cleanup_timer:
|
||||||
|
self._memory_cleanup_timer.stop()
|
||||||
|
|
||||||
|
# Clear caches when monitoring stops
|
||||||
|
self._last_screenshot = None
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
def _show_psutil_info(self):
|
def _show_psutil_info(self):
|
||||||
"""Show information about installing psutil for enhanced monitoring."""
|
"""Show information about installing psutil for enhanced monitoring."""
|
||||||
@@ -2200,6 +2235,14 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
msg.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok)
|
msg.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok)
|
||||||
msg.exec()
|
msg.exec()
|
||||||
|
|
||||||
|
def _on_gpu_label_clicked(self, event):
|
||||||
|
"""Handle GPU label click event without lambda to prevent memory leaks."""
|
||||||
|
self._show_gpu_info()
|
||||||
|
|
||||||
|
def _on_ram_label_clicked(self, event):
|
||||||
|
"""Handle RAM label click event without lambda to prevent memory leaks."""
|
||||||
|
self._show_psutil_info()
|
||||||
|
|
||||||
def _on_update_interval_changed(self):
|
def _on_update_interval_changed(self):
|
||||||
"""Handle resource monitoring update interval change."""
|
"""Handle resource monitoring update interval change."""
|
||||||
interval = self.update_interval_combo.currentData()
|
interval = self.update_interval_combo.currentData()
|
||||||
@@ -2448,18 +2491,60 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
def cleanup_resources(self):
|
def cleanup_resources(self):
|
||||||
"""Clean up resources to prevent memory leaks."""
|
"""Enhanced cleanup to prevent memory leaks."""
|
||||||
if self.running:
|
if self.running:
|
||||||
self.stop_automation()
|
self.stop_automation()
|
||||||
|
|
||||||
# Stop monitoring timer
|
# Stop and disconnect all timers
|
||||||
self.stop_monitoring()
|
self.stop_monitoring()
|
||||||
|
|
||||||
|
# Disconnect all signal connections to prevent circular references
|
||||||
|
try:
|
||||||
|
# Disconnect timer signals
|
||||||
|
if hasattr(self, 'monitor_timer') and self.monitor_timer:
|
||||||
|
self.monitor_timer.timeout.disconnect()
|
||||||
|
self.monitor_timer.stop()
|
||||||
|
self.monitor_timer.deleteLater()
|
||||||
|
self.monitor_timer = None
|
||||||
|
|
||||||
|
if hasattr(self, 'theme_monitor_timer') and self.theme_monitor_timer:
|
||||||
|
self.theme_monitor_timer.timeout.disconnect()
|
||||||
|
self.theme_monitor_timer.stop()
|
||||||
|
self.theme_monitor_timer.deleteLater()
|
||||||
|
self.theme_monitor_timer = None
|
||||||
|
|
||||||
|
if hasattr(self, '_memory_cleanup_timer') and self._memory_cleanup_timer:
|
||||||
|
self._memory_cleanup_timer.timeout.disconnect()
|
||||||
|
self._memory_cleanup_timer.stop()
|
||||||
|
self._memory_cleanup_timer.deleteLater()
|
||||||
|
self._memory_cleanup_timer = None
|
||||||
|
|
||||||
|
# Clear event handlers that use lambda functions to prevent circular references
|
||||||
|
if hasattr(self, 'system_gpu_label'):
|
||||||
|
self.system_gpu_label.mousePressEvent = None
|
||||||
|
self.system_gpu_label.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
|
||||||
|
|
||||||
|
if hasattr(self, 'program_ram_label'):
|
||||||
|
self.program_ram_label.mousePressEvent = None
|
||||||
|
self.program_ram_label.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error disconnecting signals: {e}")
|
||||||
|
|
||||||
# Clear template cache
|
# Clear template cache
|
||||||
template_cache.clear()
|
template_cache.clear()
|
||||||
|
|
||||||
# Clear screenshot cache
|
# Clear screenshot cache
|
||||||
self._last_screenshot = None
|
self._last_screenshot = None
|
||||||
|
self._last_screenshot_time = 0
|
||||||
|
|
||||||
|
# Clear GPU memory cache
|
||||||
|
if hasattr(get_gpu_memory_info, '_cached_info'):
|
||||||
|
delattr(get_gpu_memory_info, '_cached_info')
|
||||||
|
if hasattr(get_gpu_memory_info, '_cache_time'):
|
||||||
|
delattr(get_gpu_memory_info, '_cache_time')
|
||||||
|
if hasattr(get_gpu_memory_info, '_updating'):
|
||||||
|
delattr(get_gpu_memory_info, '_updating')
|
||||||
|
|
||||||
# Clear performance tracking
|
# Clear performance tracking
|
||||||
if hasattr(self, '_loop_times'):
|
if hasattr(self, '_loop_times'):
|
||||||
@@ -2470,11 +2555,100 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
for key in self._resource_history:
|
for key in self._resource_history:
|
||||||
self._resource_history[key].clear()
|
self._resource_history[key].clear()
|
||||||
|
|
||||||
# Force garbage collection
|
# Clear warning message cache
|
||||||
gc.collect()
|
if hasattr(self, '_last_warning_messages'):
|
||||||
|
self._last_warning_messages.clear()
|
||||||
|
|
||||||
|
# Clear any geometry save timer
|
||||||
|
if hasattr(self, '_geometry_save_timer'):
|
||||||
|
self._geometry_save_timer.stop()
|
||||||
|
self._geometry_save_timer.deleteLater()
|
||||||
|
self._geometry_save_timer = None
|
||||||
|
|
||||||
|
# Clear dialog-related attributes to prevent references
|
||||||
|
if hasattr(self, '_edit_step_idx'):
|
||||||
|
delattr(self, '_edit_step_idx')
|
||||||
|
if hasattr(self, '_step_dialog') and self._step_dialog:
|
||||||
|
self._step_dialog.deleteLater()
|
||||||
|
self._step_dialog = None
|
||||||
|
|
||||||
|
# Clear image preview pixmaps if any dialogs are open
|
||||||
|
try:
|
||||||
|
for widget in QtWidgets.QApplication.allWidgets():
|
||||||
|
if hasattr(widget, 'img_preview') and hasattr(widget.img_preview, 'clear'):
|
||||||
|
widget.img_preview.clear()
|
||||||
|
# Disconnect any remaining signal connections
|
||||||
|
if hasattr(widget, 'disconnect'):
|
||||||
|
try:
|
||||||
|
widget.disconnect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Force garbage collection multiple times for better cleanup
|
||||||
|
for _ in range(3):
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
logger.info("Resources cleaned up successfully")
|
logger.info("Resources cleaned up successfully")
|
||||||
|
|
||||||
|
def _periodic_memory_cleanup(self):
|
||||||
|
"""Periodic memory cleanup to prevent memory accumulation."""
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# Only cleanup if enough time has passed since last cleanup
|
||||||
|
if current_time - self._last_memory_cleanup < 300: # 5 minutes
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Clear old screenshot cache if not used recently
|
||||||
|
if (self._last_screenshot is not None and
|
||||||
|
current_time - self._last_screenshot_time > 60): # 1 minute old
|
||||||
|
self._last_screenshot = None
|
||||||
|
self._last_screenshot_time = 0
|
||||||
|
|
||||||
|
# Limit resource history size to prevent memory growth
|
||||||
|
max_history_size = 50 # Reduced from default to save memory
|
||||||
|
if hasattr(self, '_resource_history'):
|
||||||
|
for key in self._resource_history:
|
||||||
|
if len(self._resource_history[key]) > max_history_size:
|
||||||
|
# Keep only the most recent entries
|
||||||
|
self._resource_history[key] = self._resource_history[key][-max_history_size:]
|
||||||
|
|
||||||
|
# Limit loop times tracking
|
||||||
|
if hasattr(self, '_loop_times') and len(self._loop_times) > self._max_loop_time_samples:
|
||||||
|
self._loop_times = self._loop_times[-self._max_loop_time_samples:]
|
||||||
|
|
||||||
|
# Clear old step cooldowns (older than 10 minutes)
|
||||||
|
if hasattr(self, '_step_cooldown'):
|
||||||
|
cutoff_time = current_time - 600 # 10 minutes
|
||||||
|
self._step_cooldown = {k: v for k, v in self._step_cooldown.items() if v > cutoff_time}
|
||||||
|
|
||||||
|
# Clear old warning message cache
|
||||||
|
if hasattr(self, '_last_warning_messages'):
|
||||||
|
cutoff_time = current_time - self._warning_cooldown * 2
|
||||||
|
self._last_warning_messages = {
|
||||||
|
k: v for k, v in self._last_warning_messages.items()
|
||||||
|
if v[0] > cutoff_time
|
||||||
|
}
|
||||||
|
|
||||||
|
# Template cache cleanup - remove least recently used items if cache is large
|
||||||
|
if hasattr(template_cache, '_cache') and len(template_cache._cache) > 15:
|
||||||
|
# Clear 1/3 of the cache to reduce memory
|
||||||
|
items_to_remove = len(template_cache._cache) // 3
|
||||||
|
cache_items = list(template_cache._cache.items())
|
||||||
|
for i in range(items_to_remove):
|
||||||
|
del template_cache._cache[cache_items[i][0]]
|
||||||
|
|
||||||
|
# Force garbage collection
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
self._last_memory_cleanup = current_time
|
||||||
|
logger.debug("Periodic memory cleanup completed")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error during periodic memory cleanup: {e}")
|
||||||
|
|
||||||
def is_system_dark_theme(self):
|
def is_system_dark_theme(self):
|
||||||
"""
|
"""
|
||||||
Detect if the system is using dark theme.
|
Detect if the system is using dark theme.
|
||||||
@@ -3702,6 +3876,12 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self._step_dialog.deleteLater()
|
self._step_dialog.deleteLater()
|
||||||
self._step_dialog = None
|
self._step_dialog = None
|
||||||
|
|
||||||
|
def _on_edit_step_accepted_handler(self):
|
||||||
|
"""Handler to avoid lambda function for edit step dialog"""
|
||||||
|
if hasattr(self, '_edit_step_idx'):
|
||||||
|
self._on_edit_step_accepted(self._edit_step_idx)
|
||||||
|
delattr(self, '_edit_step_idx')
|
||||||
|
|
||||||
def edit_step(self):
|
def edit_step(self):
|
||||||
"""
|
"""
|
||||||
Open the dialog to edit the selected step in the current scenario.
|
Open the dialog to edit the selected step in the current scenario.
|
||||||
@@ -3711,7 +3891,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
return
|
return
|
||||||
step = self.current_scenario.steps[idx]
|
step = self.current_scenario.steps[idx]
|
||||||
self._step_dialog = StepDialog(self, step)
|
self._step_dialog = StepDialog(self, step)
|
||||||
self._step_dialog.accepted.connect(lambda: self._on_edit_step_accepted(idx))
|
self._edit_step_idx = idx # Store idx to avoid lambda
|
||||||
|
self._step_dialog.accepted.connect(self._on_edit_step_accepted_handler)
|
||||||
self._step_dialog.show()
|
self._step_dialog.show()
|
||||||
|
|
||||||
def _on_edit_step_accepted(self, idx):
|
def _on_edit_step_accepted(self, idx):
|
||||||
|
|||||||
Reference in New Issue
Block a user