sehawq.db 4.0.3 → 4.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +30 -30
- package/LICENSE +21 -21
- package/index.js +1 -1
- package/package.json +36 -36
- package/readme.md +413 -413
- package/src/core/Database.js +294 -294
- package/src/core/Events.js +285 -285
- package/src/core/IndexManager.js +813 -813
- package/src/core/Persistence.js +375 -375
- package/src/core/QueryEngine.js +447 -447
- package/src/core/Storage.js +321 -321
- package/src/core/Validator.js +324 -324
- package/src/index.js +115 -115
- package/src/performance/Cache.js +338 -338
- package/src/performance/LazyLoader.js +354 -354
- package/src/performance/MemoryManager.js +495 -495
- package/src/server/api.js +687 -687
- package/src/server/websocket.js +527 -527
- package/src/utils/benchmark.js +51 -51
- package/src/utils/dot-notation.js +247 -247
- package/src/utils/helpers.js +275 -275
- package/src/utils/profiler.js +70 -70
- package/src/version.js +37 -37
|
@@ -1,496 +1,496 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MemoryManager - Keeps your memory usage in check like a financial advisor 💰
|
|
3
|
-
*
|
|
4
|
-
* Monitors, optimizes, and prevents memory leaks
|
|
5
|
-
* Because crashing from out-of-memory is so 1990s 😅
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { performance } = require('perf_hooks');
|
|
9
|
-
|
|
10
|
-
class MemoryManager {
|
|
11
|
-
constructor(options = {}) {
|
|
12
|
-
this.options = {
|
|
13
|
-
maxMemoryMB: 100, // 100MB default limit
|
|
14
|
-
checkInterval: 30000, // Check every 30 seconds
|
|
15
|
-
gcAggressiveness: 'medium', // low, medium, high
|
|
16
|
-
leakDetection: true,
|
|
17
|
-
...options
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
this.components = new Map(); // Track memory by component
|
|
21
|
-
this.snapshots = []; // Memory usage history
|
|
22
|
-
this.alerts = []; // Memory alerts
|
|
23
|
-
|
|
24
|
-
this.stats = {
|
|
25
|
-
totalChecks: 0,
|
|
26
|
-
optimizations: 0,
|
|
27
|
-
leaksDetected: 0,
|
|
28
|
-
gcCalls: 0,
|
|
29
|
-
memorySaved: 0
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
this._startMonitoring();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Register a component for memory tracking
|
|
37
|
-
*/
|
|
38
|
-
registerComponent(name, component) {
|
|
39
|
-
this.components.set(name, {
|
|
40
|
-
instance: component,
|
|
41
|
-
baselineMemory: this._getComponentMemory(component),
|
|
42
|
-
lastCheck: Date.now(),
|
|
43
|
-
leaks: 0
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (this.options.debug) {
|
|
47
|
-
console.log(`📝 Registered component for memory tracking: ${name}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Start memory monitoring
|
|
53
|
-
*/
|
|
54
|
-
_startMonitoring() {
|
|
55
|
-
setInterval(() => {
|
|
56
|
-
this._performMemoryCheck();
|
|
57
|
-
}, this.options.checkInterval);
|
|
58
|
-
|
|
59
|
-
if (this.options.debug) {
|
|
60
|
-
console.log('🔍 Memory monitoring started');
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Perform comprehensive memory check
|
|
66
|
-
*/
|
|
67
|
-
_performMemoryCheck() {
|
|
68
|
-
this.stats.totalChecks++;
|
|
69
|
-
|
|
70
|
-
const memoryUsage = process.memoryUsage();
|
|
71
|
-
const currentMemoryMB = memoryUsage.heapUsed / 1024 / 1024;
|
|
72
|
-
|
|
73
|
-
// Take snapshot for trend analysis
|
|
74
|
-
this._takeMemorySnapshot(memoryUsage);
|
|
75
|
-
|
|
76
|
-
// Check for memory leaks
|
|
77
|
-
if (this.options.leakDetection) {
|
|
78
|
-
this._checkForLeaks();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Check if we're approaching memory limit
|
|
82
|
-
if (currentMemoryMB > this.options.maxMemoryMB * 0.8) {
|
|
83
|
-
this._handleHighMemoryUsage(currentMemoryMB);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Aggressive GC if configured
|
|
87
|
-
if (this.options.gcAggressiveness === 'high') {
|
|
88
|
-
this._forceGarbageCollection();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (this.options.debug) {
|
|
92
|
-
console.log(`🧠 Memory: ${currentMemoryMB.toFixed(2)}MB / ${this.options.maxMemoryMB}MB`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Take memory snapshot for trend analysis
|
|
98
|
-
*/
|
|
99
|
-
_takeMemorySnapshot(memoryUsage) {
|
|
100
|
-
const snapshot = {
|
|
101
|
-
timestamp: Date.now(),
|
|
102
|
-
heapUsed: memoryUsage.heapUsed,
|
|
103
|
-
heapTotal: memoryUsage.heapTotal,
|
|
104
|
-
external: memoryUsage.external,
|
|
105
|
-
rss: memoryUsage.rss,
|
|
106
|
-
components: {}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// Track memory by component
|
|
110
|
-
for (const [name, component] of this.components) {
|
|
111
|
-
snapshot.components[name] = this._getComponentMemory(component.instance);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
this.snapshots.push(snapshot);
|
|
115
|
-
|
|
116
|
-
// Keep only last 100 snapshots
|
|
117
|
-
if (this.snapshots.length > 100) {
|
|
118
|
-
this.snapshots.shift();
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Check for memory leaks in components
|
|
124
|
-
*/
|
|
125
|
-
_checkForLeaks() {
|
|
126
|
-
for (const [name, component] of this.components) {
|
|
127
|
-
const currentMemory = this._getComponentMemory(component.instance);
|
|
128
|
-
const memoryIncrease = currentMemory - component.baselineMemory;
|
|
129
|
-
|
|
130
|
-
// If memory increased by more than 50% since baseline, potential leak
|
|
131
|
-
if (memoryIncrease > component.baselineMemory * 0.5) {
|
|
132
|
-
component.leaks++;
|
|
133
|
-
this.stats.leaksDetected++;
|
|
134
|
-
|
|
135
|
-
this._alert('leak', `Potential memory leak in ${name}: +${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`);
|
|
136
|
-
|
|
137
|
-
if (this.options.debug) {
|
|
138
|
-
console.log(`🚨 Potential memory leak in ${name}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
component.lastCheck = Date.now();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Handle high memory usage situations
|
|
148
|
-
*/
|
|
149
|
-
_handleHighMemoryUsage(currentMemoryMB) {
|
|
150
|
-
const severity = currentMemoryMB > this.options.maxMemoryMB * 0.9 ? 'critical' : 'warning';
|
|
151
|
-
|
|
152
|
-
this._alert(severity,
|
|
153
|
-
`High memory usage: ${currentMemoryMB.toFixed(2)}MB / ${this.options.maxMemoryMB}MB`
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
// Trigger memory optimization
|
|
157
|
-
this.optimizeMemory(severity);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Optimize memory usage based on severity
|
|
162
|
-
*/
|
|
163
|
-
optimizeMemory(severity = 'medium') {
|
|
164
|
-
this.stats.optimizations++;
|
|
165
|
-
|
|
166
|
-
const strategies = {
|
|
167
|
-
low: ['clearCaches', 'mildGC'],
|
|
168
|
-
medium: ['clearCaches', 'aggressiveGC', 'unloadChunks'],
|
|
169
|
-
high: ['clearCaches', 'forceGC', 'unloadAllChunks', 'compressData'],
|
|
170
|
-
critical: ['emergencyMeasures']
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const strategy = strategies[severity] || strategies.medium;
|
|
174
|
-
|
|
175
|
-
if (this.options.debug) {
|
|
176
|
-
console.log(`🔄 Memory optimization (${severity}): ${strategy.join(', ')}`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
for (const action of strategy) {
|
|
180
|
-
this._executeOptimization(action);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
this._alert('info', `Memory optimization completed (${severity})`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Execute specific optimization action
|
|
188
|
-
*/
|
|
189
|
-
_executeOptimization(action) {
|
|
190
|
-
switch (action) {
|
|
191
|
-
case 'clearCaches':
|
|
192
|
-
this._clearAllCaches();
|
|
193
|
-
break;
|
|
194
|
-
case 'mildGC':
|
|
195
|
-
this._suggestGarbageCollection();
|
|
196
|
-
break;
|
|
197
|
-
case 'aggressiveGC':
|
|
198
|
-
this._forceGarbageCollection();
|
|
199
|
-
break;
|
|
200
|
-
case 'unloadChunks':
|
|
201
|
-
this._unloadNonCriticalChunks();
|
|
202
|
-
break;
|
|
203
|
-
case 'unloadAllChunks':
|
|
204
|
-
this._unloadAllChunks();
|
|
205
|
-
break;
|
|
206
|
-
case 'compressData':
|
|
207
|
-
this._compressMemoryData();
|
|
208
|
-
break;
|
|
209
|
-
case 'emergencyMeasures':
|
|
210
|
-
this._emergencyMemoryReduction();
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Clear all registered caches
|
|
217
|
-
*/
|
|
218
|
-
_clearAllCaches() {
|
|
219
|
-
for (const [name, component] of this.components) {
|
|
220
|
-
if (component.instance.clearCache) {
|
|
221
|
-
const before = this._getComponentMemory(component.instance);
|
|
222
|
-
component.instance.clearCache();
|
|
223
|
-
const after = this._getComponentMemory(component.instance);
|
|
224
|
-
|
|
225
|
-
this.stats.memorySaved += (before - after);
|
|
226
|
-
|
|
227
|
-
if (this.options.debug) {
|
|
228
|
-
console.log(`🧹 Cleared cache for ${name}: saved ${((before - after) / 1024 / 1024).toFixed(2)}MB`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Suggest garbage collection (if available)
|
|
236
|
-
*/
|
|
237
|
-
_suggestGarbageCollection() {
|
|
238
|
-
if (global.gc) {
|
|
239
|
-
const before = process.memoryUsage().heapUsed;
|
|
240
|
-
global.gc();
|
|
241
|
-
const after = process.memoryUsage().heapUsed;
|
|
242
|
-
|
|
243
|
-
this.stats.gcCalls++;
|
|
244
|
-
this.stats.memorySaved += (before - after);
|
|
245
|
-
|
|
246
|
-
if (this.options.debug) {
|
|
247
|
-
console.log(`♻️ GC freed ${((before - after) / 1024 / 1024).toFixed(2)}MB`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Force garbage collection more aggressively
|
|
254
|
-
*/
|
|
255
|
-
_forceGarbageCollection() {
|
|
256
|
-
if (global.gc) {
|
|
257
|
-
// Call multiple times for more aggressive collection
|
|
258
|
-
for (let i = 0; i < 3; i++) {
|
|
259
|
-
global.gc();
|
|
260
|
-
}
|
|
261
|
-
this.stats.gcCalls += 3;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Unload non-critical data chunks
|
|
267
|
-
*/
|
|
268
|
-
_unloadNonCriticalChunks() {
|
|
269
|
-
for (const [name, component] of this.components) {
|
|
270
|
-
if (component.instance.unloadChunks) {
|
|
271
|
-
component.instance.unloadChunks();
|
|
272
|
-
|
|
273
|
-
if (this.options.debug) {
|
|
274
|
-
console.log(`📦 Unloaded chunks for ${name}`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Unload all possible chunks
|
|
282
|
-
*/
|
|
283
|
-
_unloadAllChunks() {
|
|
284
|
-
for (const [name, component] of this.components) {
|
|
285
|
-
if (component.instance.clear) {
|
|
286
|
-
component.instance.clear();
|
|
287
|
-
|
|
288
|
-
if (this.options.debug) {
|
|
289
|
-
console.log(`🗑️ Cleared all data for ${name}`);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Compress in-memory data
|
|
297
|
-
*/
|
|
298
|
-
_compressMemoryData() {
|
|
299
|
-
// This would implement actual compression logic
|
|
300
|
-
// For now, it's a placeholder for future implementation
|
|
301
|
-
if (this.options.debug) {
|
|
302
|
-
console.log('🗜️ Memory compression would run here');
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Emergency measures for critical memory situations
|
|
308
|
-
*/
|
|
309
|
-
_emergencyMemoryReduction() {
|
|
310
|
-
this._clearAllCaches();
|
|
311
|
-
this._unloadAllChunks();
|
|
312
|
-
this._forceGarbageCollection();
|
|
313
|
-
|
|
314
|
-
// Reset component baselines
|
|
315
|
-
for (const [name, component] of this.components) {
|
|
316
|
-
component.baselineMemory = this._getComponentMemory(component.instance);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
this._alert('critical', 'Emergency memory reduction completed');
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Get memory usage for a component
|
|
324
|
-
*/
|
|
325
|
-
_getComponentMemory(component) {
|
|
326
|
-
// Try to get memory stats from component
|
|
327
|
-
if (component.getStats && component.getStats().memoryUsage) {
|
|
328
|
-
return component.getStats().memoryUsage;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Fallback: estimate from internal state
|
|
332
|
-
return this._estimateObjectSize(component);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Estimate memory size of an object
|
|
337
|
-
*/
|
|
338
|
-
_estimateObjectSize(obj) {
|
|
339
|
-
const seen = new WeakSet();
|
|
340
|
-
|
|
341
|
-
function sizeOf(obj) {
|
|
342
|
-
if (obj === null || obj === undefined) return 0;
|
|
343
|
-
if (seen.has(obj)) return 0;
|
|
344
|
-
|
|
345
|
-
seen.add(obj);
|
|
346
|
-
|
|
347
|
-
switch (typeof obj) {
|
|
348
|
-
case 'number':
|
|
349
|
-
return 8;
|
|
350
|
-
case 'string':
|
|
351
|
-
return obj.length * 2;
|
|
352
|
-
case 'boolean':
|
|
353
|
-
return 4;
|
|
354
|
-
case 'object':
|
|
355
|
-
if (Array.isArray(obj)) {
|
|
356
|
-
return obj.reduce((size, item) => size + sizeOf(item), 0);
|
|
357
|
-
} else if (obj instanceof Map) {
|
|
358
|
-
let size = 0;
|
|
359
|
-
for (const [key, value] of obj) {
|
|
360
|
-
size += sizeOf(key) + sizeOf(value);
|
|
361
|
-
}
|
|
362
|
-
return size;
|
|
363
|
-
} else if (obj instanceof Set) {
|
|
364
|
-
let size = 0;
|
|
365
|
-
for (const value of obj) {
|
|
366
|
-
size += sizeOf(value);
|
|
367
|
-
}
|
|
368
|
-
return size;
|
|
369
|
-
} else {
|
|
370
|
-
let size = 0;
|
|
371
|
-
for (const key in obj) {
|
|
372
|
-
if (obj.hasOwnProperty(key)) {
|
|
373
|
-
size += sizeOf(key) + sizeOf(obj[key]);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
return size;
|
|
377
|
-
}
|
|
378
|
-
default:
|
|
379
|
-
return 0;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return sizeOf(obj);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Create memory alert
|
|
388
|
-
*/
|
|
389
|
-
_alert(level, message) {
|
|
390
|
-
const alert = {
|
|
391
|
-
level,
|
|
392
|
-
message,
|
|
393
|
-
timestamp: Date.now(),
|
|
394
|
-
memoryUsage: process.memoryUsage().heapUsed
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
this.alerts.push(alert);
|
|
398
|
-
|
|
399
|
-
// Keep only last 50 alerts
|
|
400
|
-
if (this.alerts.length > 50) {
|
|
401
|
-
this.alerts.shift();
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Emit alert event if available
|
|
405
|
-
if (this.emit) {
|
|
406
|
-
this.emit('alert', alert);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Get memory usage trends
|
|
412
|
-
*/
|
|
413
|
-
getMemoryTrend() {
|
|
414
|
-
if (this.snapshots.length < 2) {
|
|
415
|
-
return 'insufficient_data';
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const recent = this.snapshots.slice(-5);
|
|
419
|
-
const oldest = recent[0].heapUsed;
|
|
420
|
-
const newest = recent[recent.length - 1].heapUsed;
|
|
421
|
-
|
|
422
|
-
const change = newest - oldest;
|
|
423
|
-
const percentageChange = (change / oldest) * 100;
|
|
424
|
-
|
|
425
|
-
if (percentageChange > 10) return 'increasing';
|
|
426
|
-
if (percentageChange < -10) return 'decreasing';
|
|
427
|
-
return 'stable';
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Get comprehensive memory report
|
|
432
|
-
*/
|
|
433
|
-
getReport() {
|
|
434
|
-
const currentMemory = process.memoryUsage();
|
|
435
|
-
const trend = this.getMemoryTrend();
|
|
436
|
-
|
|
437
|
-
return {
|
|
438
|
-
current: {
|
|
439
|
-
heapUsed: `${(currentMemory.heapUsed / 1024 / 1024).toFixed(2)}MB`,
|
|
440
|
-
heapTotal: `${(currentMemory.heapTotal / 1024 / 1024).toFixed(2)}MB`,
|
|
441
|
-
external: `${(currentMemory.external / 1024 / 1024).toFixed(2)}MB`,
|
|
442
|
-
rss: `${(currentMemory.rss / 1024 / 1024).toFixed(2)}MB`
|
|
443
|
-
},
|
|
444
|
-
limits: {
|
|
445
|
-
maxMemory: `${this.options.maxMemoryMB}MB`,
|
|
446
|
-
usagePercentage: `${((currentMemory.heapUsed / 1024 / 1024) / this.options.maxMemoryMB * 100).toFixed(1)}%`
|
|
447
|
-
},
|
|
448
|
-
trends: {
|
|
449
|
-
direction: trend,
|
|
450
|
-
snapshots: this.snapshots.length
|
|
451
|
-
},
|
|
452
|
-
components: Array.from(this.components.entries()).map(([name, component]) => ({
|
|
453
|
-
name,
|
|
454
|
-
memory: `${(this._getComponentMemory(component.instance) / 1024 / 1024).toFixed(2)}MB`,
|
|
455
|
-
leaks: component.leaks
|
|
456
|
-
})),
|
|
457
|
-
stats: this.stats,
|
|
458
|
-
recentAlerts: this.alerts.slice(-5)
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Set new memory limit
|
|
464
|
-
*/
|
|
465
|
-
setMemoryLimit(mb) {
|
|
466
|
-
this.options.maxMemoryMB = mb;
|
|
467
|
-
|
|
468
|
-
if (this.options.debug) {
|
|
469
|
-
console.log(`📏 Memory limit set to ${mb}MB`);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Get optimization suggestions
|
|
475
|
-
*/
|
|
476
|
-
getOptimizationSuggestions() {
|
|
477
|
-
const suggestions = [];
|
|
478
|
-
const currentMB = process.memoryUsage().heapUsed / 1024 / 1024;
|
|
479
|
-
|
|
480
|
-
if (currentMB > this.options.maxMemoryMB * 0.8) {
|
|
481
|
-
suggestions.push('Consider increasing maxMemoryMB or optimizing data structures');
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (this.stats.leaksDetected > 0) {
|
|
485
|
-
suggestions.push('Investigate potential memory leaks in registered components');
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (this.getMemoryTrend() === 'increasing') {
|
|
489
|
-
suggestions.push('Memory usage is trending upward - consider proactive optimization');
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return suggestions;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MemoryManager - Keeps your memory usage in check like a financial advisor 💰
|
|
3
|
+
*
|
|
4
|
+
* Monitors, optimizes, and prevents memory leaks
|
|
5
|
+
* Because crashing from out-of-memory is so 1990s 😅
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { performance } = require('perf_hooks');
|
|
9
|
+
|
|
10
|
+
class MemoryManager {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.options = {
|
|
13
|
+
maxMemoryMB: 100, // 100MB default limit
|
|
14
|
+
checkInterval: 30000, // Check every 30 seconds
|
|
15
|
+
gcAggressiveness: 'medium', // low, medium, high
|
|
16
|
+
leakDetection: true,
|
|
17
|
+
...options
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
this.components = new Map(); // Track memory by component
|
|
21
|
+
this.snapshots = []; // Memory usage history
|
|
22
|
+
this.alerts = []; // Memory alerts
|
|
23
|
+
|
|
24
|
+
this.stats = {
|
|
25
|
+
totalChecks: 0,
|
|
26
|
+
optimizations: 0,
|
|
27
|
+
leaksDetected: 0,
|
|
28
|
+
gcCalls: 0,
|
|
29
|
+
memorySaved: 0
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
this._startMonitoring();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Register a component for memory tracking
|
|
37
|
+
*/
|
|
38
|
+
registerComponent(name, component) {
|
|
39
|
+
this.components.set(name, {
|
|
40
|
+
instance: component,
|
|
41
|
+
baselineMemory: this._getComponentMemory(component),
|
|
42
|
+
lastCheck: Date.now(),
|
|
43
|
+
leaks: 0
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (this.options.debug) {
|
|
47
|
+
console.log(`📝 Registered component for memory tracking: ${name}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Start memory monitoring
|
|
53
|
+
*/
|
|
54
|
+
_startMonitoring() {
|
|
55
|
+
setInterval(() => {
|
|
56
|
+
this._performMemoryCheck();
|
|
57
|
+
}, this.options.checkInterval);
|
|
58
|
+
|
|
59
|
+
if (this.options.debug) {
|
|
60
|
+
console.log('🔍 Memory monitoring started');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Perform comprehensive memory check
|
|
66
|
+
*/
|
|
67
|
+
_performMemoryCheck() {
|
|
68
|
+
this.stats.totalChecks++;
|
|
69
|
+
|
|
70
|
+
const memoryUsage = process.memoryUsage();
|
|
71
|
+
const currentMemoryMB = memoryUsage.heapUsed / 1024 / 1024;
|
|
72
|
+
|
|
73
|
+
// Take snapshot for trend analysis
|
|
74
|
+
this._takeMemorySnapshot(memoryUsage);
|
|
75
|
+
|
|
76
|
+
// Check for memory leaks
|
|
77
|
+
if (this.options.leakDetection) {
|
|
78
|
+
this._checkForLeaks();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check if we're approaching memory limit
|
|
82
|
+
if (currentMemoryMB > this.options.maxMemoryMB * 0.8) {
|
|
83
|
+
this._handleHighMemoryUsage(currentMemoryMB);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Aggressive GC if configured
|
|
87
|
+
if (this.options.gcAggressiveness === 'high') {
|
|
88
|
+
this._forceGarbageCollection();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.options.debug) {
|
|
92
|
+
console.log(`🧠 Memory: ${currentMemoryMB.toFixed(2)}MB / ${this.options.maxMemoryMB}MB`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Take memory snapshot for trend analysis
|
|
98
|
+
*/
|
|
99
|
+
_takeMemorySnapshot(memoryUsage) {
|
|
100
|
+
const snapshot = {
|
|
101
|
+
timestamp: Date.now(),
|
|
102
|
+
heapUsed: memoryUsage.heapUsed,
|
|
103
|
+
heapTotal: memoryUsage.heapTotal,
|
|
104
|
+
external: memoryUsage.external,
|
|
105
|
+
rss: memoryUsage.rss,
|
|
106
|
+
components: {}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Track memory by component
|
|
110
|
+
for (const [name, component] of this.components) {
|
|
111
|
+
snapshot.components[name] = this._getComponentMemory(component.instance);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.snapshots.push(snapshot);
|
|
115
|
+
|
|
116
|
+
// Keep only last 100 snapshots
|
|
117
|
+
if (this.snapshots.length > 100) {
|
|
118
|
+
this.snapshots.shift();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check for memory leaks in components
|
|
124
|
+
*/
|
|
125
|
+
_checkForLeaks() {
|
|
126
|
+
for (const [name, component] of this.components) {
|
|
127
|
+
const currentMemory = this._getComponentMemory(component.instance);
|
|
128
|
+
const memoryIncrease = currentMemory - component.baselineMemory;
|
|
129
|
+
|
|
130
|
+
// If memory increased by more than 50% since baseline, potential leak
|
|
131
|
+
if (memoryIncrease > component.baselineMemory * 0.5) {
|
|
132
|
+
component.leaks++;
|
|
133
|
+
this.stats.leaksDetected++;
|
|
134
|
+
|
|
135
|
+
this._alert('leak', `Potential memory leak in ${name}: +${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`);
|
|
136
|
+
|
|
137
|
+
if (this.options.debug) {
|
|
138
|
+
console.log(`🚨 Potential memory leak in ${name}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
component.lastCheck = Date.now();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handle high memory usage situations
|
|
148
|
+
*/
|
|
149
|
+
_handleHighMemoryUsage(currentMemoryMB) {
|
|
150
|
+
const severity = currentMemoryMB > this.options.maxMemoryMB * 0.9 ? 'critical' : 'warning';
|
|
151
|
+
|
|
152
|
+
this._alert(severity,
|
|
153
|
+
`High memory usage: ${currentMemoryMB.toFixed(2)}MB / ${this.options.maxMemoryMB}MB`
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Trigger memory optimization
|
|
157
|
+
this.optimizeMemory(severity);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Optimize memory usage based on severity
|
|
162
|
+
*/
|
|
163
|
+
optimizeMemory(severity = 'medium') {
|
|
164
|
+
this.stats.optimizations++;
|
|
165
|
+
|
|
166
|
+
const strategies = {
|
|
167
|
+
low: ['clearCaches', 'mildGC'],
|
|
168
|
+
medium: ['clearCaches', 'aggressiveGC', 'unloadChunks'],
|
|
169
|
+
high: ['clearCaches', 'forceGC', 'unloadAllChunks', 'compressData'],
|
|
170
|
+
critical: ['emergencyMeasures']
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const strategy = strategies[severity] || strategies.medium;
|
|
174
|
+
|
|
175
|
+
if (this.options.debug) {
|
|
176
|
+
console.log(`🔄 Memory optimization (${severity}): ${strategy.join(', ')}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (const action of strategy) {
|
|
180
|
+
this._executeOptimization(action);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this._alert('info', `Memory optimization completed (${severity})`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Execute specific optimization action
|
|
188
|
+
*/
|
|
189
|
+
_executeOptimization(action) {
|
|
190
|
+
switch (action) {
|
|
191
|
+
case 'clearCaches':
|
|
192
|
+
this._clearAllCaches();
|
|
193
|
+
break;
|
|
194
|
+
case 'mildGC':
|
|
195
|
+
this._suggestGarbageCollection();
|
|
196
|
+
break;
|
|
197
|
+
case 'aggressiveGC':
|
|
198
|
+
this._forceGarbageCollection();
|
|
199
|
+
break;
|
|
200
|
+
case 'unloadChunks':
|
|
201
|
+
this._unloadNonCriticalChunks();
|
|
202
|
+
break;
|
|
203
|
+
case 'unloadAllChunks':
|
|
204
|
+
this._unloadAllChunks();
|
|
205
|
+
break;
|
|
206
|
+
case 'compressData':
|
|
207
|
+
this._compressMemoryData();
|
|
208
|
+
break;
|
|
209
|
+
case 'emergencyMeasures':
|
|
210
|
+
this._emergencyMemoryReduction();
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Clear all registered caches
|
|
217
|
+
*/
|
|
218
|
+
_clearAllCaches() {
|
|
219
|
+
for (const [name, component] of this.components) {
|
|
220
|
+
if (component.instance.clearCache) {
|
|
221
|
+
const before = this._getComponentMemory(component.instance);
|
|
222
|
+
component.instance.clearCache();
|
|
223
|
+
const after = this._getComponentMemory(component.instance);
|
|
224
|
+
|
|
225
|
+
this.stats.memorySaved += (before - after);
|
|
226
|
+
|
|
227
|
+
if (this.options.debug) {
|
|
228
|
+
console.log(`🧹 Cleared cache for ${name}: saved ${((before - after) / 1024 / 1024).toFixed(2)}MB`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Suggest garbage collection (if available)
|
|
236
|
+
*/
|
|
237
|
+
_suggestGarbageCollection() {
|
|
238
|
+
if (global.gc) {
|
|
239
|
+
const before = process.memoryUsage().heapUsed;
|
|
240
|
+
global.gc();
|
|
241
|
+
const after = process.memoryUsage().heapUsed;
|
|
242
|
+
|
|
243
|
+
this.stats.gcCalls++;
|
|
244
|
+
this.stats.memorySaved += (before - after);
|
|
245
|
+
|
|
246
|
+
if (this.options.debug) {
|
|
247
|
+
console.log(`♻️ GC freed ${((before - after) / 1024 / 1024).toFixed(2)}MB`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Force garbage collection more aggressively
|
|
254
|
+
*/
|
|
255
|
+
_forceGarbageCollection() {
|
|
256
|
+
if (global.gc) {
|
|
257
|
+
// Call multiple times for more aggressive collection
|
|
258
|
+
for (let i = 0; i < 3; i++) {
|
|
259
|
+
global.gc();
|
|
260
|
+
}
|
|
261
|
+
this.stats.gcCalls += 3;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Unload non-critical data chunks
|
|
267
|
+
*/
|
|
268
|
+
_unloadNonCriticalChunks() {
|
|
269
|
+
for (const [name, component] of this.components) {
|
|
270
|
+
if (component.instance.unloadChunks) {
|
|
271
|
+
component.instance.unloadChunks();
|
|
272
|
+
|
|
273
|
+
if (this.options.debug) {
|
|
274
|
+
console.log(`📦 Unloaded chunks for ${name}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Unload all possible chunks
|
|
282
|
+
*/
|
|
283
|
+
_unloadAllChunks() {
|
|
284
|
+
for (const [name, component] of this.components) {
|
|
285
|
+
if (component.instance.clear) {
|
|
286
|
+
component.instance.clear();
|
|
287
|
+
|
|
288
|
+
if (this.options.debug) {
|
|
289
|
+
console.log(`🗑️ Cleared all data for ${name}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Compress in-memory data
|
|
297
|
+
*/
|
|
298
|
+
_compressMemoryData() {
|
|
299
|
+
// This would implement actual compression logic
|
|
300
|
+
// For now, it's a placeholder for future implementation
|
|
301
|
+
if (this.options.debug) {
|
|
302
|
+
console.log('🗜️ Memory compression would run here');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Emergency measures for critical memory situations
|
|
308
|
+
*/
|
|
309
|
+
_emergencyMemoryReduction() {
|
|
310
|
+
this._clearAllCaches();
|
|
311
|
+
this._unloadAllChunks();
|
|
312
|
+
this._forceGarbageCollection();
|
|
313
|
+
|
|
314
|
+
// Reset component baselines
|
|
315
|
+
for (const [name, component] of this.components) {
|
|
316
|
+
component.baselineMemory = this._getComponentMemory(component.instance);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
this._alert('critical', 'Emergency memory reduction completed');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get memory usage for a component
|
|
324
|
+
*/
|
|
325
|
+
_getComponentMemory(component) {
|
|
326
|
+
// Try to get memory stats from component
|
|
327
|
+
if (component.getStats && component.getStats().memoryUsage) {
|
|
328
|
+
return component.getStats().memoryUsage;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Fallback: estimate from internal state
|
|
332
|
+
return this._estimateObjectSize(component);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Estimate memory size of an object
|
|
337
|
+
*/
|
|
338
|
+
_estimateObjectSize(obj) {
|
|
339
|
+
const seen = new WeakSet();
|
|
340
|
+
|
|
341
|
+
function sizeOf(obj) {
|
|
342
|
+
if (obj === null || obj === undefined) return 0;
|
|
343
|
+
if (seen.has(obj)) return 0;
|
|
344
|
+
|
|
345
|
+
seen.add(obj);
|
|
346
|
+
|
|
347
|
+
switch (typeof obj) {
|
|
348
|
+
case 'number':
|
|
349
|
+
return 8;
|
|
350
|
+
case 'string':
|
|
351
|
+
return obj.length * 2;
|
|
352
|
+
case 'boolean':
|
|
353
|
+
return 4;
|
|
354
|
+
case 'object':
|
|
355
|
+
if (Array.isArray(obj)) {
|
|
356
|
+
return obj.reduce((size, item) => size + sizeOf(item), 0);
|
|
357
|
+
} else if (obj instanceof Map) {
|
|
358
|
+
let size = 0;
|
|
359
|
+
for (const [key, value] of obj) {
|
|
360
|
+
size += sizeOf(key) + sizeOf(value);
|
|
361
|
+
}
|
|
362
|
+
return size;
|
|
363
|
+
} else if (obj instanceof Set) {
|
|
364
|
+
let size = 0;
|
|
365
|
+
for (const value of obj) {
|
|
366
|
+
size += sizeOf(value);
|
|
367
|
+
}
|
|
368
|
+
return size;
|
|
369
|
+
} else {
|
|
370
|
+
let size = 0;
|
|
371
|
+
for (const key in obj) {
|
|
372
|
+
if (obj.hasOwnProperty(key)) {
|
|
373
|
+
size += sizeOf(key) + sizeOf(obj[key]);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return size;
|
|
377
|
+
}
|
|
378
|
+
default:
|
|
379
|
+
return 0;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return sizeOf(obj);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Create memory alert
|
|
388
|
+
*/
|
|
389
|
+
_alert(level, message) {
|
|
390
|
+
const alert = {
|
|
391
|
+
level,
|
|
392
|
+
message,
|
|
393
|
+
timestamp: Date.now(),
|
|
394
|
+
memoryUsage: process.memoryUsage().heapUsed
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
this.alerts.push(alert);
|
|
398
|
+
|
|
399
|
+
// Keep only last 50 alerts
|
|
400
|
+
if (this.alerts.length > 50) {
|
|
401
|
+
this.alerts.shift();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Emit alert event if available
|
|
405
|
+
if (this.emit) {
|
|
406
|
+
this.emit('alert', alert);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Get memory usage trends
|
|
412
|
+
*/
|
|
413
|
+
getMemoryTrend() {
|
|
414
|
+
if (this.snapshots.length < 2) {
|
|
415
|
+
return 'insufficient_data';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const recent = this.snapshots.slice(-5);
|
|
419
|
+
const oldest = recent[0].heapUsed;
|
|
420
|
+
const newest = recent[recent.length - 1].heapUsed;
|
|
421
|
+
|
|
422
|
+
const change = newest - oldest;
|
|
423
|
+
const percentageChange = (change / oldest) * 100;
|
|
424
|
+
|
|
425
|
+
if (percentageChange > 10) return 'increasing';
|
|
426
|
+
if (percentageChange < -10) return 'decreasing';
|
|
427
|
+
return 'stable';
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Get comprehensive memory report
|
|
432
|
+
*/
|
|
433
|
+
getReport() {
|
|
434
|
+
const currentMemory = process.memoryUsage();
|
|
435
|
+
const trend = this.getMemoryTrend();
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
current: {
|
|
439
|
+
heapUsed: `${(currentMemory.heapUsed / 1024 / 1024).toFixed(2)}MB`,
|
|
440
|
+
heapTotal: `${(currentMemory.heapTotal / 1024 / 1024).toFixed(2)}MB`,
|
|
441
|
+
external: `${(currentMemory.external / 1024 / 1024).toFixed(2)}MB`,
|
|
442
|
+
rss: `${(currentMemory.rss / 1024 / 1024).toFixed(2)}MB`
|
|
443
|
+
},
|
|
444
|
+
limits: {
|
|
445
|
+
maxMemory: `${this.options.maxMemoryMB}MB`,
|
|
446
|
+
usagePercentage: `${((currentMemory.heapUsed / 1024 / 1024) / this.options.maxMemoryMB * 100).toFixed(1)}%`
|
|
447
|
+
},
|
|
448
|
+
trends: {
|
|
449
|
+
direction: trend,
|
|
450
|
+
snapshots: this.snapshots.length
|
|
451
|
+
},
|
|
452
|
+
components: Array.from(this.components.entries()).map(([name, component]) => ({
|
|
453
|
+
name,
|
|
454
|
+
memory: `${(this._getComponentMemory(component.instance) / 1024 / 1024).toFixed(2)}MB`,
|
|
455
|
+
leaks: component.leaks
|
|
456
|
+
})),
|
|
457
|
+
stats: this.stats,
|
|
458
|
+
recentAlerts: this.alerts.slice(-5)
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Set new memory limit
|
|
464
|
+
*/
|
|
465
|
+
setMemoryLimit(mb) {
|
|
466
|
+
this.options.maxMemoryMB = mb;
|
|
467
|
+
|
|
468
|
+
if (this.options.debug) {
|
|
469
|
+
console.log(`📏 Memory limit set to ${mb}MB`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Get optimization suggestions
|
|
475
|
+
*/
|
|
476
|
+
getOptimizationSuggestions() {
|
|
477
|
+
const suggestions = [];
|
|
478
|
+
const currentMB = process.memoryUsage().heapUsed / 1024 / 1024;
|
|
479
|
+
|
|
480
|
+
if (currentMB > this.options.maxMemoryMB * 0.8) {
|
|
481
|
+
suggestions.push('Consider increasing maxMemoryMB or optimizing data structures');
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (this.stats.leaksDetected > 0) {
|
|
485
|
+
suggestions.push('Investigate potential memory leaks in registered components');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (this.getMemoryTrend() === 'increasing') {
|
|
489
|
+
suggestions.push('Memory usage is trending upward - consider proactive optimization');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return suggestions;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
496
|
module.exports = MemoryManager;
|