digital-samba-mcp-server
Version:
Digital Samba MCP Server - Model Context Protocol server for Digital Samba's video conferencing API
427 lines • 14.7 kB
JavaScript
/**
* Digital Samba MCP Server - Resource Optimizer Module
*
* This module provides optimization strategies for high-traffic scenarios,
* reducing resource usage and improving performance of the MCP server.
*
* Features include:
* - Request batching to reduce API calls
* - Resource prioritization
* - Response compression
* - Incremental data loading
* - Memory usage optimization
*
* @module resource-optimizer
* @author Digital Samba Team
* @version 0.1.0
*/
// Node.js built-in modules
import { EventEmitter } from 'events';
// Local modules
import logger from './logger.js';
import { MemoryCache } from './cache.js';
/**
* Resource Optimizer class
*
* Optimizes resource usage for high-traffic scenarios by batching requests,
* compressing responses, and managing memory usage.
*/
export class ResourceOptimizer extends EventEmitter {
/**
* Resource Optimizer constructor
* @param options Resource optimizer options
*/
constructor(options = {}) {
super();
this.batches = new Map();
this.batchTimers = new Map();
// Set default options
this.options = {
maxBatchSize: options.maxBatchSize || 10,
batchDelayMs: options.batchDelayMs || 50,
enableCompression: options.enableCompression !== false,
enableIncrementalLoading: options.enableIncrementalLoading !== false,
cacheTtl: options.cacheTtl || 60000, // 1 minute
maxMemoryUsage: options.maxMemoryUsage || 100 * 1024 * 1024, // 100MB
memoryCheckIntervalMs: options.memoryCheckIntervalMs || 30000 // 30 seconds
};
// Initialize cache
this.cache = new MemoryCache({
ttl: this.options.cacheTtl,
maxItems: 1000
});
logger.info('Resource Optimizer initialized', {
maxBatchSize: this.options.maxBatchSize,
batchDelayMs: this.options.batchDelayMs,
cacheTtl: this.options.cacheTtl
});
// Start memory monitoring
this.startMemoryMonitoring();
}
/**
* Start memory usage monitoring
*/
startMemoryMonitoring() {
// Clear existing timer
this.stopMemoryMonitoring();
// Start new timer
this.memoryCheckTimer = setInterval(() => {
this.checkMemoryUsage();
}, this.options.memoryCheckIntervalMs);
logger.debug('Memory monitoring started', {
interval: `${this.options.memoryCheckIntervalMs / 1000} seconds`,
maxMemoryUsage: `${Math.round(this.options.maxMemoryUsage / (1024 * 1024))}MB`
});
}
/**
* Stop memory usage monitoring
*/
stopMemoryMonitoring() {
if (this.memoryCheckTimer) {
clearInterval(this.memoryCheckTimer);
this.memoryCheckTimer = undefined;
logger.debug('Memory monitoring stopped');
}
}
/**
* Check memory usage and take action if needed
*/
checkMemoryUsage() {
try {
// Get memory usage
const memoryUsage = process.memoryUsage();
const heapUsed = memoryUsage.heapUsed;
logger.debug('Memory usage check', {
heapUsedMB: Math.round(heapUsed / (1024 * 1024)),
maxMemoryUsageMB: Math.round(this.options.maxMemoryUsage / (1024 * 1024)),
rss: Math.round(memoryUsage.rss / (1024 * 1024))
});
// Check if memory usage exceeds threshold
if (heapUsed > this.options.maxMemoryUsage) {
logger.warn('Memory usage exceeds threshold, clearing cache', {
heapUsedMB: Math.round(heapUsed / (1024 * 1024)),
maxMemoryUsageMB: Math.round(this.options.maxMemoryUsage / (1024 * 1024))
});
// Clear cache
this.cache.clear();
// Run garbage collection if available
if (global.gc) {
logger.debug('Triggering garbage collection');
global.gc();
}
// Emit event
this.emit('memory:exceeded', {
heapUsed,
maxMemoryUsage: this.options.maxMemoryUsage
});
}
}
catch (error) {
logger.error('Error checking memory usage', {
error: error instanceof Error ? error.message : String(error)
});
}
}
/**
* Batch a request
* @param batchId Batch identifier
* @param key Request key
* @param executor Function to execute the batch
* @returns Promise resolving to the result
*/
batchRequest(batchId, key, executor) {
// Try to get from cache first
const cached = this.cache.get(`batch:${batchId}`, key);
if (cached) {
logger.debug('Batch request cache hit', { batchId, key });
return Promise.resolve(cached.value);
}
return new Promise((resolve, reject) => {
// Get or create batch
let batch = this.batches.get(batchId);
if (!batch) {
batch = {
id: batchId,
keys: [],
resolvers: new Map(),
rejecters: new Map(),
createdAt: Date.now(),
processing: false
};
this.batches.set(batchId, batch);
// Schedule batch execution
const timerId = setTimeout(() => {
this.executeBatch(batchId, executor);
}, this.options.batchDelayMs);
this.batchTimers.set(batchId, timerId);
logger.debug('Created new batch', { batchId });
}
// Add to batch
batch.keys.push(key);
batch.resolvers.set(key, resolve);
batch.rejecters.set(key, reject);
logger.debug('Added request to batch', {
batchId,
key,
batchSize: batch.keys.length
});
// Execute immediately if batch size reached
if (batch.keys.length >= this.options.maxBatchSize && !batch.processing) {
// Clear scheduled execution
const timerId = this.batchTimers.get(batchId);
if (timerId) {
clearTimeout(timerId);
this.batchTimers.delete(batchId);
}
// Execute batch
this.executeBatch(batchId, executor);
}
});
}
/**
* Execute a batch
* @param batchId Batch identifier
* @param executor Function to execute the batch
*/
async executeBatch(batchId, executor) {
// Get batch
const batch = this.batches.get(batchId);
if (!batch) {
logger.warn('Attempted to execute non-existent batch', { batchId });
return;
}
// Clear timer
const timerId = this.batchTimers.get(batchId);
if (timerId) {
clearTimeout(timerId);
this.batchTimers.delete(batchId);
}
// Skip if already processing
if (batch.processing) {
return;
}
// Mark as processing
batch.processing = true;
logger.info('Executing batch', {
batchId,
keyCount: batch.keys.length,
batchAge: `${(Date.now() - batch.createdAt)}ms`
});
try {
// Get unique keys
const uniqueKeys = Array.from(new Set(batch.keys));
// Execute batch
const results = await executor(uniqueKeys);
// Process results
for (const key of batch.keys) {
const resolver = batch.resolvers.get(key);
if (!resolver)
continue;
const result = results.get(key);
if (result !== undefined) {
// Cache result
this.cache.set(`batch:${batchId}`, key, result);
// Resolve promise
resolver(result);
logger.debug('Batch request resolved', { batchId, key });
}
else {
// Not found, reject
const rejecter = batch.rejecters.get(key);
if (rejecter) {
rejecter(new Error(`No result found for key: ${key}`));
}
logger.debug('Batch request rejected (no result)', { batchId, key });
}
}
// Emit event
this.emit('batch:success', {
batchId,
keyCount: batch.keys.length,
uniqueKeyCount: uniqueKeys.length,
duration: Date.now() - batch.createdAt
});
}
catch (error) {
logger.error('Error executing batch', {
batchId,
error: error instanceof Error ? error.message : String(error)
});
// Reject all promises
for (const key of batch.keys) {
const rejecter = batch.rejecters.get(key);
if (rejecter) {
rejecter(error);
}
}
// Emit event
this.emit('batch:error', {
batchId,
error: error instanceof Error ? error.message : String(error),
keyCount: batch.keys.length
});
}
finally {
// Remove batch
this.batches.delete(batchId);
logger.debug('Batch execution completed', { batchId });
}
}
/**
* Check if a batch is in progress
* @param batchId Batch identifier
* @returns Whether the batch is in progress
*/
isBatchInProgress(batchId) {
return this.batches.has(batchId);
}
/**
* Cancel a batch
* @param batchId Batch identifier
* @returns Whether the batch was cancelled
*/
cancelBatch(batchId) {
// Get batch
const batch = this.batches.get(batchId);
if (!batch) {
return false;
}
// Clear timer
const timerId = this.batchTimers.get(batchId);
if (timerId) {
clearTimeout(timerId);
this.batchTimers.delete(batchId);
}
// Reject all promises
for (const key of batch.keys) {
const rejecter = batch.rejecters.get(key);
if (rejecter) {
rejecter(new Error('Batch cancelled'));
}
}
// Remove batch
this.batches.delete(batchId);
logger.info('Batch cancelled', { batchId, keyCount: batch.keys.length });
// Emit event
this.emit('batch:cancelled', {
batchId,
keyCount: batch.keys.length
});
return true;
}
/**
* Compress a response if compression is enabled
* @param data Data to compress
* @returns Compressed data or original data if compression disabled
*/
compressResponse(data) {
if (!this.options.enableCompression) {
return data;
}
try {
// Simple compression for demonstration
// In a real implementation, use a proper compression algorithm
if (typeof data === 'object' && data !== null) {
// Remove null and undefined values
const compressed = Object.fromEntries(Object.entries(data).filter(([_, v]) => v != null));
return compressed;
}
return data;
}
catch (error) {
logger.warn('Error compressing response', {
error: error instanceof Error ? error.message : String(error)
});
// Return original data on error
return data;
}
}
/**
* Load data incrementally
* @param dataLoader Function to load data
* @param pageSize Page size
* @param maxPages Maximum number of pages to load
* @returns Promise resolving to the loaded data
*/
async loadIncrementally(dataLoader, pageSize = 20, maxPages = 10) {
if (!this.options.enableIncrementalLoading) {
// Load all data at once
const result = await dataLoader(1, pageSize * maxPages);
return result.data;
}
const allData = [];
let page = 1;
let total = 0;
do {
// Load page
const result = await dataLoader(page, pageSize);
const data = result.data;
total = result.total;
// Add to results
allData.push(...data);
// Emit progress event
this.emit('incremental:progress', {
page,
loaded: allData.length,
total,
percentage: Math.min(100, Math.round((allData.length / total) * 100))
});
// Check if we're done
if (data.length < pageSize || allData.length >= total || page >= maxPages) {
break;
}
// Next page
page++;
} while (true);
// Emit completion event
this.emit('incremental:complete', {
pages: page,
loaded: allData.length,
total
});
return allData;
}
/**
* Get statistics for the resource optimizer
* @returns Resource optimizer statistics
*/
getStats() {
return {
activeBatches: this.batches.size,
cacheStats: this.cache.getStats(),
memoryUsage: process.memoryUsage()
};
}
/**
* Clean up resources
*/
destroy() {
// Clear batches
for (const batchId of this.batches.keys()) {
this.cancelBatch(batchId);
}
// Clear cache
this.cache.clear();
// Stop memory monitoring
this.stopMemoryMonitoring();
// Remove all listeners
this.removeAllListeners();
logger.info('Resource Optimizer destroyed');
}
}
/**
* Create a resource optimizer
* @param options Resource optimizer options
* @returns A new resource optimizer instance
*/
export function createResourceOptimizer(options = {}) {
return new ResourceOptimizer(options);
}
/**
* Export default resource optimizer utilities
*/
export default {
ResourceOptimizer,
createResourceOptimizer
};
//# sourceMappingURL=resource-optimizer.js.map