@devilsdev/rag-pipeline-utils 2.2.1 → 2.3.1
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/README.md +260 -19
- package/contracts/contract-schema.json +189 -0
- package/dist/index.cjs +19 -0
- package/dist/index.mjs +15 -1
- package/package.json +3 -2
- package/src/cli/commands/wizard.js +627 -0
- package/src/cli/input-sanitization-middleware.js +356 -0
- package/src/cli/templates/development.template.json +64 -0
- package/src/cli/templates/minimal.template.json +33 -0
- package/src/cli/templates/production.template.json +104 -0
- package/src/cli/templates/testing.template.json +45 -0
- package/src/config/connection-pool-config.json +259 -0
- package/src/config/memory-optimization.js +414 -0
- package/src/config/redaction-patterns.json +150 -0
- package/src/config/sanitization-rules.json +323 -0
- package/src/core/plugin-registry.js +266 -20
- package/src/dag/core/error-context.js +391 -0
- package/src/dag/core/execution-scheduler.js +350 -0
- package/src/dag/core/topology-validation.js +415 -0
- package/src/dag/dag-engine.js +26 -424
- package/src/dev/README.md +126 -0
- package/src/dev/dev-server.js +356 -0
- package/src/dev/examples/hot-reload-usage.js +355 -0
- package/src/dev/hot-reload.js +807 -0
- package/src/enterprise/sso-integration.js +67 -25
- package/src/index.js +19 -0
- package/src/observability/event-logger.js +62 -2
- package/src/plugins/optimized-openai-embedder.js +169 -0
- package/src/security/jwt-config.js +431 -0
- package/src/security/jwt-validator.js +701 -0
- package/src/utils/api-client-pool-integration.js +436 -0
- package/src/utils/batch-processor.js +710 -0
- package/src/utils/connection-pool.js +605 -0
- package/src/utils/contract-validator.js +460 -0
- package/src/utils/error-formatter.js +603 -0
- package/src/utils/examples/error-formatter-usage.js +351 -0
- package/src/utils/input-sanitizer.js +967 -0
- package/src/utils/memory-monitor.js +800 -0
- package/src/utils/secure-logger-config.js +352 -0
- package/src/utils/secure-logger.js +576 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Enhanced Memory Monitor
|
|
5
|
+
*
|
|
6
|
+
* Advanced memory monitoring with leak detection, historical tracking,
|
|
7
|
+
* GC optimization, and actionable insights for long-running processes.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Real-time memory usage tracking
|
|
11
|
+
* - Memory leak detection with trend analysis
|
|
12
|
+
* - Historical memory tracking with snapshots
|
|
13
|
+
* - GC optimization recommendations
|
|
14
|
+
* - Performance overhead measurement (<5%)
|
|
15
|
+
* - Actionable memory metrics
|
|
16
|
+
* - Integration with existing backpressure systems
|
|
17
|
+
*
|
|
18
|
+
* @module utils/memory-monitor
|
|
19
|
+
* @since 2.3.0
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const { EventEmitter } = require('events');
|
|
23
|
+
const { performance } = require('perf_hooks');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default configuration for memory monitoring
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_CONFIG = {
|
|
29
|
+
// Memory limits
|
|
30
|
+
maxMemoryMB: 512,
|
|
31
|
+
warningThreshold: 0.75, // 75% of max memory
|
|
32
|
+
criticalThreshold: 0.9, // 90% of max memory
|
|
33
|
+
|
|
34
|
+
// Sampling and history
|
|
35
|
+
samplingInterval: 5000, // Sample every 5 seconds
|
|
36
|
+
historySize: 100, // Keep last 100 samples
|
|
37
|
+
snapshotInterval: 60000, // Snapshot every minute
|
|
38
|
+
|
|
39
|
+
// Leak detection
|
|
40
|
+
leakDetectionEnabled: true,
|
|
41
|
+
leakThreshold: 5, // 5 consecutive increases = leak
|
|
42
|
+
leakRateThreshold: 0.05, // 5% growth per sample = leak
|
|
43
|
+
|
|
44
|
+
// GC hints
|
|
45
|
+
gcHintsEnabled: true,
|
|
46
|
+
gcThreshold: 0.85, // Suggest GC at 85%
|
|
47
|
+
autoGC: false, // Auto-trigger GC (requires --expose-gc)
|
|
48
|
+
|
|
49
|
+
// Performance
|
|
50
|
+
enableDetailedMetrics: true,
|
|
51
|
+
trackPerformanceOverhead: true,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Memory snapshot for historical tracking
|
|
56
|
+
*/
|
|
57
|
+
class MemorySnapshot {
|
|
58
|
+
constructor(usage, timestamp = Date.now()) {
|
|
59
|
+
this.timestamp = timestamp;
|
|
60
|
+
this.heapUsed = usage.heapUsed;
|
|
61
|
+
this.heapTotal = usage.heapTotal;
|
|
62
|
+
this.external = usage.external;
|
|
63
|
+
this.rss = usage.rss;
|
|
64
|
+
this.arrayBuffers = usage.arrayBuffers || 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
toJSON() {
|
|
68
|
+
return {
|
|
69
|
+
timestamp: this.timestamp,
|
|
70
|
+
heapUsedMB: Math.round(this.heapUsed / 1024 / 1024),
|
|
71
|
+
heapTotalMB: Math.round(this.heapTotal / 1024 / 1024),
|
|
72
|
+
externalMB: Math.round(this.external / 1024 / 1024),
|
|
73
|
+
rssMB: Math.round(this.rss / 1024 / 1024),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Memory leak detector
|
|
80
|
+
*/
|
|
81
|
+
class MemoryLeakDetector {
|
|
82
|
+
constructor(options = {}) {
|
|
83
|
+
this.leakThreshold = options.leakThreshold || 5;
|
|
84
|
+
this.leakRateThreshold = options.leakRateThreshold || 0.05;
|
|
85
|
+
this.consecutiveIncreases = 0;
|
|
86
|
+
this.lastHeapUsed = 0;
|
|
87
|
+
this.leakDetected = false;
|
|
88
|
+
this.leakHistory = [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Analyze memory trend for leaks
|
|
93
|
+
*
|
|
94
|
+
* @param {number} currentHeapUsed - Current heap usage in bytes
|
|
95
|
+
* @returns {Object} Leak analysis result
|
|
96
|
+
*/
|
|
97
|
+
analyze(currentHeapUsed) {
|
|
98
|
+
if (this.lastHeapUsed === 0) {
|
|
99
|
+
this.lastHeapUsed = currentHeapUsed;
|
|
100
|
+
return {
|
|
101
|
+
leakDetected: false,
|
|
102
|
+
consecutiveIncreases: 0,
|
|
103
|
+
increase: 0,
|
|
104
|
+
increaseRate: 0,
|
|
105
|
+
confidence: 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const increase = currentHeapUsed - this.lastHeapUsed;
|
|
110
|
+
const increaseRate = increase / this.lastHeapUsed;
|
|
111
|
+
|
|
112
|
+
if (increase > 0 && increaseRate > this.leakRateThreshold) {
|
|
113
|
+
this.consecutiveIncreases++;
|
|
114
|
+
|
|
115
|
+
// Record leak event
|
|
116
|
+
this.leakHistory.push({
|
|
117
|
+
timestamp: Date.now(),
|
|
118
|
+
heapUsed: currentHeapUsed,
|
|
119
|
+
increase,
|
|
120
|
+
increaseRate: increaseRate * 100,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Keep only recent history
|
|
124
|
+
if (this.leakHistory.length > 50) {
|
|
125
|
+
this.leakHistory.shift();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Detect leak
|
|
129
|
+
if (this.consecutiveIncreases >= this.leakThreshold) {
|
|
130
|
+
this.leakDetected = true;
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
this.consecutiveIncreases = Math.max(0, this.consecutiveIncreases - 1);
|
|
134
|
+
if (this.consecutiveIncreases === 0) {
|
|
135
|
+
this.leakDetected = false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.lastHeapUsed = currentHeapUsed;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
leakDetected: this.leakDetected,
|
|
143
|
+
consecutiveIncreases: this.consecutiveIncreases,
|
|
144
|
+
leakThreshold: this.leakThreshold,
|
|
145
|
+
increase,
|
|
146
|
+
increaseRate: increaseRate * 100,
|
|
147
|
+
confidence: Math.min(
|
|
148
|
+
100,
|
|
149
|
+
(this.consecutiveIncreases / this.leakThreshold) * 100,
|
|
150
|
+
),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get leak trend analysis
|
|
156
|
+
*/
|
|
157
|
+
getTrend() {
|
|
158
|
+
if (this.leakHistory.length < 2) {
|
|
159
|
+
return { trend: 'unknown', averageGrowthRate: 0 };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const recentHistory = this.leakHistory.slice(-10);
|
|
163
|
+
const avgGrowthRate =
|
|
164
|
+
recentHistory.reduce((sum, h) => sum + h.increaseRate, 0) /
|
|
165
|
+
recentHistory.length;
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
trend:
|
|
169
|
+
avgGrowthRate > this.leakRateThreshold * 100 ? 'increasing' : 'stable',
|
|
170
|
+
averageGrowthRate: avgGrowthRate,
|
|
171
|
+
sampleCount: recentHistory.length,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Reset leak detector
|
|
177
|
+
*/
|
|
178
|
+
reset() {
|
|
179
|
+
this.consecutiveIncreases = 0;
|
|
180
|
+
this.lastHeapUsed = 0;
|
|
181
|
+
this.leakDetected = false;
|
|
182
|
+
this.leakHistory = [];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* GC optimizer
|
|
188
|
+
*/
|
|
189
|
+
class GCOptimizer {
|
|
190
|
+
constructor(options = {}) {
|
|
191
|
+
this.gcThreshold = options.gcThreshold || 0.85;
|
|
192
|
+
this.autoGC = options.autoGC || false;
|
|
193
|
+
this.gcHistory = [];
|
|
194
|
+
this.lastGCTime = 0;
|
|
195
|
+
this.minGCInterval = 30000; // Minimum 30s between GCs
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Analyze if GC should be triggered
|
|
200
|
+
*
|
|
201
|
+
* @param {number} memoryRatio - Current memory usage ratio
|
|
202
|
+
* @param {Object} usage - Memory usage object
|
|
203
|
+
* @returns {Object} GC recommendation
|
|
204
|
+
*/
|
|
205
|
+
analyze(memoryRatio, usage) {
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
const timeSinceLastGC = now - this.lastGCTime;
|
|
208
|
+
|
|
209
|
+
const shouldTriggerGC =
|
|
210
|
+
memoryRatio > this.gcThreshold && timeSinceLastGC > this.minGCInterval;
|
|
211
|
+
|
|
212
|
+
const recommendation = {
|
|
213
|
+
shouldTriggerGC,
|
|
214
|
+
reason: null,
|
|
215
|
+
canAutoGC: global.gc !== undefined,
|
|
216
|
+
timeSinceLastGC,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (shouldTriggerGC) {
|
|
220
|
+
recommendation.reason = `Memory usage at ${(memoryRatio * 100).toFixed(1)}% (threshold: ${(this.gcThreshold * 100).toFixed(1)}%)`;
|
|
221
|
+
|
|
222
|
+
if (this.autoGC && global.gc) {
|
|
223
|
+
this.triggerGC();
|
|
224
|
+
recommendation.gcTriggered = true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return recommendation;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Trigger garbage collection
|
|
233
|
+
*/
|
|
234
|
+
triggerGC() {
|
|
235
|
+
if (!global.gc) {
|
|
236
|
+
return { triggered: false, reason: 'GC not exposed' };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const before = process.memoryUsage();
|
|
240
|
+
const startTime = performance.now();
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
global.gc();
|
|
244
|
+
|
|
245
|
+
const after = process.memoryUsage();
|
|
246
|
+
const duration = performance.now() - startTime;
|
|
247
|
+
const freed = before.heapUsed - after.heapUsed;
|
|
248
|
+
|
|
249
|
+
this.lastGCTime = Date.now();
|
|
250
|
+
|
|
251
|
+
this.gcHistory.push({
|
|
252
|
+
timestamp: this.lastGCTime,
|
|
253
|
+
duration,
|
|
254
|
+
freedMB: Math.round(freed / 1024 / 1024),
|
|
255
|
+
heapBefore: Math.round(before.heapUsed / 1024 / 1024),
|
|
256
|
+
heapAfter: Math.round(after.heapUsed / 1024 / 1024),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Keep only recent history
|
|
260
|
+
if (this.gcHistory.length > 50) {
|
|
261
|
+
this.gcHistory.shift();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
triggered: true,
|
|
266
|
+
duration,
|
|
267
|
+
freedMB: Math.round(freed / 1024 / 1024),
|
|
268
|
+
};
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return { triggered: false, error: error.message };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get GC statistics
|
|
276
|
+
*/
|
|
277
|
+
getStats() {
|
|
278
|
+
if (this.gcHistory.length === 0) {
|
|
279
|
+
return { gcCount: 0 };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const avgDuration =
|
|
283
|
+
this.gcHistory.reduce((sum, gc) => sum + gc.duration, 0) /
|
|
284
|
+
this.gcHistory.length;
|
|
285
|
+
const totalFreed = this.gcHistory.reduce((sum, gc) => sum + gc.freedMB, 0);
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
gcCount: this.gcHistory.length,
|
|
289
|
+
avgDuration: avgDuration.toFixed(2),
|
|
290
|
+
totalFreedMB: totalFreed,
|
|
291
|
+
avgFreedMB: (totalFreed / this.gcHistory.length).toFixed(2),
|
|
292
|
+
lastGC: this.gcHistory[this.gcHistory.length - 1],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Enhanced Memory Monitor Class
|
|
299
|
+
*/
|
|
300
|
+
class EnhancedMemoryMonitor extends EventEmitter {
|
|
301
|
+
constructor(options = {}) {
|
|
302
|
+
super();
|
|
303
|
+
|
|
304
|
+
this.config = {
|
|
305
|
+
...DEFAULT_CONFIG,
|
|
306
|
+
...options,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// State
|
|
310
|
+
this.monitoring = false;
|
|
311
|
+
this.samplingInterval = null;
|
|
312
|
+
this.snapshotInterval = null;
|
|
313
|
+
|
|
314
|
+
// History
|
|
315
|
+
this.history = [];
|
|
316
|
+
this.snapshots = [];
|
|
317
|
+
|
|
318
|
+
// Components
|
|
319
|
+
this.leakDetector = new MemoryLeakDetector({
|
|
320
|
+
leakThreshold: this.config.leakThreshold,
|
|
321
|
+
leakRateThreshold: this.config.leakRateThreshold,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
this.gcOptimizer = new GCOptimizer({
|
|
325
|
+
gcThreshold: this.config.gcThreshold,
|
|
326
|
+
autoGC: this.config.autoGC,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Metrics
|
|
330
|
+
this.metrics = {
|
|
331
|
+
totalSamples: 0,
|
|
332
|
+
totalSnapshots: 0,
|
|
333
|
+
warningCount: 0,
|
|
334
|
+
criticalCount: 0,
|
|
335
|
+
leakDetections: 0,
|
|
336
|
+
gcSuggestions: 0,
|
|
337
|
+
gcTriggers: 0,
|
|
338
|
+
overheadMs: 0,
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// Performance overhead tracking
|
|
342
|
+
this.overheadSamples = [];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Start monitoring
|
|
347
|
+
*/
|
|
348
|
+
start() {
|
|
349
|
+
if (this.monitoring) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.monitoring = true;
|
|
354
|
+
this.emit('start');
|
|
355
|
+
|
|
356
|
+
// Start sampling
|
|
357
|
+
this.samplingInterval = setInterval(() => {
|
|
358
|
+
this._sample();
|
|
359
|
+
}, this.config.samplingInterval);
|
|
360
|
+
|
|
361
|
+
// Start snapshots
|
|
362
|
+
if (this.config.snapshotInterval > 0) {
|
|
363
|
+
this.snapshotInterval = setInterval(() => {
|
|
364
|
+
this._takeSnapshot();
|
|
365
|
+
}, this.config.snapshotInterval);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Don't keep process alive
|
|
369
|
+
if (this.samplingInterval.unref) {
|
|
370
|
+
this.samplingInterval.unref();
|
|
371
|
+
}
|
|
372
|
+
if (this.snapshotInterval && this.snapshotInterval.unref) {
|
|
373
|
+
this.snapshotInterval.unref();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Take initial snapshot
|
|
377
|
+
this._takeSnapshot();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Stop monitoring
|
|
382
|
+
*/
|
|
383
|
+
stop() {
|
|
384
|
+
if (!this.monitoring) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
this.monitoring = false;
|
|
389
|
+
|
|
390
|
+
if (this.samplingInterval) {
|
|
391
|
+
clearInterval(this.samplingInterval);
|
|
392
|
+
this.samplingInterval = null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (this.snapshotInterval) {
|
|
396
|
+
clearInterval(this.snapshotInterval);
|
|
397
|
+
this.snapshotInterval = null;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
this.emit('stop');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Take a memory sample
|
|
405
|
+
* @private
|
|
406
|
+
*/
|
|
407
|
+
_sample() {
|
|
408
|
+
const startTime = performance.now();
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
const usage = this._getMemoryUsage();
|
|
412
|
+
const maxMemoryBytes = this.config.maxMemoryMB * 1024 * 1024;
|
|
413
|
+
const ratio = usage.heapUsed / maxMemoryBytes;
|
|
414
|
+
|
|
415
|
+
// Add to history
|
|
416
|
+
this.history.push({
|
|
417
|
+
timestamp: Date.now(),
|
|
418
|
+
...usage,
|
|
419
|
+
ratio,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// Trim history
|
|
423
|
+
if (this.history.length > this.config.historySize) {
|
|
424
|
+
this.history.shift();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
this.metrics.totalSamples++;
|
|
428
|
+
|
|
429
|
+
// Check thresholds
|
|
430
|
+
if (ratio > this.config.criticalThreshold) {
|
|
431
|
+
this.metrics.criticalCount++;
|
|
432
|
+
this.emit('critical', {
|
|
433
|
+
ratio,
|
|
434
|
+
heapUsedMB: Math.round(usage.heapUsed / 1024 / 1024),
|
|
435
|
+
maxMemoryMB: this.config.maxMemoryMB,
|
|
436
|
+
});
|
|
437
|
+
} else if (ratio > this.config.warningThreshold) {
|
|
438
|
+
this.metrics.warningCount++;
|
|
439
|
+
this.emit('warning', {
|
|
440
|
+
ratio,
|
|
441
|
+
heapUsedMB: Math.round(usage.heapUsed / 1024 / 1024),
|
|
442
|
+
maxMemoryMB: this.config.maxMemoryMB,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Leak detection
|
|
447
|
+
if (this.config.leakDetectionEnabled) {
|
|
448
|
+
const leakAnalysis = this.leakDetector.analyze(usage.heapUsed);
|
|
449
|
+
|
|
450
|
+
if (leakAnalysis.leakDetected && this.metrics.leakDetections === 0) {
|
|
451
|
+
this.metrics.leakDetections++;
|
|
452
|
+
this.emit('leak_detected', {
|
|
453
|
+
...leakAnalysis,
|
|
454
|
+
heapUsedMB: Math.round(usage.heapUsed / 1024 / 1024),
|
|
455
|
+
trend: this.leakDetector.getTrend(),
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// GC optimization
|
|
461
|
+
if (this.config.gcHintsEnabled) {
|
|
462
|
+
const gcAnalysis = this.gcOptimizer.analyze(ratio, usage);
|
|
463
|
+
|
|
464
|
+
if (gcAnalysis.shouldTriggerGC) {
|
|
465
|
+
this.metrics.gcSuggestions++;
|
|
466
|
+
|
|
467
|
+
if (gcAnalysis.gcTriggered) {
|
|
468
|
+
this.metrics.gcTriggers++;
|
|
469
|
+
this.emit('gc_triggered', gcAnalysis);
|
|
470
|
+
} else {
|
|
471
|
+
this.emit('gc_suggested', gcAnalysis);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Track overhead
|
|
477
|
+
const overhead = performance.now() - startTime;
|
|
478
|
+
this.overheadSamples.push(overhead);
|
|
479
|
+
if (this.overheadSamples.length > 100) {
|
|
480
|
+
this.overheadSamples.shift();
|
|
481
|
+
}
|
|
482
|
+
this.metrics.overheadMs =
|
|
483
|
+
this.overheadSamples.reduce((a, b) => a + b, 0) /
|
|
484
|
+
this.overheadSamples.length;
|
|
485
|
+
|
|
486
|
+
this.emit('sample', {
|
|
487
|
+
usage,
|
|
488
|
+
ratio,
|
|
489
|
+
timestamp: Date.now(),
|
|
490
|
+
});
|
|
491
|
+
} catch (error) {
|
|
492
|
+
this.emit('error', error);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Take a memory snapshot
|
|
498
|
+
* @private
|
|
499
|
+
*/
|
|
500
|
+
_takeSnapshot() {
|
|
501
|
+
try {
|
|
502
|
+
const usage = this._getMemoryUsage();
|
|
503
|
+
const snapshot = new MemorySnapshot(usage);
|
|
504
|
+
|
|
505
|
+
this.snapshots.push(snapshot);
|
|
506
|
+
|
|
507
|
+
// Trim snapshots
|
|
508
|
+
if (this.snapshots.length > 100) {
|
|
509
|
+
this.snapshots.shift();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
this.metrics.totalSnapshots++;
|
|
513
|
+
|
|
514
|
+
this.emit('snapshot', snapshot);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
this.emit('error', error);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Get current memory usage
|
|
522
|
+
* @private
|
|
523
|
+
*/
|
|
524
|
+
_getMemoryUsage() {
|
|
525
|
+
if (typeof process.memoryUsage === 'function') {
|
|
526
|
+
return process.memoryUsage();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Fallback for environments without process.memoryUsage
|
|
530
|
+
return {
|
|
531
|
+
heapUsed: 0,
|
|
532
|
+
heapTotal: 0,
|
|
533
|
+
external: 0,
|
|
534
|
+
rss: 0,
|
|
535
|
+
arrayBuffers: 0,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Get current memory metrics
|
|
541
|
+
*/
|
|
542
|
+
getMetrics() {
|
|
543
|
+
const usage = this._getMemoryUsage();
|
|
544
|
+
const maxMemoryBytes = this.config.maxMemoryMB * 1024 * 1024;
|
|
545
|
+
const ratio = usage.heapUsed / maxMemoryBytes;
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
// Current usage
|
|
549
|
+
current: {
|
|
550
|
+
heapUsedMB: Math.round(usage.heapUsed / 1024 / 1024),
|
|
551
|
+
heapTotalMB: Math.round(usage.heapTotal / 1024 / 1024),
|
|
552
|
+
externalMB: Math.round(usage.external / 1024 / 1024),
|
|
553
|
+
rssMB: Math.round(usage.rss / 1024 / 1024),
|
|
554
|
+
ratio: ratio,
|
|
555
|
+
percentage: Math.round(ratio * 100),
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
// Thresholds
|
|
559
|
+
thresholds: {
|
|
560
|
+
warning: this.config.warningThreshold,
|
|
561
|
+
critical: this.config.criticalThreshold,
|
|
562
|
+
maxMemoryMB: this.config.maxMemoryMB,
|
|
563
|
+
},
|
|
564
|
+
|
|
565
|
+
// Status
|
|
566
|
+
status:
|
|
567
|
+
ratio > this.config.criticalThreshold
|
|
568
|
+
? 'critical'
|
|
569
|
+
: ratio > this.config.warningThreshold
|
|
570
|
+
? 'warning'
|
|
571
|
+
: 'normal',
|
|
572
|
+
|
|
573
|
+
// Statistics
|
|
574
|
+
stats: {
|
|
575
|
+
...this.metrics,
|
|
576
|
+
historySize: this.history.length,
|
|
577
|
+
snapshotCount: this.snapshots.length,
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
// Leak detection
|
|
581
|
+
leak: this.config.leakDetectionEnabled
|
|
582
|
+
? {
|
|
583
|
+
detected: this.leakDetector.leakDetected,
|
|
584
|
+
consecutiveIncreases: this.leakDetector.consecutiveIncreases,
|
|
585
|
+
confidence: Math.min(
|
|
586
|
+
100,
|
|
587
|
+
(this.leakDetector.consecutiveIncreases /
|
|
588
|
+
this.leakDetector.leakThreshold) *
|
|
589
|
+
100,
|
|
590
|
+
),
|
|
591
|
+
trend: this.leakDetector.getTrend(),
|
|
592
|
+
}
|
|
593
|
+
: null,
|
|
594
|
+
|
|
595
|
+
// GC stats
|
|
596
|
+
gc: this.config.gcHintsEnabled ? this.gcOptimizer.getStats() : null,
|
|
597
|
+
|
|
598
|
+
// Performance overhead
|
|
599
|
+
overhead: {
|
|
600
|
+
avgMs: this.metrics.overheadMs.toFixed(3),
|
|
601
|
+
percentage: (
|
|
602
|
+
(this.metrics.overheadMs / this.config.samplingInterval) *
|
|
603
|
+
100
|
|
604
|
+
).toFixed(2),
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Get memory history
|
|
611
|
+
*/
|
|
612
|
+
getHistory() {
|
|
613
|
+
return this.history.map((h) => ({
|
|
614
|
+
timestamp: h.timestamp,
|
|
615
|
+
heapUsedMB: Math.round(h.heapUsed / 1024 / 1024),
|
|
616
|
+
heapTotalMB: Math.round(h.heapTotal / 1024 / 1024),
|
|
617
|
+
ratio: h.ratio,
|
|
618
|
+
}));
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Get memory snapshots
|
|
623
|
+
*/
|
|
624
|
+
getSnapshots() {
|
|
625
|
+
return this.snapshots.map((s) => s.toJSON());
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Get actionable recommendations
|
|
630
|
+
*/
|
|
631
|
+
getRecommendations() {
|
|
632
|
+
const metrics = this.getMetrics();
|
|
633
|
+
const recommendations = [];
|
|
634
|
+
|
|
635
|
+
// High memory usage
|
|
636
|
+
if (metrics.status === 'critical') {
|
|
637
|
+
recommendations.push({
|
|
638
|
+
priority: 'high',
|
|
639
|
+
category: 'memory',
|
|
640
|
+
message: `Critical memory usage: ${metrics.current.percentage}%`,
|
|
641
|
+
action:
|
|
642
|
+
'Reduce batch sizes, implement streaming, or increase memory limit',
|
|
643
|
+
});
|
|
644
|
+
} else if (metrics.status === 'warning') {
|
|
645
|
+
recommendations.push({
|
|
646
|
+
priority: 'medium',
|
|
647
|
+
category: 'memory',
|
|
648
|
+
message: `High memory usage: ${metrics.current.percentage}%`,
|
|
649
|
+
action: 'Monitor closely and consider optimizations',
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Memory leak
|
|
654
|
+
if (metrics.leak && metrics.leak.detected) {
|
|
655
|
+
recommendations.push({
|
|
656
|
+
priority: 'high',
|
|
657
|
+
category: 'leak',
|
|
658
|
+
message: `Memory leak detected with ${metrics.leak.confidence.toFixed(0)}% confidence`,
|
|
659
|
+
action:
|
|
660
|
+
'Investigate increasing memory trend. Check for unclosed resources, event listeners, or circular references',
|
|
661
|
+
details: metrics.leak.trend,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// GC optimization
|
|
666
|
+
if (metrics.gc && metrics.current.ratio > this.config.gcThreshold) {
|
|
667
|
+
recommendations.push({
|
|
668
|
+
priority: 'medium',
|
|
669
|
+
category: 'gc',
|
|
670
|
+
message: `GC recommended at ${metrics.current.percentage}% memory usage`,
|
|
671
|
+
action: global.gc
|
|
672
|
+
? 'Trigger manual GC or enable autoGC'
|
|
673
|
+
: 'Run with --expose-gc flag to enable manual GC',
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Performance overhead
|
|
678
|
+
if (parseFloat(metrics.overhead.percentage) > 5) {
|
|
679
|
+
recommendations.push({
|
|
680
|
+
priority: 'low',
|
|
681
|
+
category: 'performance',
|
|
682
|
+
message: `Monitoring overhead at ${metrics.overhead.percentage}%`,
|
|
683
|
+
action:
|
|
684
|
+
'Consider increasing samplingInterval or disabling detailed metrics',
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return recommendations;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Get comprehensive report
|
|
693
|
+
*/
|
|
694
|
+
getReport() {
|
|
695
|
+
const metrics = this.getMetrics();
|
|
696
|
+
const recommendations = this.getRecommendations();
|
|
697
|
+
|
|
698
|
+
const report = [];
|
|
699
|
+
|
|
700
|
+
report.push('='.repeat(60));
|
|
701
|
+
report.push('ENHANCED MEMORY MONITOR REPORT');
|
|
702
|
+
report.push('='.repeat(60));
|
|
703
|
+
|
|
704
|
+
report.push('\nCurrent Memory Usage:');
|
|
705
|
+
report.push(` Heap Used: ${metrics.current.heapUsedMB} MB`);
|
|
706
|
+
report.push(` Heap Total: ${metrics.current.heapTotalMB} MB`);
|
|
707
|
+
report.push(` External: ${metrics.current.externalMB} MB`);
|
|
708
|
+
report.push(` RSS: ${metrics.current.rssMB} MB`);
|
|
709
|
+
report.push(` Usage: ${metrics.current.percentage}%`);
|
|
710
|
+
report.push(` Status: ${metrics.status.toUpperCase()}`);
|
|
711
|
+
|
|
712
|
+
report.push('\nStatistics:');
|
|
713
|
+
report.push(` Total Samples: ${metrics.stats.totalSamples}`);
|
|
714
|
+
report.push(` Snapshots: ${metrics.stats.snapshotCount}`);
|
|
715
|
+
report.push(` Warnings: ${metrics.stats.warningCount}`);
|
|
716
|
+
report.push(` Critical Events: ${metrics.stats.criticalCount}`);
|
|
717
|
+
|
|
718
|
+
if (metrics.leak) {
|
|
719
|
+
report.push('\nLeak Detection:');
|
|
720
|
+
report.push(
|
|
721
|
+
` Status: ${metrics.leak.detected ? 'LEAK DETECTED' : 'No leak'}`,
|
|
722
|
+
);
|
|
723
|
+
report.push(` Confidence: ${metrics.leak.confidence.toFixed(0)}%`);
|
|
724
|
+
report.push(` Trend: ${metrics.leak.trend.trend}`);
|
|
725
|
+
if (metrics.leak.trend.averageGrowthRate > 0) {
|
|
726
|
+
report.push(
|
|
727
|
+
` Avg Growth Rate: ${metrics.leak.trend.averageGrowthRate.toFixed(2)}%`,
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (metrics.gc) {
|
|
733
|
+
report.push('\nGarbage Collection:');
|
|
734
|
+
report.push(` GC Count: ${metrics.gc.gcCount}`);
|
|
735
|
+
report.push(` Suggestions: ${metrics.stats.gcSuggestions}`);
|
|
736
|
+
report.push(` Auto Triggers: ${metrics.stats.gcTriggers}`);
|
|
737
|
+
if (metrics.gc.gcCount > 0) {
|
|
738
|
+
report.push(` Avg Duration: ${metrics.gc.avgDuration}ms`);
|
|
739
|
+
report.push(` Total Freed: ${metrics.gc.totalFreedMB} MB`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
report.push('\nPerformance Overhead:');
|
|
744
|
+
report.push(` Avg Overhead: ${metrics.overhead.avgMs}ms per sample`);
|
|
745
|
+
report.push(` Percentage: ${metrics.overhead.percentage}%`);
|
|
746
|
+
|
|
747
|
+
if (recommendations.length > 0) {
|
|
748
|
+
report.push('\nRecommendations:');
|
|
749
|
+
recommendations.forEach((rec, i) => {
|
|
750
|
+
report.push(
|
|
751
|
+
`\n${i + 1}. [${rec.priority.toUpperCase()}] ${rec.category}`,
|
|
752
|
+
);
|
|
753
|
+
report.push(` ${rec.message}`);
|
|
754
|
+
report.push(` Action: ${rec.action}`);
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
report.push('\n' + '='.repeat(60));
|
|
759
|
+
|
|
760
|
+
return report.join('\n');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Reset monitor state
|
|
765
|
+
*/
|
|
766
|
+
reset() {
|
|
767
|
+
this.history = [];
|
|
768
|
+
this.snapshots = [];
|
|
769
|
+
this.leakDetector.reset();
|
|
770
|
+
this.metrics = {
|
|
771
|
+
totalSamples: 0,
|
|
772
|
+
totalSnapshots: 0,
|
|
773
|
+
warningCount: 0,
|
|
774
|
+
criticalCount: 0,
|
|
775
|
+
leakDetections: 0,
|
|
776
|
+
gcSuggestions: 0,
|
|
777
|
+
gcTriggers: 0,
|
|
778
|
+
overheadMs: 0,
|
|
779
|
+
};
|
|
780
|
+
this.overheadSamples = [];
|
|
781
|
+
|
|
782
|
+
this.emit('reset');
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Create memory monitor with default configuration
|
|
788
|
+
*/
|
|
789
|
+
function createMemoryMonitor(options = {}) {
|
|
790
|
+
return new EnhancedMemoryMonitor(options);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
module.exports = {
|
|
794
|
+
EnhancedMemoryMonitor,
|
|
795
|
+
MemoryLeakDetector,
|
|
796
|
+
GCOptimizer,
|
|
797
|
+
MemorySnapshot,
|
|
798
|
+
createMemoryMonitor,
|
|
799
|
+
DEFAULT_CONFIG,
|
|
800
|
+
};
|