-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmemory_manager.py
More file actions
288 lines (235 loc) · 9.58 KB
/
memory_manager.py
File metadata and controls
288 lines (235 loc) · 9.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
import gc
import psutil
import time
import logging
from datetime import datetime
from typing import Dict, Optional, Any
class MemoryManager:
"""
Advanced memory management utility for Python applications.
Provides memory monitoring, garbage collection, and resource cleanup.
"""
def __init__(self, logger: Optional[logging.Logger] = None, process_name: str = "analyzeclaude"):
"""
Initialize the memory manager.
Args:
logger: Optional logger instance for output
process_name: Name for logging context
"""
self.logger = logger or logging.getLogger(__name__)
self.process_name = process_name
self.start_time = time.time()
self.process = psutil.Process()
self.initial_memory = self.get_memory_stats()
def get_memory_stats(self) -> Dict[str, float]:
"""
Get current memory usage statistics.
Returns:
Dictionary with memory stats in MB
"""
try:
memory_info = self.process.memory_info()
return {
'rss': round(memory_info.rss / 1024 / 1024, 2), # Resident Set Size
'vms': round(memory_info.vms / 1024 / 1024, 2), # Virtual Memory Size
'percent': round(self.process.memory_percent(), 2),
'available': round(psutil.virtual_memory().available / 1024 / 1024, 2),
'total': round(psutil.virtual_memory().total / 1024 / 1024, 2)
}
except Exception as e:
self.logger.error(f"Error getting memory stats: {e}")
return {'rss': 0, 'vms': 0, 'percent': 0, 'available': 0, 'total': 0}
def log_memory_usage(self, context: str = '') -> Dict[str, float]:
"""
Log current memory usage with optional context.
Args:
context: Optional context string for logging
Returns:
Current memory statistics
"""
stats = self.get_memory_stats()
runtime = round((time.time() - self.start_time), 1)
log_msg = f"Memory{f' ({context})' if context else ''}: "
log_msg += f"RSS {stats['rss']}MB | "
log_msg += f"VMS {stats['vms']}MB | "
log_msg += f"{stats['percent']}% of system | "
log_msg += f"Available {stats['available']}MB | "
log_msg += f"Runtime {runtime}s"
self.logger.info(log_msg)
return stats
def force_garbage_collection(self, context: str = '') -> Dict[str, Any]:
"""
Force garbage collection and measure impact.
Args:
context: Optional context for logging
Returns:
GC results including objects collected
"""
before_stats = self.get_memory_stats()
# Force full garbage collection
collected = gc.collect()
after_stats = self.get_memory_stats()
memory_freed = round(before_stats['rss'] - after_stats['rss'], 2)
log_msg = f"GC{f' ({context})' if context else ''}: "
log_msg += f"Collected {collected} objects"
if memory_freed > 0:
log_msg += f", freed {memory_freed}MB RSS"
elif memory_freed < 0:
log_msg += f", RSS increased by {abs(memory_freed)}MB"
else:
log_msg += ", no significant memory change"
self.logger.info(log_msg)
return {
'objects_collected': collected,
'memory_freed_mb': memory_freed,
'before_stats': before_stats,
'after_stats': after_stats
}
def monitor_memory_threshold(self, threshold_mb: int = 1000, context: str = '') -> bool:
"""
Monitor memory usage against a threshold and force GC if exceeded.
Args:
threshold_mb: Memory threshold in MB
context: Optional context for logging
Returns:
True if threshold was exceeded
"""
stats = self.get_memory_stats()
if stats['rss'] > threshold_mb:
self.logger.warning(
f"⚠️ Memory threshold exceeded{f' ({context})' if context else ''}: "
f"{stats['rss']}MB > {threshold_mb}MB"
)
self.force_garbage_collection(f"threshold-{threshold_mb}MB")
return True
return False
def get_memory_report(self) -> Dict[str, Any]:
"""
Generate comprehensive memory usage report.
Returns:
Detailed memory report
"""
current_stats = self.get_memory_stats()
runtime_seconds = round(time.time() - self.start_time, 1)
runtime_minutes = round(runtime_seconds / 60, 1)
memory_growth = {
'rss': round(current_stats['rss'] - self.initial_memory['rss'], 2),
'vms': round(current_stats['vms'] - self.initial_memory['vms'], 2)
}
# Get garbage collection stats
gc_stats = gc.get_stats()
return {
'process_name': self.process_name,
'current': current_stats,
'initial': self.initial_memory,
'runtime': {
'seconds': runtime_seconds,
'minutes': runtime_minutes
},
'memory_growth': memory_growth,
'gc_stats': {
'generations': len(gc_stats),
'total_collections': sum(stat['collections'] for stat in gc_stats),
'total_collected': sum(stat['collected'] for stat in gc_stats),
'total_uncollectable': sum(stat['uncollectable'] for stat in gc_stats)
},
'system': {
'cpu_percent': round(self.process.cpu_percent(), 2),
'num_threads': self.process.num_threads(),
'open_files': len(self.process.open_files()) if hasattr(self.process, 'open_files') else 'N/A'
}
}
def cleanup_resources(self, context: str = 'cleanup') -> None:
"""
Perform comprehensive resource cleanup.
Args:
context: Context for logging
"""
self.logger.info(f"Performing resource cleanup ({context})")
# Force garbage collection
self.force_garbage_collection(context)
# Clear any matplotlib figures (if using plotting)
try:
import matplotlib.pyplot as plt
plt.close('all')
self.logger.info("Closed all matplotlib figures")
except ImportError:
pass
# Clear NLTK cache if using NLTK
try:
import nltk
if hasattr(nltk.data, 'clear_cache'):
nltk.data.clear_cache()
self.logger.info("Cleared NLTK cache")
except (ImportError, AttributeError):
pass
def log_final_report(self) -> Dict[str, Any]:
"""
Log final memory usage report.
Returns:
Final memory report
"""
self.logger.info("📊 === FINAL MEMORY REPORT ===")
report = self.get_memory_report()
self.logger.info(
f"Final Memory Usage: {report['current']['rss']}MB RSS, "
f"{report['current']['vms']}MB VMS ({report['current']['percent']}% of system)"
)
self.logger.info(
f"Memory Growth: +{report['memory_growth']['rss']}MB RSS, "
f"+{report['memory_growth']['vms']}MB VMS"
)
self.logger.info(f"Total Runtime: {report['runtime']['minutes']} minutes")
self.logger.info(
f"GC Stats: {report['gc_stats']['total_collections']} collections, "
f"{report['gc_stats']['total_collected']} objects collected"
)
# Final cleanup
self.cleanup_resources('final')
return report
# Global memory manager instance
_global_memory_manager = None
def get_memory_manager(logger: Optional[logging.Logger] = None) -> MemoryManager:
"""
Get or create the global memory manager instance.
Args:
logger: Optional logger instance
Returns:
MemoryManager instance
"""
global _global_memory_manager
if _global_memory_manager is None:
_global_memory_manager = MemoryManager(logger)
return _global_memory_manager
def log_memory(context: str = '', logger: Optional[logging.Logger] = None) -> Dict[str, float]:
"""
Convenience function to log memory usage.
Args:
context: Context string for logging
logger: Optional logger instance
Returns:
Memory statistics
"""
return get_memory_manager(logger).log_memory_usage(context)
def force_gc(context: str = '', logger: Optional[logging.Logger] = None) -> Dict[str, Any]:
"""
Convenience function to force garbage collection.
Args:
context: Context string for logging
logger: Optional logger instance
Returns:
GC results
"""
return get_memory_manager(logger).force_garbage_collection(context)
def monitor_threshold(threshold_mb: int = 1000, context: str = '',
logger: Optional[logging.Logger] = None) -> bool:
"""
Convenience function to monitor memory threshold.
Args:
threshold_mb: Memory threshold in MB
context: Context string for logging
logger: Optional logger instance
Returns:
True if threshold was exceeded
"""
return get_memory_manager(logger).monitor_memory_threshold(threshold_mb, context)