♻️ refactor(memory_monitor): improve memory calculation logic
This commit is contained in:
1 file changed
-382
@@ -1,382 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Memory Monitor Script for Modus Components
|
||||
Shows memory usage for each component in the modules/ directory.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import psutil
|
||||
import subprocess
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
import argparse
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComponentMemory:
|
||||
"""Data class to store component memory information."""
|
||||
name: str
|
||||
pid: Optional[int]
|
||||
memory_mb: float
|
||||
memory_percent: float
|
||||
cpu_percent: float
|
||||
status: str
|
||||
children_count: int = 0
|
||||
children_memory_mb: float = 0.0
|
||||
|
||||
|
||||
class ModusMemoryMonitor:
|
||||
"""Monitor memory usage of Modus components."""
|
||||
|
||||
def __init__(self):
|
||||
self.modus_processes = {}
|
||||
self.component_mapping = {
|
||||
# Main components from modules/
|
||||
'dock': ['dock', 'Dock'],
|
||||
'launcher': ['launcher', 'Launcher'],
|
||||
'notification_popup': ['notification', 'NotificationPopup'],
|
||||
'osd': ['osd', 'OSD'],
|
||||
'switcher': ['switcher', 'ApplicationSwitcher'],
|
||||
'corners': ['corners', 'Corners'],
|
||||
|
||||
# Dock components
|
||||
'dock_applications': ['applications'],
|
||||
'dock_battery': ['battery'],
|
||||
'dock_controls': ['controls'],
|
||||
'dock_indicators': ['indicators'],
|
||||
'dock_metrics': ['metrics'],
|
||||
'dock_notifications': ['dock_notifications'],
|
||||
'dock_workspaces': ['workspaces'],
|
||||
|
||||
# Launcher plugins
|
||||
'plugin_applications': ['plugin_applications'],
|
||||
'plugin_bluetooth': ['plugin_bluetooth'],
|
||||
'plugin_bookmarks': ['plugin_bookmarks'],
|
||||
'plugin_caffeine': ['plugin_caffeine'],
|
||||
'plugin_calculator': ['plugin_calculator'],
|
||||
'plugin_calendar': ['plugin_calendar'],
|
||||
'plugin_clipboard': ['plugin_clipboard'],
|
||||
'plugin_emoji': ['plugin_emoji'],
|
||||
'plugin_kanban': ['plugin_kanban'],
|
||||
'plugin_network': ['plugin_network'],
|
||||
'plugin_otp': ['plugin_otp'],
|
||||
'plugin_password': ['plugin_password'],
|
||||
'plugin_power': ['plugin_power'],
|
||||
'plugin_process': ['plugin_process'],
|
||||
'plugin_reminders': ['plugin_reminders'],
|
||||
'plugin_screencapture': ['plugin_screencapture'],
|
||||
'plugin_system': ['plugin_system'],
|
||||
'plugin_wallpaper': ['plugin_wallpaper'],
|
||||
'plugin_websearch': ['plugin_websearch'],
|
||||
|
||||
# Services
|
||||
'service_brightness': ['brightness'],
|
||||
'service_network': ['network'],
|
||||
'service_battery': ['battery_service'],
|
||||
'service_mpris': ['mpris'],
|
||||
'service_notification': ['notification_service'],
|
||||
}
|
||||
|
||||
def find_modus_processes(self) -> List[psutil.Process]:
|
||||
"""Find all processes related to Modus."""
|
||||
modus_processes = []
|
||||
|
||||
try:
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cmdline', 'ppid']):
|
||||
try:
|
||||
info = proc.info
|
||||
cmdline = ' '.join(info['cmdline']) if info['cmdline'] else ''
|
||||
|
||||
# Check if this is a Modus-related process
|
||||
if (info['name'] == 'modus' or
|
||||
'main.py' in cmdline or
|
||||
'Modus' in cmdline or
|
||||
any(module in cmdline.lower() for module in ['dock', 'launcher', 'notification'])):
|
||||
modus_processes.append(proc)
|
||||
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error finding Modus processes: {e}")
|
||||
|
||||
return modus_processes
|
||||
|
||||
def get_process_memory_info(self, proc: psutil.Process) -> Tuple[float, float, float]:
|
||||
"""Get memory and CPU information for a process."""
|
||||
try:
|
||||
memory_info = proc.memory_info()
|
||||
memory_mb = memory_info.rss / (1024 * 1024)
|
||||
memory_percent = proc.memory_percent()
|
||||
cpu_percent = proc.cpu_percent()
|
||||
return memory_mb, memory_percent, cpu_percent
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
return 0.0, 0.0, 0.0
|
||||
|
||||
def categorize_process(self, proc: psutil.Process) -> str:
|
||||
"""Categorize a process based on its command line and name."""
|
||||
try:
|
||||
info = proc.info
|
||||
cmdline = ' '.join(info['cmdline']) if info['cmdline'] else ''
|
||||
name = info['name'].lower()
|
||||
|
||||
# Check for main process
|
||||
if 'main.py' in cmdline:
|
||||
return 'main_process'
|
||||
|
||||
# Check for specific components based on command line or imports
|
||||
for component, keywords in self.component_mapping.items():
|
||||
if any(keyword.lower() in cmdline.lower() or keyword.lower() in name for keyword in keywords):
|
||||
return component
|
||||
|
||||
# Default categorization
|
||||
if 'python' in name:
|
||||
return 'python_subprocess'
|
||||
else:
|
||||
return 'other'
|
||||
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
return 'unknown'
|
||||
|
||||
def get_component_memory_usage(self) -> Dict[str, ComponentMemory]:
|
||||
"""Get memory usage for each Modus component."""
|
||||
components = {}
|
||||
modus_processes = self.find_modus_processes()
|
||||
|
||||
if not modus_processes:
|
||||
print("No Modus processes found. Make sure Modus is running.")
|
||||
return components
|
||||
|
||||
# Initialize CPU monitoring for better readings
|
||||
for proc in modus_processes:
|
||||
try:
|
||||
proc.cpu_percent()
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
continue
|
||||
|
||||
# Wait a bit for CPU percentage calculation
|
||||
time.sleep(0.1)
|
||||
|
||||
for proc in modus_processes:
|
||||
try:
|
||||
category = self.categorize_process(proc)
|
||||
memory_mb, memory_percent, cpu_percent = self.get_process_memory_info(proc)
|
||||
|
||||
if category not in components:
|
||||
components[category] = ComponentMemory(
|
||||
name=category,
|
||||
pid=proc.pid,
|
||||
memory_mb=memory_mb,
|
||||
memory_percent=memory_percent,
|
||||
cpu_percent=cpu_percent,
|
||||
status=proc.status(),
|
||||
children_count=0,
|
||||
children_memory_mb=0.0
|
||||
)
|
||||
else:
|
||||
# Aggregate memory for components with multiple processes
|
||||
existing = components[category]
|
||||
existing.memory_mb += memory_mb
|
||||
existing.memory_percent += memory_percent
|
||||
existing.cpu_percent += cpu_percent
|
||||
existing.children_count += 1
|
||||
existing.children_memory_mb += memory_mb
|
||||
|
||||
# Also check for child processes
|
||||
try:
|
||||
children = proc.children(recursive=True)
|
||||
for child in children:
|
||||
child_memory_mb, child_memory_percent, child_cpu_percent = self.get_process_memory_info(child)
|
||||
components[category].children_memory_mb += child_memory_mb
|
||||
components[category].memory_mb += child_memory_mb
|
||||
components[category].memory_percent += child_memory_percent
|
||||
components[category].cpu_percent += child_cpu_percent
|
||||
components[category].children_count += 1
|
||||
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
pass
|
||||
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
continue
|
||||
|
||||
return components
|
||||
|
||||
def get_module_file_sizes(self) -> Dict[str, float]:
|
||||
"""Get file sizes for modules in the modules/ directory."""
|
||||
module_sizes = {}
|
||||
modules_dir = os.path.join(os.path.dirname(__file__), 'modules')
|
||||
|
||||
if not os.path.exists(modules_dir):
|
||||
return module_sizes
|
||||
|
||||
try:
|
||||
for root, _, files in os.walk(modules_dir):
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
file_path = os.path.join(root, file)
|
||||
try:
|
||||
size_kb = os.path.getsize(file_path) / 1024
|
||||
relative_path = os.path.relpath(file_path, modules_dir)
|
||||
module_sizes[relative_path] = size_kb
|
||||
except OSError:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Error getting module file sizes: {e}")
|
||||
|
||||
return module_sizes
|
||||
|
||||
def print_memory_report(self, components: Dict[str, ComponentMemory],
|
||||
module_sizes: Dict[str, float],
|
||||
format_type: str = 'table'):
|
||||
"""Print a formatted memory usage report."""
|
||||
if not components:
|
||||
print("No Modus components found running.")
|
||||
return
|
||||
|
||||
total_memory = sum(comp.memory_mb for comp in components.values())
|
||||
total_cpu = sum(comp.cpu_percent for comp in components.values())
|
||||
|
||||
if format_type == 'json':
|
||||
self._print_json_report(components, module_sizes, total_memory, total_cpu)
|
||||
else:
|
||||
self._print_table_report(components, module_sizes, total_memory, total_cpu)
|
||||
|
||||
def _print_table_report(self, components: Dict[str, ComponentMemory],
|
||||
module_sizes: Dict[str, float],
|
||||
total_memory: float, total_cpu: float):
|
||||
"""Print memory report in table format."""
|
||||
print("\n" + "="*80)
|
||||
print("MODUS MEMORY USAGE REPORT")
|
||||
print("="*80)
|
||||
|
||||
# Sort components by memory usage
|
||||
sorted_components = sorted(components.items(), key=lambda x: x[1].memory_mb, reverse=True)
|
||||
|
||||
print(f"\n{'Component':<25} {'Memory (MB)':<12} {'Memory %':<10} {'CPU %':<8} {'PID':<8} {'Status':<10}")
|
||||
print("-" * 80)
|
||||
|
||||
for name, comp in sorted_components:
|
||||
print(f"{name:<25} {comp.memory_mb:>8.2f} {comp.memory_percent:>6.2f}% "
|
||||
f"{comp.cpu_percent:>5.2f}% {comp.pid or 'N/A':<8} {comp.status:<10}")
|
||||
|
||||
if comp.children_count > 0:
|
||||
print(f" └─ Children: {comp.children_count} processes, "
|
||||
f"{comp.children_memory_mb:.2f} MB")
|
||||
|
||||
print("-" * 80)
|
||||
print(f"{'TOTAL':<25} {total_memory:>8.2f} {'-':<10} {total_cpu:>5.2f}%")
|
||||
|
||||
# Module file sizes
|
||||
if module_sizes:
|
||||
print(f"\n{'MODULE FILE SIZES':<25} {'Size (KB)':<12}")
|
||||
print("-" * 40)
|
||||
sorted_modules = sorted(module_sizes.items(), key=lambda x: x[1], reverse=True)
|
||||
for module, size in sorted_modules[:10]: # Top 10 largest files
|
||||
print(f"{module:<25} {size:>8.2f}")
|
||||
|
||||
print("\n" + "="*80)
|
||||
|
||||
def _print_json_report(self, components: Dict[str, ComponentMemory],
|
||||
module_sizes: Dict[str, float],
|
||||
total_memory: float, total_cpu: float):
|
||||
"""Print memory report in JSON format."""
|
||||
report = {
|
||||
'timestamp': time.time(),
|
||||
'total_memory_mb': round(total_memory, 2),
|
||||
'total_cpu_percent': round(total_cpu, 2),
|
||||
'components': {},
|
||||
'module_file_sizes_kb': module_sizes
|
||||
}
|
||||
|
||||
for name, comp in components.items():
|
||||
report['components'][name] = {
|
||||
'memory_mb': round(comp.memory_mb, 2),
|
||||
'memory_percent': round(comp.memory_percent, 2),
|
||||
'cpu_percent': round(comp.cpu_percent, 2),
|
||||
'pid': comp.pid,
|
||||
'status': comp.status,
|
||||
'children_count': comp.children_count,
|
||||
'children_memory_mb': round(comp.children_memory_mb, 2)
|
||||
}
|
||||
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
def monitor_continuously(self, interval: int = 5, format_type: str = 'table'):
|
||||
"""Monitor memory usage continuously."""
|
||||
try:
|
||||
while True:
|
||||
os.system('clear' if os.name == 'posix' else 'cls')
|
||||
components = self.get_component_memory_usage()
|
||||
module_sizes = self.get_module_file_sizes()
|
||||
self.print_memory_report(components, module_sizes, format_type)
|
||||
print(f"\nRefreshing every {interval} seconds... (Press Ctrl+C to stop)")
|
||||
time.sleep(interval)
|
||||
except KeyboardInterrupt:
|
||||
print("\nMonitoring stopped.")
|
||||
|
||||
def get_memory_summary(self) -> Dict[str, float]:
|
||||
"""Get a summary of memory usage by category."""
|
||||
components = self.get_component_memory_usage()
|
||||
|
||||
summary = {
|
||||
'main_components': 0.0,
|
||||
'dock_components': 0.0,
|
||||
'launcher_plugins': 0.0,
|
||||
'services': 0.0,
|
||||
'other': 0.0
|
||||
}
|
||||
|
||||
for name, comp in components.items():
|
||||
if name in ['main_process', 'dock', 'launcher', 'notification_popup', 'osd', 'switcher', 'corners']:
|
||||
summary['main_components'] += comp.memory_mb
|
||||
elif name.startswith('dock_'):
|
||||
summary['dock_components'] += comp.memory_mb
|
||||
elif name.startswith('plugin_'):
|
||||
summary['launcher_plugins'] += comp.memory_mb
|
||||
elif name.startswith('service_'):
|
||||
summary['services'] += comp.memory_mb
|
||||
else:
|
||||
summary['other'] += comp.memory_mb
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run the memory monitor."""
|
||||
parser = argparse.ArgumentParser(description='Monitor memory usage of Modus components')
|
||||
parser.add_argument('--format', choices=['table', 'json'], default='table',
|
||||
help='Output format (default: table)')
|
||||
parser.add_argument('--monitor', action='store_true',
|
||||
help='Monitor continuously')
|
||||
parser.add_argument('--interval', type=int, default=5,
|
||||
help='Refresh interval in seconds for continuous monitoring (default: 5)')
|
||||
parser.add_argument('--summary', action='store_true',
|
||||
help='Show only summary by category')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
monitor = ModusMemoryMonitor()
|
||||
|
||||
if args.monitor:
|
||||
monitor.monitor_continuously(args.interval, args.format)
|
||||
elif args.summary:
|
||||
summary = monitor.get_memory_summary()
|
||||
print("\nMODUS MEMORY SUMMARY BY CATEGORY")
|
||||
print("="*40)
|
||||
total = sum(summary.values())
|
||||
for category, memory in summary.items():
|
||||
percentage = (memory / total * 100) if total > 0 else 0
|
||||
print(f"{category.replace('_', ' ').title():<20} {memory:>8.2f} MB ({percentage:>5.1f}%)")
|
||||
print("-"*40)
|
||||
print(f"{'Total':<20} {total:>8.2f} MB")
|
||||
else:
|
||||
components = monitor.get_component_memory_usage()
|
||||
module_sizes = monitor.get_module_file_sizes()
|
||||
monitor.print_memory_report(components, module_sizes, args.format)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user