mastercontroller 1.2.12 → 1.2.14
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/.claude/settings.local.json +12 -0
- package/MasterAction.js +297 -73
- package/MasterControl.js +112 -19
- package/MasterHtml.js +101 -14
- package/MasterRouter.js +281 -66
- package/MasterTemplate.js +96 -3
- package/README.md +0 -44
- package/error/ErrorBoundary.js +353 -0
- package/error/HydrationMismatch.js +265 -0
- package/error/MasterBackendErrorHandler.js +769 -0
- package/{MasterError.js → error/MasterError.js} +2 -2
- package/error/MasterErrorHandler.js +487 -0
- package/error/MasterErrorLogger.js +360 -0
- package/error/MasterErrorMiddleware.js +407 -0
- package/error/SSRErrorHandler.js +273 -0
- package/monitoring/MasterCache.js +400 -0
- package/monitoring/MasterMemoryMonitor.js +188 -0
- package/monitoring/MasterProfiler.js +409 -0
- package/monitoring/PerformanceMonitor.js +233 -0
- package/package.json +3 -3
- package/security/CSPConfig.js +319 -0
- package/security/EventHandlerValidator.js +464 -0
- package/security/MasterSanitizer.js +429 -0
- package/security/MasterValidator.js +546 -0
- package/security/SecurityMiddleware.js +486 -0
- package/security/SessionSecurity.js +416 -0
- package/ssr/hydration-client.js +93 -0
- package/ssr/runtime-ssr.cjs +553 -0
- package/ssr/ssr-shims.js +73 -0
- package/examples/FileServingExample.js +0 -88
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
// version 1.0.1
|
|
2
|
+
// MasterController Cache System - Runtime Performance Optimization
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Multi-level cache system for MasterController
|
|
6
|
+
* - Event manifest caching
|
|
7
|
+
* - Component render caching
|
|
8
|
+
* - Template caching
|
|
9
|
+
* - LRU eviction
|
|
10
|
+
* - TTL support
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { logger } = require('../error/MasterErrorLogger');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* LRU Cache with TTL support
|
|
17
|
+
*/
|
|
18
|
+
class LRUCache {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.maxSize = options.maxSize || 100;
|
|
21
|
+
this.ttl = options.ttl || 3600000; // 1 hour default
|
|
22
|
+
this.cache = new Map();
|
|
23
|
+
this.accessOrder = [];
|
|
24
|
+
|
|
25
|
+
// Statistics
|
|
26
|
+
this.hits = 0;
|
|
27
|
+
this.misses = 0;
|
|
28
|
+
this.evictions = 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get value from cache
|
|
33
|
+
*/
|
|
34
|
+
get(key) {
|
|
35
|
+
const entry = this.cache.get(key);
|
|
36
|
+
|
|
37
|
+
if (!entry) {
|
|
38
|
+
this.misses++;
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check TTL
|
|
43
|
+
if (Date.now() > entry.expiry) {
|
|
44
|
+
this.cache.delete(key);
|
|
45
|
+
this.accessOrder = this.accessOrder.filter(k => k !== key);
|
|
46
|
+
this.misses++;
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Update access order (move to end)
|
|
51
|
+
this.accessOrder = this.accessOrder.filter(k => k !== key);
|
|
52
|
+
this.accessOrder.push(key);
|
|
53
|
+
|
|
54
|
+
this.hits++;
|
|
55
|
+
return entry.value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Set value in cache
|
|
60
|
+
*/
|
|
61
|
+
set(key, value, ttl = null) {
|
|
62
|
+
// Remove if already exists
|
|
63
|
+
if (this.cache.has(key)) {
|
|
64
|
+
this.accessOrder = this.accessOrder.filter(k => k !== key);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Evict if at capacity
|
|
68
|
+
if (this.cache.size >= this.maxSize) {
|
|
69
|
+
const oldestKey = this.accessOrder.shift();
|
|
70
|
+
this.cache.delete(oldestKey);
|
|
71
|
+
this.evictions++;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add new entry
|
|
75
|
+
this.cache.set(key, {
|
|
76
|
+
value,
|
|
77
|
+
expiry: Date.now() + (ttl || this.ttl)
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this.accessOrder.push(key);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if key exists
|
|
85
|
+
*/
|
|
86
|
+
has(key) {
|
|
87
|
+
return this.get(key) !== null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Delete entry
|
|
92
|
+
*/
|
|
93
|
+
delete(key) {
|
|
94
|
+
this.cache.delete(key);
|
|
95
|
+
this.accessOrder = this.accessOrder.filter(k => k !== key);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Clear cache
|
|
100
|
+
*/
|
|
101
|
+
clear() {
|
|
102
|
+
this.cache.clear();
|
|
103
|
+
this.accessOrder = [];
|
|
104
|
+
this.hits = 0;
|
|
105
|
+
this.misses = 0;
|
|
106
|
+
this.evictions = 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get cache statistics
|
|
111
|
+
*/
|
|
112
|
+
getStats() {
|
|
113
|
+
const total = this.hits + this.misses;
|
|
114
|
+
const hitRate = total > 0 ? (this.hits / total * 100).toFixed(2) : 0;
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
size: this.cache.size,
|
|
118
|
+
maxSize: this.maxSize,
|
|
119
|
+
hits: this.hits,
|
|
120
|
+
misses: this.misses,
|
|
121
|
+
evictions: this.evictions,
|
|
122
|
+
hitRate: `${hitRate}%`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* MasterController Cache Manager
|
|
129
|
+
*/
|
|
130
|
+
class MasterCache {
|
|
131
|
+
constructor(options = {}) {
|
|
132
|
+
// Event manifest cache
|
|
133
|
+
this.manifestCache = new LRUCache({
|
|
134
|
+
maxSize: options.manifestCacheSize || 50,
|
|
135
|
+
ttl: options.manifestTTL || 3600000 // 1 hour
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Component render cache
|
|
139
|
+
this.renderCache = new LRUCache({
|
|
140
|
+
maxSize: options.renderCacheSize || 200,
|
|
141
|
+
ttl: options.renderTTL || 300000 // 5 minutes
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Template cache
|
|
145
|
+
this.templateCache = new LRUCache({
|
|
146
|
+
maxSize: options.templateCacheSize || 100,
|
|
147
|
+
ttl: options.templateTTL || 3600000 // 1 hour
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Module cache (for require/import)
|
|
151
|
+
this.moduleCache = new Map();
|
|
152
|
+
|
|
153
|
+
// Enabled flag
|
|
154
|
+
this.enabled = options.enabled !== false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Cache event manifest
|
|
159
|
+
*/
|
|
160
|
+
cacheManifest(componentName, manifest) {
|
|
161
|
+
if (!this.enabled) return;
|
|
162
|
+
|
|
163
|
+
const key = `manifest:${componentName}`;
|
|
164
|
+
this.manifestCache.set(key, manifest);
|
|
165
|
+
|
|
166
|
+
logger.debug({
|
|
167
|
+
code: 'MC_CACHE_MANIFEST',
|
|
168
|
+
message: `Cached manifest for ${componentName}`,
|
|
169
|
+
size: JSON.stringify(manifest).length
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get cached event manifest
|
|
175
|
+
*/
|
|
176
|
+
getManifest(componentName) {
|
|
177
|
+
if (!this.enabled) return null;
|
|
178
|
+
|
|
179
|
+
const key = `manifest:${componentName}`;
|
|
180
|
+
return this.manifestCache.get(key);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Cache component render output
|
|
185
|
+
*/
|
|
186
|
+
cacheRender(componentName, props, html) {
|
|
187
|
+
if (!this.enabled) return;
|
|
188
|
+
|
|
189
|
+
// Create cache key from component name and props
|
|
190
|
+
const propsKey = JSON.stringify(props || {});
|
|
191
|
+
const key = `render:${componentName}:${this.hashString(propsKey)}`;
|
|
192
|
+
|
|
193
|
+
this.renderCache.set(key, html);
|
|
194
|
+
|
|
195
|
+
logger.debug({
|
|
196
|
+
code: 'MC_CACHE_RENDER',
|
|
197
|
+
message: `Cached render for ${componentName}`,
|
|
198
|
+
size: html.length
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get cached component render
|
|
204
|
+
*/
|
|
205
|
+
getCachedRender(componentName, props) {
|
|
206
|
+
if (!this.enabled) return null;
|
|
207
|
+
|
|
208
|
+
const propsKey = JSON.stringify(props || {});
|
|
209
|
+
const key = `render:${componentName}:${this.hashString(propsKey)}`;
|
|
210
|
+
|
|
211
|
+
return this.renderCache.get(key);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Cache template
|
|
216
|
+
*/
|
|
217
|
+
cacheTemplate(templatePath, compiled) {
|
|
218
|
+
if (!this.enabled) return;
|
|
219
|
+
|
|
220
|
+
const key = `template:${templatePath}`;
|
|
221
|
+
this.templateCache.set(key, compiled);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get cached template
|
|
226
|
+
*/
|
|
227
|
+
getTemplate(templatePath) {
|
|
228
|
+
if (!this.enabled) return null;
|
|
229
|
+
|
|
230
|
+
const key = `template:${templatePath}`;
|
|
231
|
+
return this.templateCache.get(key);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Cache module (require/import result)
|
|
236
|
+
*/
|
|
237
|
+
cacheModule(modulePath, exports) {
|
|
238
|
+
if (!this.enabled) return;
|
|
239
|
+
|
|
240
|
+
this.moduleCache.set(modulePath, exports);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get cached module
|
|
245
|
+
*/
|
|
246
|
+
getModule(modulePath) {
|
|
247
|
+
if (!this.enabled) return null;
|
|
248
|
+
|
|
249
|
+
return this.moduleCache.get(modulePath);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Invalidate cache for component
|
|
254
|
+
*/
|
|
255
|
+
invalidateComponent(componentName) {
|
|
256
|
+
// Clear manifest
|
|
257
|
+
const manifestKey = `manifest:${componentName}`;
|
|
258
|
+
this.manifestCache.delete(manifestKey);
|
|
259
|
+
|
|
260
|
+
// Clear all renders for this component
|
|
261
|
+
// (We'd need to track which keys belong to which components for this)
|
|
262
|
+
// For now, just clear the entire render cache
|
|
263
|
+
this.renderCache.clear();
|
|
264
|
+
|
|
265
|
+
logger.info({
|
|
266
|
+
code: 'MC_CACHE_INVALIDATE',
|
|
267
|
+
message: `Cache invalidated for ${componentName}`
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Clear all caches
|
|
273
|
+
*/
|
|
274
|
+
clearAll() {
|
|
275
|
+
this.manifestCache.clear();
|
|
276
|
+
this.renderCache.clear();
|
|
277
|
+
this.templateCache.clear();
|
|
278
|
+
this.moduleCache.clear();
|
|
279
|
+
|
|
280
|
+
logger.info({
|
|
281
|
+
code: 'MC_CACHE_CLEAR',
|
|
282
|
+
message: 'All caches cleared'
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get cache statistics
|
|
288
|
+
*/
|
|
289
|
+
getStats() {
|
|
290
|
+
return {
|
|
291
|
+
manifest: this.manifestCache.getStats(),
|
|
292
|
+
render: this.renderCache.getStats(),
|
|
293
|
+
template: this.templateCache.getStats(),
|
|
294
|
+
module: {
|
|
295
|
+
size: this.moduleCache.size
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Log cache statistics
|
|
302
|
+
*/
|
|
303
|
+
logStats() {
|
|
304
|
+
const stats = this.getStats();
|
|
305
|
+
|
|
306
|
+
console.log('\n═══════════════════════════════════════════════════');
|
|
307
|
+
console.log('📊 MasterController Cache Statistics');
|
|
308
|
+
console.log('═══════════════════════════════════════════════════');
|
|
309
|
+
|
|
310
|
+
console.log('\nManifest Cache:');
|
|
311
|
+
console.log(` Size: ${stats.manifest.size}/${stats.manifest.maxSize}`);
|
|
312
|
+
console.log(` Hits: ${stats.manifest.hits}`);
|
|
313
|
+
console.log(` Misses: ${stats.manifest.misses}`);
|
|
314
|
+
console.log(` Hit Rate: ${stats.manifest.hitRate}`);
|
|
315
|
+
console.log(` Evictions: ${stats.manifest.evictions}`);
|
|
316
|
+
|
|
317
|
+
console.log('\nRender Cache:');
|
|
318
|
+
console.log(` Size: ${stats.render.size}/${stats.render.maxSize}`);
|
|
319
|
+
console.log(` Hits: ${stats.render.hits}`);
|
|
320
|
+
console.log(` Misses: ${stats.render.misses}`);
|
|
321
|
+
console.log(` Hit Rate: ${stats.render.hitRate}`);
|
|
322
|
+
console.log(` Evictions: ${stats.render.evictions}`);
|
|
323
|
+
|
|
324
|
+
console.log('\nTemplate Cache:');
|
|
325
|
+
console.log(` Size: ${stats.template.size}/${stats.template.maxSize}`);
|
|
326
|
+
console.log(` Hits: ${stats.template.hits}`);
|
|
327
|
+
console.log(` Misses: ${stats.template.misses}`);
|
|
328
|
+
console.log(` Hit Rate: ${stats.template.hitRate}`);
|
|
329
|
+
console.log(` Evictions: ${stats.template.evictions}`);
|
|
330
|
+
|
|
331
|
+
console.log('\nModule Cache:');
|
|
332
|
+
console.log(` Size: ${stats.module.size}`);
|
|
333
|
+
|
|
334
|
+
console.log('═══════════════════════════════════════════════════\n');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Simple string hash function
|
|
339
|
+
*/
|
|
340
|
+
hashString(str) {
|
|
341
|
+
let hash = 0;
|
|
342
|
+
for (let i = 0; i < str.length; i++) {
|
|
343
|
+
const char = str.charCodeAt(i);
|
|
344
|
+
hash = ((hash << 5) - hash) + char;
|
|
345
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
346
|
+
}
|
|
347
|
+
return hash.toString(36);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Enable cache
|
|
352
|
+
*/
|
|
353
|
+
enable() {
|
|
354
|
+
this.enabled = true;
|
|
355
|
+
logger.info({
|
|
356
|
+
code: 'MC_CACHE_ENABLED',
|
|
357
|
+
message: 'Cache enabled'
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Disable cache
|
|
363
|
+
*/
|
|
364
|
+
disable() {
|
|
365
|
+
this.enabled = false;
|
|
366
|
+
logger.info({
|
|
367
|
+
code: 'MC_CACHE_DISABLED',
|
|
368
|
+
message: 'Cache disabled'
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Create singleton instance
|
|
374
|
+
const cache = new MasterCache({
|
|
375
|
+
manifestCacheSize: 50,
|
|
376
|
+
renderCacheSize: 200,
|
|
377
|
+
templateCacheSize: 100,
|
|
378
|
+
manifestTTL: 3600000, // 1 hour
|
|
379
|
+
renderTTL: 300000, // 5 minutes
|
|
380
|
+
templateTTL: 3600000, // 1 hour
|
|
381
|
+
enabled: process.env.NODE_ENV === 'production' || process.env.MC_CACHE_ENABLED === 'true'
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Auto-cleanup interval (every 5 minutes)
|
|
385
|
+
setInterval(() => {
|
|
386
|
+
// Force garbage collection of expired entries
|
|
387
|
+
const stats = cache.getStats();
|
|
388
|
+
|
|
389
|
+
logger.debug({
|
|
390
|
+
code: 'MC_CACHE_CLEANUP',
|
|
391
|
+
message: 'Cache cleanup running',
|
|
392
|
+
stats
|
|
393
|
+
});
|
|
394
|
+
}, 300000);
|
|
395
|
+
|
|
396
|
+
module.exports = {
|
|
397
|
+
MasterCache,
|
|
398
|
+
LRUCache,
|
|
399
|
+
cache
|
|
400
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// version 1.0.1
|
|
2
|
+
// MasterController Memory Monitor - Memory Leak Detection
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Memory monitor for detecting memory leaks
|
|
6
|
+
* - Heap usage tracking
|
|
7
|
+
* - Memory leak detection
|
|
8
|
+
* - Garbage collection monitoring
|
|
9
|
+
* - Memory alerts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { logger } = require('../error/MasterErrorLogger');
|
|
13
|
+
|
|
14
|
+
class MasterMemoryMonitor {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.enabled = options.enabled !== false;
|
|
17
|
+
this.checkInterval = options.checkInterval || 30000; // 30 seconds
|
|
18
|
+
this.leakThreshold = options.leakThreshold || 50; // 50MB growth
|
|
19
|
+
this.alertThreshold = options.alertThreshold || 500; // 500MB
|
|
20
|
+
|
|
21
|
+
this.snapshots = [];
|
|
22
|
+
this.maxSnapshots = 100;
|
|
23
|
+
this.intervalId = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Start monitoring
|
|
28
|
+
*/
|
|
29
|
+
start() {
|
|
30
|
+
if (!this.enabled || this.intervalId) return;
|
|
31
|
+
|
|
32
|
+
this.takeSnapshot();
|
|
33
|
+
|
|
34
|
+
this.intervalId = setInterval(() => {
|
|
35
|
+
this.takeSnapshot();
|
|
36
|
+
this.checkForLeaks();
|
|
37
|
+
}, this.checkInterval);
|
|
38
|
+
|
|
39
|
+
logger.info({
|
|
40
|
+
code: 'MC_MEMORY_MONITOR_START',
|
|
41
|
+
message: 'Memory monitoring started',
|
|
42
|
+
interval: `${this.checkInterval}ms`
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Stop monitoring
|
|
48
|
+
*/
|
|
49
|
+
stop() {
|
|
50
|
+
if (this.intervalId) {
|
|
51
|
+
clearInterval(this.intervalId);
|
|
52
|
+
this.intervalId = null;
|
|
53
|
+
|
|
54
|
+
logger.info({
|
|
55
|
+
code: 'MC_MEMORY_MONITOR_STOP',
|
|
56
|
+
message: 'Memory monitoring stopped'
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Take memory snapshot
|
|
63
|
+
*/
|
|
64
|
+
takeSnapshot() {
|
|
65
|
+
const usage = process.memoryUsage();
|
|
66
|
+
|
|
67
|
+
const snapshot = {
|
|
68
|
+
timestamp: Date.now(),
|
|
69
|
+
heapUsed: usage.heapUsed,
|
|
70
|
+
heapTotal: usage.heapTotal,
|
|
71
|
+
external: usage.external,
|
|
72
|
+
rss: usage.rss
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
this.snapshots.push(snapshot);
|
|
76
|
+
|
|
77
|
+
// Keep only last N snapshots
|
|
78
|
+
if (this.snapshots.length > this.maxSnapshots) {
|
|
79
|
+
this.snapshots.shift();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Alert if memory usage is high
|
|
83
|
+
const heapUsedMB = usage.heapUsed / 1024 / 1024;
|
|
84
|
+
if (heapUsedMB > this.alertThreshold) {
|
|
85
|
+
logger.warn({
|
|
86
|
+
code: 'MC_MEMORY_HIGH',
|
|
87
|
+
message: 'High memory usage detected',
|
|
88
|
+
heapUsed: `${heapUsedMB.toFixed(2)} MB`,
|
|
89
|
+
threshold: `${this.alertThreshold} MB`
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return snapshot;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check for memory leaks
|
|
98
|
+
*/
|
|
99
|
+
checkForLeaks() {
|
|
100
|
+
if (this.snapshots.length < 10) return;
|
|
101
|
+
|
|
102
|
+
// Compare first 5 and last 5 snapshots
|
|
103
|
+
const oldSnapshots = this.snapshots.slice(0, 5);
|
|
104
|
+
const newSnapshots = this.snapshots.slice(-5);
|
|
105
|
+
|
|
106
|
+
const oldAvg = oldSnapshots.reduce((sum, s) => sum + s.heapUsed, 0) / oldSnapshots.length;
|
|
107
|
+
const newAvg = newSnapshots.reduce((sum, s) => sum + s.heapUsed, 0) / newSnapshots.length;
|
|
108
|
+
|
|
109
|
+
const growthBytes = newAvg - oldAvg;
|
|
110
|
+
const growthMB = growthBytes / 1024 / 1024;
|
|
111
|
+
|
|
112
|
+
if (growthMB > this.leakThreshold) {
|
|
113
|
+
logger.warn({
|
|
114
|
+
code: 'MC_MEMORY_LEAK_DETECTED',
|
|
115
|
+
message: 'Potential memory leak detected',
|
|
116
|
+
growth: `${growthMB.toFixed(2)} MB`,
|
|
117
|
+
oldAvg: `${(oldAvg / 1024 / 1024).toFixed(2)} MB`,
|
|
118
|
+
newAvg: `${(newAvg / 1024 / 1024).toFixed(2)} MB`,
|
|
119
|
+
suggestion: 'Review component lifecycle and event listener cleanup'
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get current memory usage
|
|
126
|
+
*/
|
|
127
|
+
getCurrentUsage() {
|
|
128
|
+
const usage = process.memoryUsage();
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
heapUsed: `${(usage.heapUsed / 1024 / 1024).toFixed(2)} MB`,
|
|
132
|
+
heapTotal: `${(usage.heapTotal / 1024 / 1024).toFixed(2)} MB`,
|
|
133
|
+
external: `${(usage.external / 1024 / 1024).toFixed(2)} MB`,
|
|
134
|
+
rss: `${(usage.rss / 1024 / 1024).toFixed(2)} MB`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Print memory report
|
|
140
|
+
*/
|
|
141
|
+
printReport() {
|
|
142
|
+
if (this.snapshots.length === 0) {
|
|
143
|
+
console.log('No memory snapshots available');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const current = this.snapshots[this.snapshots.length - 1];
|
|
148
|
+
const first = this.snapshots[0];
|
|
149
|
+
|
|
150
|
+
const growth = current.heapUsed - first.heapUsed;
|
|
151
|
+
const growthPercent = (growth / first.heapUsed * 100).toFixed(2);
|
|
152
|
+
|
|
153
|
+
console.log('\n═══════════════════════════════════════════════════');
|
|
154
|
+
console.log('💾 MasterController Memory Report');
|
|
155
|
+
console.log('═══════════════════════════════════════════════════');
|
|
156
|
+
|
|
157
|
+
console.log('\nCurrent Usage:');
|
|
158
|
+
console.log(` Heap Used: ${(current.heapUsed / 1024 / 1024).toFixed(2)} MB`);
|
|
159
|
+
console.log(` Heap Total: ${(current.heapTotal / 1024 / 1024).toFixed(2)} MB`);
|
|
160
|
+
console.log(` External: ${(current.external / 1024 / 1024).toFixed(2)} MB`);
|
|
161
|
+
console.log(` RSS: ${(current.rss / 1024 / 1024).toFixed(2)} MB`);
|
|
162
|
+
|
|
163
|
+
console.log('\nMemory Growth:');
|
|
164
|
+
console.log(` Initial: ${(first.heapUsed / 1024 / 1024).toFixed(2)} MB`);
|
|
165
|
+
console.log(` Current: ${(current.heapUsed / 1024 / 1024).toFixed(2)} MB`);
|
|
166
|
+
console.log(` Growth: ${(growth / 1024 / 1024).toFixed(2)} MB (${growthPercent}%)`);
|
|
167
|
+
|
|
168
|
+
console.log(`\nSnapshots: ${this.snapshots.length}`);
|
|
169
|
+
console.log(`Duration: ${((current.timestamp - first.timestamp) / 1000 / 60).toFixed(2)} minutes`);
|
|
170
|
+
|
|
171
|
+
console.log('═══════════════════════════════════════════════════\n');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Create singleton
|
|
176
|
+
const memoryMonitor = new MasterMemoryMonitor({
|
|
177
|
+
enabled: process.env.MC_MEMORY_MONITOR === 'true',
|
|
178
|
+
checkInterval: 30000,
|
|
179
|
+
leakThreshold: 50,
|
|
180
|
+
alertThreshold: 500
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Auto-start in development
|
|
184
|
+
if (process.env.NODE_ENV === 'development') {
|
|
185
|
+
memoryMonitor.start();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = { MasterMemoryMonitor, memoryMonitor };
|