glassbox-ai
Version:
Enterprise-grade AI testing framework with reliability, observability, and comprehensive validation
374 lines (318 loc) • 9.88 kB
JavaScript
import { EventEmitter } from 'events';
import { performance } from 'perf_hooks';
/**
* Memory Profiler and Leak Detector
*/
export class MemoryProfiler extends EventEmitter {
constructor(options = {}) {
super();
this.enabled = options.enabled !== false;
this.interval = options.interval || 30000; // 30 seconds
this.threshold = options.threshold || 100 * 1024 * 1024; // 100MB
this.snapshots = [];
this.maxSnapshots = options.maxSnapshots || 50;
this.monitoring = false;
this.intervalId = null;
this.stats = {
totalSnapshots: 0,
memoryLeaks: 0,
peakUsage: 0,
averageUsage: 0,
garbageCollections: 0
};
}
/**
* Start memory monitoring
*/
start() {
if (!this.enabled || this.monitoring) return;
this.monitoring = true;
this.intervalId = setInterval(() => {
this.takeSnapshot();
}, this.interval);
this.emit('monitoring-started');
}
/**
* Stop memory monitoring
*/
stop() {
if (!this.monitoring) return;
this.monitoring = false;
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
this.emit('monitoring-stopped');
}
/**
* Take a memory snapshot
*/
takeSnapshot() {
const usage = process.memoryUsage();
const snapshot = {
timestamp: Date.now(),
rss: usage.rss,
heapTotal: usage.heapTotal,
heapUsed: usage.heapUsed,
external: usage.external,
arrayBuffers: usage.arrayBuffers || 0
};
this.snapshots.push(snapshot);
// Keep only recent snapshots
if (this.snapshots.length > this.maxSnapshots) {
this.snapshots.shift();
}
this.stats.totalSnapshots++;
this.updateStats(snapshot);
// Check for memory leaks
this.detectMemoryLeak();
this.emit('snapshot-taken', snapshot);
}
/**
* Update memory statistics
*/
updateStats(snapshot) {
this.stats.peakUsage = Math.max(this.stats.peakUsage, snapshot.heapUsed);
const totalUsage = this.snapshots.reduce((sum, s) => sum + s.heapUsed, 0);
this.stats.averageUsage = totalUsage / this.snapshots.length;
}
/**
* Detect memory leaks
*/
detectMemoryLeak() {
if (this.snapshots.length < 3) return;
const recent = this.snapshots.slice(-3);
const trend = this.calculateTrend(recent.map(s => s.heapUsed));
if (trend > 0.1) { // 10% growth threshold
this.stats.memoryLeaks++;
this.emit('memory-leak-detected', {
trend,
snapshots: recent,
recommendation: this.getLeakRecommendation(recent)
});
}
}
/**
* Calculate trend from data points
*/
calculateTrend(data) {
if (data.length < 2) return 0;
const n = data.length;
const sumX = (n * (n - 1)) / 2;
const sumY = data.reduce((sum, y) => sum + y, 0);
const sumXY = data.reduce((sum, y, i) => sum + (i * y), 0);
const sumX2 = (n * (n - 1) * (2 * n - 1)) / 6;
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
return slope / data[0]; // Normalize by initial value
}
/**
* Get memory leak recommendations
*/
getLeakRecommendation(snapshots) {
const growth = snapshots[snapshots.length - 1].heapUsed - snapshots[0].heapUsed;
const growthMB = growth / (1024 * 1024);
if (growthMB > 50) {
return 'Critical: Large memory leak detected. Check for unclosed resources, event listeners, or circular references.';
} else if (growthMB > 20) {
return 'Warning: Moderate memory leak detected. Review object lifecycle management.';
} else if (growthMB > 5) {
return 'Info: Small memory growth detected. Monitor for patterns.';
}
return 'No significant memory leak detected.';
}
/**
* Force garbage collection (if available)
*/
forceGC() {
if (global.gc) {
global.gc();
this.stats.garbageCollections++;
this.emit('garbage-collection');
}
}
/**
* Get memory usage report
*/
getMemoryReport() {
const current = process.memoryUsage();
const recent = this.snapshots.slice(-5);
return {
current: {
rss: this.formatBytes(current.rss),
heapTotal: this.formatBytes(current.heapTotal),
heapUsed: this.formatBytes(current.heapUsed),
external: this.formatBytes(current.external)
},
trend: recent.length >= 2 ? this.calculateTrend(recent.map(s => s.heapUsed)) : 0,
snapshots: recent.length,
stats: this.stats
};
}
/**
* Format bytes to human readable format
*/
formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* Profile a function's memory usage
*/
async profileFunction(fn, name = 'anonymous') {
const before = process.memoryUsage();
const startTime = performance.now();
try {
const result = await fn();
const after = process.memoryUsage();
const endTime = performance.now();
const memoryDelta = {
rss: after.rss - before.rss,
heapTotal: after.heapTotal - before.heapTotal,
heapUsed: after.heapUsed - before.heapUsed,
external: after.external - before.external
};
const profile = {
name,
duration: endTime - startTime,
memoryDelta,
memoryDeltaFormatted: {
rss: this.formatBytes(memoryDelta.rss),
heapTotal: this.formatBytes(memoryDelta.heapTotal),
heapUsed: this.formatBytes(memoryDelta.heapUsed),
external: this.formatBytes(memoryDelta.external)
}
};
this.emit('function-profiled', profile);
return { result, profile };
} catch (error) {
const after = process.memoryUsage();
const endTime = performance.now();
const profile = {
name,
duration: endTime - startTime,
error: error.message,
memoryDelta: {
rss: after.rss - before.rss,
heapTotal: after.heapTotal - before.heapTotal,
heapUsed: after.heapUsed - before.heapUsed,
external: after.external - before.external
}
};
this.emit('function-profiled', profile);
throw error;
}
}
/**
* Monitor specific objects for leaks
*/
monitorObject(obj, name = 'object') {
const weakRef = new WeakRef(obj);
const initialSize = this.estimateObjectSize(obj);
const monitor = {
name,
initialSize,
currentSize: initialSize,
references: 0,
lastAccessed: Date.now()
};
// Set up periodic checking
const checkInterval = setInterval(() => {
const ref = weakRef.deref();
if (ref) {
monitor.currentSize = this.estimateObjectSize(ref);
monitor.lastAccessed = Date.now();
monitor.references++;
if (monitor.currentSize > monitor.initialSize * 2) {
this.emit('object-growth', monitor);
}
} else {
clearInterval(checkInterval);
this.emit('object-collected', monitor);
}
}, 10000); // Check every 10 seconds
return monitor;
}
/**
* Estimate object size (rough approximation)
*/
estimateObjectSize(obj) {
if (obj === null || obj === undefined) return 0;
let size = 0;
const visited = new WeakSet();
const estimate = (item) => {
if (visited.has(item)) return 0;
visited.add(item);
if (typeof item === 'string') {
size += item.length * 2; // UTF-16
} else if (typeof item === 'number') {
size += 8;
} else if (typeof item === 'boolean') {
size += 4;
} else if (Array.isArray(item)) {
size += 8; // Array overhead
for (const element of item) {
estimate(element);
}
} else if (typeof item === 'object') {
size += 8; // Object overhead
for (const key in item) {
if (item.hasOwnProperty(key)) {
size += key.length * 2; // Key size
estimate(item[key]);
}
}
}
};
estimate(obj);
return size;
}
/**
* Get detailed memory analysis
*/
getDetailedAnalysis() {
const current = process.memoryUsage();
const recent = this.snapshots.slice(-10);
const analysis = {
current: current,
trend: recent.length >= 2 ? this.calculateTrend(recent.map(s => s.heapUsed)) : 0,
peak: this.stats.peakUsage,
average: this.stats.averageUsage,
snapshots: recent.length,
recommendations: []
};
// Generate recommendations
if (current.heapUsed > this.threshold) {
analysis.recommendations.push('High memory usage detected. Consider optimizing data structures or implementing pagination.');
}
if (analysis.trend > 0.05) {
analysis.recommendations.push('Memory growth detected. Check for memory leaks in event listeners or circular references.');
}
if (current.external > current.heapUsed * 0.5) {
analysis.recommendations.push('High external memory usage. Review file handles, network connections, or native modules.');
}
return analysis;
}
/**
* Clear all snapshots
*/
clear() {
this.snapshots = [];
this.stats = {
totalSnapshots: 0,
memoryLeaks: 0,
peakUsage: 0,
averageUsage: 0,
garbageCollections: 0
};
}
}
// Global memory profiler instance
export const globalMemoryProfiler = new MemoryProfiler({
enabled: true,
interval: 30000,
threshold: 100 * 1024 * 1024, // 100MB
maxSnapshots: 50
});