polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
417 lines • 15.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryManager = void 0;
const events_1 = require("events");
class MemoryManager extends events_1.EventEmitter {
constructor(config = {}) {
super();
this.snapshots = [];
this.managedResources = new Map();
this.isRunning = false;
this.config = {
warningThreshold: 80 * 1024 * 1024,
criticalThreshold: 150 * 1024 * 1024,
cleanupInterval: 30000,
enableAutoCleanup: true,
enableGcForcing: false,
gcInterval: 60000,
enableLeakDetection: true,
leakDetectionInterval: 60000,
trendSampleCount: 60,
...config,
};
const initialSnapshot = this.takeMemorySnapshot();
this.stats = {
current: initialSnapshot,
peak: initialSnapshot,
average: {
rss: initialSnapshot.rss,
heapUsed: initialSnapshot.heapUsed,
heapTotal: initialSnapshot.heapTotal,
},
growthRate: 0,
cleanupCount: 0,
gcCount: 0,
leakAlerts: 0,
pressureLevel: 'normal',
};
this.peakSnapshot = initialSnapshot;
}
start() {
if (this.isRunning) {
return;
}
this.isRunning = true;
this.startCleanupMonitoring();
if (this.config.enableGcForcing) {
this.startGcMonitoring();
}
if (this.config.enableLeakDetection) {
this.startLeakDetection();
}
this.emit('started', {
timestamp: Date.now(),
config: this.config,
});
}
stop() {
if (!this.isRunning) {
return;
}
this.isRunning = false;
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
delete this.cleanupTimer;
}
if (this.gcTimer) {
clearInterval(this.gcTimer);
delete this.gcTimer;
}
if (this.leakDetectionTimer) {
clearInterval(this.leakDetectionTimer);
delete this.leakDetectionTimer;
}
this.emit('stopped', {
timestamp: Date.now(),
finalStats: this.getStats(),
});
}
registerResource(resource) {
const managedResource = {
...resource,
createdAt: Date.now(),
lastAccessed: Date.now(),
};
this.managedResources.set(resource.id, managedResource);
this.emit('resourceRegistered', {
resourceId: resource.id,
type: resource.type,
size: resource.size,
timestamp: Date.now(),
});
}
unregisterResource(resourceId) {
const resource = this.managedResources.get(resourceId);
if (resource) {
this.managedResources.delete(resourceId);
this.emit('resourceUnregistered', {
resourceId,
type: resource.type,
size: resource.size,
timestamp: Date.now(),
});
}
}
touchResource(resourceId) {
const resource = this.managedResources.get(resourceId);
if (resource) {
resource.lastAccessed = Date.now();
}
}
async forceCleanup() {
const cleanedCount = await this.performCleanup();
this.emit('cleanupForced', {
resourcesCleaned: cleanedCount,
timestamp: Date.now(),
});
return cleanedCount;
}
forceGarbageCollection() {
if (global.gc) {
try {
global.gc();
this.stats.gcCount++;
this.emit('gcForced', {
timestamp: Date.now(),
});
}
catch (error) {
this.emit('gcError', {
error,
timestamp: Date.now(),
});
}
}
}
getStats() {
this.updateStats();
return { ...this.stats };
}
getSnapshots(count) {
const snapshots = count ? this.snapshots.slice(-count) : this.snapshots;
return [...snapshots];
}
getManagedResources() {
const now = Date.now();
return Array.from(this.managedResources.values()).map(resource => ({
id: resource.id,
type: resource.type,
size: resource.size,
age: now - resource.createdAt,
lastAccessed: resource.lastAccessed,
priority: resource.priority,
}));
}
detectMemoryLeaks() {
if (this.snapshots.length < 5) {
return {
leakDetected: false,
severity: 'low',
growthTrend: 'stable',
growthRatePerMinute: 0,
confidence: 0.1,
recommendations: ['Insufficient data for leak detection'],
};
}
const recentSnapshots = this.snapshots.slice(-10);
const growthRates = this.calculateGrowthRates(recentSnapshots);
const avgGrowthRate = growthRates.reduce((sum, rate) => sum + rate, 0) / growthRates.length;
const growthRatePerMinute = avgGrowthRate * 60;
let growthTrend = 'stable';
if (growthRatePerMinute > 5 * 1024 * 1024) {
growthTrend = 'rapidly_growing';
}
else if (growthRatePerMinute > 1 * 1024 * 1024) {
growthTrend = 'growing';
}
const growthConsistency = this.calculateGrowthConsistency(growthRates);
const confidence = Math.min(growthConsistency, 1.0);
const leakDetected = growthTrend !== 'stable' && confidence > 0.7;
let severity = 'low';
if (growthRatePerMinute > 10 * 1024 * 1024) {
severity = 'high';
}
else if (growthRatePerMinute > 3 * 1024 * 1024) {
severity = 'medium';
}
const recommendations = this.generateLeakRecommendations(growthTrend, severity);
return {
leakDetected,
severity,
growthTrend,
growthRatePerMinute,
confidence,
recommendations,
};
}
setPressureLevel(level) {
this.stats.pressureLevel = level;
this.emit('pressureLevelChanged', {
level,
timestamp: Date.now(),
});
if (level === 'critical') {
this.handleCriticalMemoryPressure();
}
}
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
this.emit('configUpdated', {
config: this.config,
timestamp: Date.now(),
});
}
takeMemorySnapshot() {
const memUsage = process.memoryUsage();
const timestamp = Date.now();
const usagePercentage = (memUsage.heapUsed / memUsage.heapTotal) * 100;
return {
timestamp,
rss: memUsage.rss,
heapUsed: memUsage.heapUsed,
heapTotal: memUsage.heapTotal,
external: memUsage.external,
arrayBuffers: memUsage.arrayBuffers || 0,
usagePercentage,
};
}
updateStats() {
const currentSnapshot = this.takeMemorySnapshot();
this.stats.current = currentSnapshot;
this.snapshots.push(currentSnapshot);
if (this.snapshots.length > this.config.trendSampleCount) {
this.snapshots.shift();
}
if (!this.peakSnapshot || currentSnapshot.heapUsed > this.peakSnapshot.heapUsed) {
this.peakSnapshot = currentSnapshot;
this.stats.peak = currentSnapshot;
}
if (this.snapshots.length > 1) {
this.stats.average = {
rss: this.snapshots.reduce((sum, s) => sum + s.rss, 0) / this.snapshots.length,
heapUsed: this.snapshots.reduce((sum, s) => sum + s.heapUsed, 0) / this.snapshots.length,
heapTotal: this.snapshots.reduce((sum, s) => sum + s.heapTotal, 0) / this.snapshots.length,
};
const recentSnapshots = this.snapshots.slice(-5);
if (recentSnapshots.length > 1) {
const growthRates = this.calculateGrowthRates(recentSnapshots);
this.stats.growthRate = growthRates.reduce((sum, rate) => sum + rate, 0) / growthRates.length;
}
}
this.updatePressureLevel(currentSnapshot);
}
updatePressureLevel(snapshot) {
const oldLevel = this.stats.pressureLevel;
if (snapshot.heapUsed > this.config.criticalThreshold) {
this.stats.pressureLevel = 'critical';
}
else if (snapshot.heapUsed > this.config.warningThreshold) {
this.stats.pressureLevel = 'warning';
}
else {
this.stats.pressureLevel = 'normal';
}
if (oldLevel !== this.stats.pressureLevel) {
this.emit('pressureLevelChanged', {
level: this.stats.pressureLevel,
previousLevel: oldLevel,
snapshot,
timestamp: Date.now(),
});
if (this.stats.pressureLevel === 'critical') {
this.handleCriticalMemoryPressure();
}
}
}
async handleCriticalMemoryPressure() {
this.emit('criticalMemoryPressure', {
snapshot: this.stats.current,
timestamp: Date.now(),
});
const cleanedResources = await this.performCleanup(true);
if (this.config.enableGcForcing) {
this.forceGarbageCollection();
}
this.emit('criticalMemoryHandled', {
cleanedResources,
timestamp: Date.now(),
});
}
startCleanupMonitoring() {
this.cleanupTimer = setInterval(() => {
if (this.isRunning && this.config.enableAutoCleanup) {
this.performCleanup();
}
this.updateStats();
}, this.config.cleanupInterval);
}
startGcMonitoring() {
this.gcTimer = setInterval(() => {
if (this.isRunning && this.stats.pressureLevel !== 'normal') {
this.forceGarbageCollection();
}
}, this.config.gcInterval);
}
startLeakDetection() {
this.leakDetectionTimer = setInterval(() => {
if (this.isRunning) {
const leakDetection = this.detectMemoryLeaks();
if (leakDetection.leakDetected) {
this.stats.leakAlerts++;
this.emit('memoryLeakDetected', {
detection: leakDetection,
timestamp: Date.now(),
});
}
}
}, this.config.leakDetectionInterval);
}
async performCleanup(aggressive = false) {
const now = Date.now();
const cleanupThreshold = aggressive ? 10000 : 300000;
let cleanedCount = 0;
const resourcesToCleanup = [];
for (const resource of this.managedResources.values()) {
if (resource.disposable) {
const age = now - resource.lastAccessed;
const shouldCleanup = age > cleanupThreshold ||
(aggressive && resource.priority === 'low');
if (shouldCleanup) {
resourcesToCleanup.push(resource);
}
}
}
resourcesToCleanup.sort((a, b) => {
const priorityOrder = { low: 0, medium: 1, high: 2 };
return priorityOrder[a.priority] - priorityOrder[b.priority];
});
for (const resource of resourcesToCleanup) {
try {
resource.cleanup();
this.managedResources.delete(resource.id);
cleanedCount++;
this.emit('resourceCleaned', {
resourceId: resource.id,
type: resource.type,
size: resource.size,
age: now - resource.createdAt,
timestamp: now,
});
}
catch (error) {
this.emit('cleanupError', {
resourceId: resource.id,
error,
timestamp: now,
});
}
}
if (cleanedCount > 0) {
this.stats.cleanupCount++;
this.emit('cleanupCompleted', {
resourcesCleaned: cleanedCount,
aggressive,
timestamp: now,
});
}
return cleanedCount;
}
calculateGrowthRates(snapshots) {
const growthRates = [];
for (let i = 1; i < snapshots.length; i++) {
const current = snapshots[i];
const previous = snapshots[i - 1];
if (!current || !previous)
continue;
const timeDiff = (current.timestamp - previous.timestamp) / 1000;
const memoryDiff = current.heapUsed - previous.heapUsed;
if (timeDiff > 0) {
growthRates.push(memoryDiff / timeDiff);
}
}
return growthRates;
}
calculateGrowthConsistency(growthRates) {
if (growthRates.length < 2) {
return 0;
}
const mean = growthRates.reduce((sum, rate) => sum + rate, 0) / growthRates.length;
const variance = growthRates.reduce((sum, rate) => sum + Math.pow(rate - mean, 2), 0) / growthRates.length;
const standardDeviation = Math.sqrt(variance);
const consistency = 1 / (1 + standardDeviation / (Math.abs(mean) + 1));
return consistency;
}
generateLeakRecommendations(growthTrend, severity) {
const recommendations = [];
if (growthTrend === 'rapidly_growing') {
recommendations.push('Immediate action required: Memory is growing rapidly');
recommendations.push('Check for unbounded arrays, maps, or event listeners');
recommendations.push('Review recent code changes for potential leaks');
}
if (severity === 'high') {
recommendations.push('Consider forcing garbage collection');
recommendations.push('Enable aggressive resource cleanup');
recommendations.push('Monitor component lifecycle for proper cleanup');
}
if (growthTrend === 'growing') {
recommendations.push('Monitor memory usage closely');
recommendations.push('Review data structures for potential optimization');
recommendations.push('Check for circular references');
}
recommendations.push('Use memory profiling tools for detailed analysis');
recommendations.push('Implement resource pooling where appropriate');
return recommendations;
}
}
exports.MemoryManager = MemoryManager;
//# sourceMappingURL=memory-manager.js.map