ai-debug-local-mcp
Version:
๐ฏ ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh
276 lines โข 10.4 kB
JavaScript
/**
* Advanced Memory Manager for AI-Debug MCP Server
* Implements comprehensive memory optimization strategies
*/
import { AbortSignalManager } from './abort-signal-manager.js';
export class AdvancedMemoryManager {
static instance;
memoryHistory = [];
sessionMemoryMap = new Map();
MAX_HISTORY = 100;
SESSION_TTL = 30 * 60 * 1000; // 30 minutes
MAX_SESSIONS = 50; // Maximum concurrent sessions
MEMORY_PRESSURE_THRESHOLD = 0.98; // 98% of heap - Node.js naturally uses high heap %
GC_INTERVAL = 5 * 60 * 1000; // 5 minutes
constructor() {
this.startMemoryMonitoring();
}
static getInstance() {
if (!AdvancedMemoryManager.instance) {
AdvancedMemoryManager.instance = new AdvancedMemoryManager();
}
return AdvancedMemoryManager.instance;
}
/**
* Start continuous memory monitoring
*/
startMemoryMonitoring() {
setInterval(() => {
this.recordMemoryProfile();
this.checkMemoryPressure();
this.cleanupExpiredSessions();
}, this.GC_INTERVAL);
}
/**
* Record current memory profile
*/
recordMemoryProfile() {
const usage = process.memoryUsage();
const profile = {
heapUsed: usage.heapUsed,
heapTotal: usage.heapTotal,
external: usage.external,
rss: usage.rss,
timestamp: Date.now()
};
this.memoryHistory.push(profile);
if (this.memoryHistory.length > this.MAX_HISTORY) {
this.memoryHistory.shift();
}
}
/**
* Check for memory pressure and take action
*/
checkMemoryPressure() {
const current = process.memoryUsage();
const heapUsageRatio = current.heapUsed / current.heapTotal;
const heapUsedMB = current.heapUsed / 1024 / 1024;
if (heapUsageRatio > this.MEMORY_PRESSURE_THRESHOLD || heapUsedMB > 2048) {
console.warn(`๐จ Memory pressure detected: ${Math.round(heapUsageRatio * 100)}% heap usage (${Math.round(heapUsedMB)} MB)`);
this.performEmergencyCleanup();
}
}
/**
* Perform emergency memory cleanup
*/
async performEmergencyCleanup() {
console.log('๐งน Performing emergency memory cleanup...');
// 1. Clean up expired sessions
await this.cleanupExpiredSessions();
// 2. Limit active sessions to most recent
await this.limitActiveSessions(Math.floor(this.MAX_SESSIONS * 0.7)); // Keep 70% of max
// 3. Force garbage collection if available
if (global.gc) {
global.gc();
console.log('โป๏ธ Forced garbage collection completed');
}
// 4. Clear memory history to free space
this.memoryHistory = this.memoryHistory.slice(-20); // Keep only last 20 entries
// 5. Emergency AbortSignal cleanup to prevent EventTarget memory leaks
AbortSignalManager.emergencyCleanup();
console.log('โ
Emergency cleanup completed');
this.logMemoryUsage('post-cleanup');
}
/**
* Register a new session
*/
registerSession(sessionId, estimatedMemoryMB = 10) {
const now = Date.now();
this.sessionMemoryMap.set(sessionId, {
sessionId,
createdAt: now,
lastAccessed: now,
resourceCount: 0,
memoryFootprint: estimatedMemoryMB * 1024 * 1024
});
// Check if we're at session limit
if (this.sessionMemoryMap.size > this.MAX_SESSIONS) {
console.warn(`โ ๏ธ Session limit exceeded (${this.sessionMemoryMap.size}/${this.MAX_SESSIONS})`);
this.cleanupOldestSessions(5); // Remove 5 oldest sessions
}
}
/**
* Update session access time
*/
touchSession(sessionId) {
const session = this.sessionMemoryMap.get(sessionId);
if (session) {
session.lastAccessed = Date.now();
}
}
/**
* Add resource to session tracking
*/
addResource(sessionId, resourceMemoryMB = 5) {
const session = this.sessionMemoryMap.get(sessionId);
if (session) {
session.resourceCount++;
session.memoryFootprint += resourceMemoryMB * 1024 * 1024;
session.lastAccessed = Date.now();
}
}
/**
* Remove session and free memory tracking
*/
removeSession(sessionId) {
this.sessionMemoryMap.delete(sessionId);
}
/**
* Clean up expired sessions
*/
async cleanupExpiredSessions() {
const now = Date.now();
const expiredSessions = [];
for (const [sessionId, session] of this.sessionMemoryMap.entries()) {
if (now - session.lastAccessed > this.SESSION_TTL) {
expiredSessions.push(sessionId);
}
}
// Remove expired sessions
for (const sessionId of expiredSessions) {
this.sessionMemoryMap.delete(sessionId);
}
if (expiredSessions.length > 0) {
console.log(`๐งน Cleaned up ${expiredSessions.length} expired sessions`);
}
return expiredSessions;
}
/**
* Limit active sessions to most recent N sessions
*/
async limitActiveSessions(maxSessions) {
if (this.sessionMemoryMap.size <= maxSessions) {
return;
}
// Sort sessions by last accessed time (oldest first)
const sessions = Array.from(this.sessionMemoryMap.entries())
.sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed);
const sessionsToRemove = sessions.slice(0, this.sessionMemoryMap.size - maxSessions);
for (const [sessionId] of sessionsToRemove) {
this.sessionMemoryMap.delete(sessionId);
}
console.log(`๐ Limited sessions to ${maxSessions} (removed ${sessionsToRemove.length} oldest)`);
}
/**
* Clean up N oldest sessions
*/
cleanupOldestSessions(count) {
const sessions = Array.from(this.sessionMemoryMap.entries())
.sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed)
.slice(0, count);
for (const [sessionId] of sessions) {
this.sessionMemoryMap.delete(sessionId);
}
console.log(`๐งน Removed ${sessions.length} oldest sessions to free memory`);
}
/**
* Get memory usage statistics
*/
getMemoryStats() {
const usage = process.memoryUsage();
const current = {
heapUsed: usage.heapUsed,
heapTotal: usage.heapTotal,
external: usage.external,
rss: usage.rss,
timestamp: Date.now()
};
const sessions = Array.from(this.sessionMemoryMap.values());
const totalMemory = sessions.reduce((sum, s) => sum + s.memoryFootprint, 0);
const averageMemory = sessions.length > 0 ? totalMemory / sessions.length : 0;
const oldestSession = sessions.reduce((oldest, s) => !oldest || s.lastAccessed < oldest.lastAccessed ? s : oldest, undefined);
const heapUsagePercent = (current.heapUsed / current.heapTotal) * 100;
const heapUsedMB = current.heapUsed / 1024 / 1024;
return {
current,
sessions: {
total: sessions.length,
totalMemoryMB: Math.round(totalMemory / 1024 / 1024),
averageMemoryMB: Math.round(averageMemory / 1024 / 1024),
oldestSessionAge: oldestSession ? Date.now() - oldestSession.lastAccessed : 0
},
pressure: {
isUnderPressure: heapUsagePercent > (this.MEMORY_PRESSURE_THRESHOLD * 100) || heapUsedMB > 2048,
heapUsagePercent: Math.round(heapUsagePercent),
heapUsedMB: Math.round(heapUsedMB)
},
abortSignals: AbortSignalManager.getStats()
};
}
/**
* Log comprehensive memory usage
*/
logMemoryUsage(context = 'general') {
const stats = this.getMemoryStats();
console.log(`๐ Memory Statistics (${context}):`);
console.log(` Heap: ${stats.pressure.heapUsedMB} MB (${stats.pressure.heapUsagePercent}%)`);
console.log(` Sessions: ${stats.sessions.total} active (${stats.sessions.totalMemoryMB} MB)`);
console.log(` Average per session: ${stats.sessions.averageMemoryMB} MB`);
if (stats.pressure.isUnderPressure) {
console.log(` โ ๏ธ Memory pressure detected!`);
}
else {
console.log(` โ
Memory usage healthy`);
}
}
/**
* Get tool loading strategy based on memory availability
*/
getToolLoadingStrategy() {
const stats = this.getMemoryStats();
if (stats.pressure.heapUsedMB > 1500 || stats.sessions.total > 40) {
return {
strategy: 'minimal',
batchSize: 25,
reason: 'High memory pressure - loading essential tools only'
};
}
else if (stats.pressure.heapUsedMB > 800 || stats.sessions.total > 20) {
return {
strategy: 'batch',
batchSize: 100,
reason: 'Moderate memory usage - loading tools in batches'
};
}
else {
return {
strategy: 'all',
batchSize: 300,
reason: 'Low memory usage - loading all tools'
};
}
}
/**
* Optimize tool loading based on memory constraints
*/
shouldLoadToolCategory(category) {
const strategy = this.getToolLoadingStrategy();
// Essential categories that should always load
const essentialCategories = ['core', 'debug', 'session'];
if (essentialCategories.includes(category)) {
return true;
}
// Load all categories if memory allows
if (strategy.strategy === 'all') {
return true;
}
// In minimal mode, only load essentials
if (strategy.strategy === 'minimal') {
return false;
}
// In batch mode, load common categories
const commonCategories = ['flutter', 'performance', 'audit'];
return commonCategories.includes(category);
}
}
//# sourceMappingURL=advanced-memory-manager.js.map