@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
JavaScript
/**
* 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