UNPKG

@dorothywebb/any-browser-mcp

Version:

Any Browser MCP - Launch Chrome with your actual data in debug mode for comprehensive browser automation

315 lines โ€ข 10.4 kB
/** * Resource Manager for Any Browser MCP * * @fileoverview Manages system resources including memory usage, connection limits, * and performance monitoring. Provides automatic resource cleanup and optimization. * * @example * ```typescript * import { ResourceManager } from './ResourceManager.js'; * * const resourceManager = ResourceManager.getInstance(); * * // Monitor resource usage * const usage = resourceManager.getResourceUsage(); * console.log(`Memory usage: ${usage.memoryUsage.heapUsed / 1024 / 1024} MB`); * * // Register a resource for cleanup * resourceManager.registerResource('connection-1', async () => { * await connection.close(); * }); * ``` * * @category Utilities */ import { EventEmitter } from 'events'; import { ErrorFactory } from '../types/errors.js'; import { handleError } from './ErrorHandler.js'; import { ConfigManager } from '../core/ConfigManager.js'; /** * Resource manager for monitoring and managing system resources. * * @description This class provides centralized resource management including * memory monitoring, connection tracking, automatic cleanup, and performance * optimization. It helps prevent resource leaks and maintains system stability. */ export class ResourceManager extends EventEmitter { static instance; resources = new Map(); limits; config; monitoringInterval; startTime; activeConnections = 0; /** * Private constructor to enforce singleton pattern. */ constructor() { super(); this.config = ConfigManager.getInstance(); this.startTime = new Date(); this.limits = { maxMemoryUsage: 512 * 1024 * 1024, // 512 MB maxConnections: this.config.getServerConfig().maxConcurrentConnections, maxCpuUsage: 80, // 80% memoryCleanupThreshold: 75 // 75% }; this.startMonitoring(); this.setupProcessHandlers(); } /** * Gets the singleton instance of ResourceManager. * * @returns The singleton ResourceManager instance */ static getInstance() { if (!ResourceManager.instance) { ResourceManager.instance = new ResourceManager(); } return ResourceManager.instance; } /** * Registers a resource for automatic cleanup. * * @param id - Unique identifier for the resource * @param cleanup - Function to call when cleaning up the resource * @param type - Type/category of the resource * @param metadata - Optional metadata about the resource * * @example * ```typescript * resourceManager.registerResource( * 'browser-connection-1', * async () => await connection.close(), * 'connection', * { port: 9223, pid: 12345 } * ); * ``` */ registerResource(id, cleanup, type = 'unknown', metadata) { const resource = { id, cleanup, registeredAt: new Date(), type, metadata }; this.resources.set(id, resource); if (this.config.isVerbose()) { console.log(`๐Ÿ“‹ Registered resource: ${id} (${type})`); } } /** * Unregisters a resource. * * @param id - Unique identifier of the resource to unregister * @param cleanup - Whether to run cleanup function before unregistering * * @example * ```typescript * await resourceManager.unregisterResource('browser-connection-1', true); * ``` */ async unregisterResource(id, cleanup = false) { const resource = this.resources.get(id); if (!resource) return; if (cleanup) { try { await resource.cleanup(); } catch (error) { handleError(ErrorFactory.createDataError('resource-cleanup', `Failed to cleanup resource ${id}: ${error.message}`, { details: { resourceId: id, resourceType: resource.type } }, error)); } } this.resources.delete(id); if (this.config.isVerbose()) { console.log(`๐Ÿ—‘๏ธ Unregistered resource: ${id}`); } } /** * Gets current resource usage information. * * @returns Current resource usage statistics */ getResourceUsage() { return { memoryUsage: process.memoryUsage(), activeConnections: this.activeConnections, registeredResources: this.resources.size, uptime: Date.now() - this.startTime.getTime(), timestamp: new Date() }; } /** * Sets resource limits. * * @param limits - New resource limits to apply * * @example * ```typescript * resourceManager.setLimits({ * maxMemoryUsage: 1024 * 1024 * 1024, // 1 GB * maxConnections: 10 * }); * ``` */ setLimits(limits) { this.limits = { ...this.limits, ...limits }; if (this.config.isVerbose()) { console.log('๐Ÿ“Š Updated resource limits:', this.limits); } } /** * Gets current resource limits. * * @returns Current resource limits */ getLimits() { return { ...this.limits }; } /** * Increments the active connection count. */ incrementConnections() { this.activeConnections++; } /** * Decrements the active connection count. */ decrementConnections() { this.activeConnections = Math.max(0, this.activeConnections - 1); } /** * Forces garbage collection if available. * * @returns Whether garbage collection was triggered */ forceGarbageCollection() { if (global.gc) { const beforeUsage = process.memoryUsage().heapUsed; global.gc(); const afterUsage = process.memoryUsage().heapUsed; this.emit('memory-cleanup', beforeUsage, afterUsage); if (this.config.isVerbose()) { const saved = (beforeUsage - afterUsage) / 1024 / 1024; console.log(`๐Ÿงน Garbage collection freed ${saved.toFixed(2)} MB`); } return true; } return false; } /** * Cleans up all registered resources. * * @param type - Optional type filter for cleanup * * @example * ```typescript * // Clean up all resources * await resourceManager.cleanupResources(); * * // Clean up only connection resources * await resourceManager.cleanupResources('connection'); * ``` */ async cleanupResources(type) { const resourcesToCleanup = Array.from(this.resources.values()).filter(resource => !type || resource.type === type); let cleanedCount = 0; const cleanupPromises = resourcesToCleanup.map(async (resource) => { try { await resource.cleanup(); this.resources.delete(resource.id); cleanedCount++; } catch (error) { handleError(ErrorFactory.createDataError('resource-cleanup', `Failed to cleanup resource ${resource.id}: ${error.message}`, { details: { resourceId: resource.id, resourceType: resource.type } }, error)); } }); await Promise.all(cleanupPromises); this.emit('resources-cleaned', cleanedCount); if (this.config.isVerbose()) { console.log(`๐Ÿงน Cleaned up ${cleanedCount} resources`); } } /** * Shuts down the resource manager and cleans up all resources. */ async shutdown() { if (this.monitoringInterval) { clearInterval(this.monitoringInterval); } await this.cleanupResources(); if (this.config.isVerbose()) { console.log('๐Ÿ”Œ Resource manager shut down'); } } /** * Gets a list of all registered resources. * * @param type - Optional type filter * @returns Array of registered resources */ getRegisteredResources(type) { const resources = Array.from(this.resources.values()); return type ? resources.filter(r => r.type === type) : resources; } /** * Starts resource monitoring. */ startMonitoring() { this.monitoringInterval = setInterval(() => { this.checkResourceUsage(); }, 30000); // Check every 30 seconds } /** * Checks current resource usage against limits. */ checkResourceUsage() { const usage = this.getResourceUsage(); // Check memory usage if (this.limits.maxMemoryUsage && usage.memoryUsage.heapUsed > this.limits.maxMemoryUsage) { this.emit('resource-limit-exceeded', usage, 'maxMemoryUsage'); // Trigger cleanup if threshold exceeded const memoryUsagePercent = (usage.memoryUsage.heapUsed / this.limits.maxMemoryUsage) * 100; if (this.limits.memoryCleanupThreshold && memoryUsagePercent > this.limits.memoryCleanupThreshold) { this.forceGarbageCollection(); } } // Check connection count if (this.limits.maxConnections && usage.activeConnections > this.limits.maxConnections) { this.emit('resource-limit-exceeded', usage, 'maxConnections'); } // Emit warnings for high resource usage const memoryUsageMB = usage.memoryUsage.heapUsed / 1024 / 1024; if (memoryUsageMB > 256) { // Warn at 256 MB this.emit('resource-warning', `High memory usage: ${memoryUsageMB.toFixed(2)} MB`, usage); } } /** * Sets up process event handlers for cleanup. */ setupProcessHandlers() { const cleanup = async () => { try { await this.shutdown(); } catch (error) { console.error('Error during resource manager shutdown:', error); } }; process.on('SIGINT', cleanup); process.on('SIGTERM', cleanup); process.on('exit', () => { // Synchronous cleanup only if (this.monitoringInterval) { clearInterval(this.monitoringInterval); } }); } } /** * Global resource manager instance. */ export const globalResourceManager = ResourceManager.getInstance(); //# sourceMappingURL=ResourceManager.js.map