Skip to main content
Glama
browser-performance-optimizer.tsโ€ข20.1 kB
/** * Browser Performance Optimizer * Task 4.3: Performance Optimization - Browser Automation Performance * * Optimizes browser automation performance for the educational content system: * - Browser instance pooling and reuse * - Page lifecycle optimization * - Script injection optimization * - Resource loading optimization * - Connection management */ import { EventEmitter } from 'events'; export interface BrowserPoolConfig { min_instances: number; max_instances: number; idle_timeout: number; launch_timeout: number; page_timeout: number; enable_resource_blocking: boolean; optimization_level: 'minimal' | 'standard' | 'aggressive'; } export interface BrowserPerformanceMetrics { startup_time: number; navigation_time: number; script_execution_time: number; memory_usage: number; page_load_time: number; resource_count: number; } export interface OptimizedBrowserInstance { id: string; browser: any; created_at: number; last_used: number; page_count: number; memory_usage: number; status: 'idle' | 'busy' | 'closing'; } export class BrowserPerformanceOptimizer extends EventEmitter { private config: BrowserPoolConfig; private browserPool: Map<string, OptimizedBrowserInstance> = new Map(); private performanceMetrics: BrowserPerformanceMetrics[] = []; private poolMaintenanceInterval: NodeJS.Timeout | null = null; private resourceBlockList: string[] = []; constructor(config: BrowserPoolConfig) { super(); this.config = config; this.initializeOptimizer(); } private initializeOptimizer(): void { console.log('๐Ÿš€ Initializing Browser Performance Optimizer...'); // Setup resource blocking list this.setupResourceBlocking(); // Initialize browser pool with minimum instances this.initializeBrowserPool(); // Setup pool maintenance this.setupPoolMaintenance(); console.log('โœ… Browser performance optimizer initialized'); } /** * Gets an optimized browser instance from the pool */ public async getOptimizedBrowser(): Promise<{ browser: any; instance_id: string; performance: Partial<BrowserPerformanceMetrics>; }> { const startTime = Date.now(); // Try to get idle browser from pool let instance = this.getIdleBrowserFromPool(); if (!instance) { // Create new browser if pool allows if (this.browserPool.size < this.config.max_instances) { instance = await this.createOptimizedBrowserInstance(); } else { // Wait for available browser or create if critical instance = await this.waitForAvailableBrowser(); } } // Mark as busy instance.status = 'busy'; instance.last_used = Date.now(); const performance: Partial<BrowserPerformanceMetrics> = { startup_time: Date.now() - startTime }; this.emit('browser-acquired', { instance_id: instance.id, performance }); return { browser: instance.browser, instance_id: instance.id, performance }; } /** * Returns browser instance to pool */ public async returnBrowserToPool(instanceId: string): Promise<void> { const instance = this.browserPool.get(instanceId); if (!instance) { console.warn(`โš ๏ธ Browser instance ${instanceId} not found in pool`); return; } // Update instance status instance.status = 'idle'; instance.last_used = Date.now(); // Clean up pages if too many await this.cleanupBrowserPages(instance); this.emit('browser-returned', { instance_id: instanceId }); } /** * Creates an optimized page with performance enhancements */ public async createOptimizedPage(browser: any): Promise<{ page: any; performance: Partial<BrowserPerformanceMetrics>; }> { const startTime = Date.now(); // Create page with optimized settings const page = await browser.newPage(); // Apply optimization settings await this.applyPageOptimizations(page); const performance: Partial<BrowserPerformanceMetrics> = { startup_time: Date.now() - startTime }; return { page, performance }; } /** * Optimized navigation with performance tracking */ public async optimizedNavigate( page: any, url: string, options?: any ): Promise<{ response: any; performance: Partial<BrowserPerformanceMetrics>; }> { const startTime = Date.now(); // Pre-navigation optimizations await this.preNavigationOptimizations(page); // Navigate with timeout and optimizations const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: this.config.page_timeout, ...options }); const navigationTime = Date.now() - startTime; // Post-navigation optimizations await this.postNavigationOptimizations(page); const performance: Partial<BrowserPerformanceMetrics> = { navigation_time: navigationTime, page_load_time: navigationTime }; this.emit('navigation-completed', { url, performance }); return { response, performance }; } /** * Optimized script execution with performance tracking */ public async executeOptimizedScript( page: any, script: string | Function, args?: any[] ): Promise<{ result: any; performance: Partial<BrowserPerformanceMetrics>; }> { const startTime = Date.now(); // Optimize script for execution const optimizedScript = this.optimizeScript(script); // Execute with timeout const result = await page.evaluate(optimizedScript, ...(args || [])); const performance: Partial<BrowserPerformanceMetrics> = { script_execution_time: Date.now() - startTime }; return { result, performance }; } /** * Comprehensive performance analysis */ public async analyzePerformance( page: any ): Promise<{ metrics: BrowserPerformanceMetrics; recommendations: string[]; score: number; }> { const performanceEntries = await page.evaluate(() => { return JSON.stringify(performance.getEntriesByType('navigation')); }); const memoryInfo = await page.evaluate(() => { return (performance as any).memory ? { usedJSHeapSize: (performance as any).memory.usedJSHeapSize, totalJSHeapSize: (performance as any).memory.totalJSHeapSize, jsHeapSizeLimit: (performance as any).memory.jsHeapSizeLimit } : null; }); const resourceEntries = await page.evaluate(() => { return performance.getEntriesByType('resource').length; }); const navigationData = JSON.parse(performanceEntries)[0] || {}; const metrics: BrowserPerformanceMetrics = { startup_time: navigationData.domComplete - navigationData.navigationStart || 0, navigation_time: navigationData.loadEventEnd - navigationData.navigationStart || 0, script_execution_time: navigationData.domContentLoadedEventEnd - navigationData.domContentLoadedEventStart || 0, memory_usage: memoryInfo ? Math.round(memoryInfo.usedJSHeapSize / 1024 / 1024) : 0, page_load_time: navigationData.loadEventEnd - navigationData.fetchStart || 0, resource_count: resourceEntries }; const { recommendations, score } = this.generatePerformanceReport(metrics); this.performanceMetrics.push(metrics); return { metrics, recommendations, score }; } /** * Pool status and management */ public getPoolStatus(): { total_instances: number; idle_instances: number; busy_instances: number; pool_utilization: number; average_age: number; } { const instances = Array.from(this.browserPool.values()); const idleCount = instances.filter(i => i.status === 'idle').length; const busyCount = instances.filter(i => i.status === 'busy').length; const now = Date.now(); const averageAge = instances.length > 0 ? instances.reduce((sum, i) => sum + (now - i.created_at), 0) / instances.length / 1000 : 0; return { total_instances: instances.length, idle_instances: idleCount, busy_instances: busyCount, pool_utilization: busyCount / Math.max(instances.length, 1), average_age: Math.round(averageAge) }; } // Private optimization methods private async initializeBrowserPool(): Promise<void> { console.log(`๐Ÿ”ง Initializing browser pool with ${this.config.min_instances} instances...`); for (let i = 0; i < this.config.min_instances; i++) { try { await this.createOptimizedBrowserInstance(); } catch (error) { console.error(`โŒ Failed to create browser instance ${i}:`, error); } } console.log(`โœ… Browser pool initialized with ${this.browserPool.size} instances`); } private async createOptimizedBrowserInstance(): Promise<OptimizedBrowserInstance> { const instanceId = `browser_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const startTime = Date.now(); const launchOptions = this.getBrowserLaunchOptions(); // Mock browser creation for demonstration const browser = { newPage: async () => ({ goto: async (url: string, options?: any) => ({ status: 200 }), evaluate: async (script: any, ...args: any[]) => 'result', setRequestInterception: async (value: boolean) => {}, on: (event: string, handler: Function) => {}, close: async () => {} }), close: async () => {}, pages: async () => [], process: () => ({ pid: Math.floor(Math.random() * 10000) }) }; const instance: OptimizedBrowserInstance = { id: instanceId, browser, created_at: Date.now(), last_used: Date.now(), page_count: 0, memory_usage: 50, // MB estimate status: 'idle' }; this.browserPool.set(instanceId, instance); console.log(`โœ… Created optimized browser instance ${instanceId} in ${Date.now() - startTime}ms`); return instance; } private getIdleBrowserFromPool(): OptimizedBrowserInstance | null { for (const instance of this.browserPool.values()) { if (instance.status === 'idle') { return instance; } } return null; } private async waitForAvailableBrowser(): Promise<OptimizedBrowserInstance> { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Timeout waiting for available browser')); }, this.config.launch_timeout); const checkForAvailable = () => { const available = this.getIdleBrowserFromPool(); if (available) { clearTimeout(timeout); resolve(available); } else { setTimeout(checkForAvailable, 100); } }; checkForAvailable(); }); } private async cleanupBrowserPages(instance: OptimizedBrowserInstance): Promise<void> { try { const pages = await instance.browser.pages(); // Close excess pages (keep only 1-2) if (pages.length > 2) { const pagesToClose = pages.slice(2); await Promise.all(pagesToClose.map((page: any) => page.close())); console.log(`๐Ÿงน Cleaned up ${pagesToClose.length} pages from browser ${instance.id}`); } instance.page_count = Math.max(0, pages.length - pagesToClose.length); } catch (error) { console.error(`โŒ Error cleaning browser pages:`, error); } } private getBrowserLaunchOptions(): any { const baseOptions = { headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-web-security', '--disable-features=VizDisplayCompositor' ] }; // Apply optimization level specific settings switch (this.config.optimization_level) { case 'aggressive': return { ...baseOptions, args: [ ...baseOptions.args, '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-features=TranslateUI', '--disable-default-apps', '--disable-extensions', '--no-first-run', '--disable-background-mode', '--memory-pressure-off' ] }; case 'standard': return { ...baseOptions, args: [ ...baseOptions.args, '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows' ] }; case 'minimal': default: return baseOptions; } } private async applyPageOptimizations(page: any): Promise<void> { // Enable request interception for resource blocking if (this.config.enable_resource_blocking) { await page.setRequestInterception(true); page.on('request', (request: any) => { const resourceType = request.resourceType(); const url = request.url(); // Block unnecessary resources if (this.shouldBlockResource(resourceType, url)) { request.abort(); } else { request.continue(); } }); } // Set optimized viewport await page.setViewport({ width: 1366, height: 768, deviceScaleFactor: 1 }); // Disable images and CSS if aggressive optimization if (this.config.optimization_level === 'aggressive') { await page.setJavaScriptEnabled(true); // Note: In real implementation, you'd disable images/CSS here } } private async preNavigationOptimizations(page: any): Promise<void> { // Clear any existing cache or storage await page.evaluate(() => { if (typeof localStorage !== 'undefined') { localStorage.clear(); } if (typeof sessionStorage !== 'undefined') { sessionStorage.clear(); } }); } private async postNavigationOptimizations(page: any): Promise<void> { // Wait for critical resources to load await page.waitForTimeout(100); // Remove unnecessary DOM elements for memory optimization if (this.config.optimization_level === 'aggressive') { await page.evaluate(() => { // Remove ads, tracking scripts, etc. const ads = document.querySelectorAll('[class*="ad"], [id*="ad"], [class*="banner"]'); ads.forEach(ad => ad.remove()); }); } } private optimizeScript(script: string | Function): string | Function { if (typeof script === 'function') { return script; } // Basic script optimization return script .replace(/console\.log\([^)]*\);?/g, '') // Remove console.log .replace(/debugger;?/g, '') // Remove debugger statements .replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments .trim(); } private setupResourceBlocking(): void { this.resourceBlockList = [ // Analytics and tracking 'google-analytics.com', 'googletagmanager.com', 'facebook.com/tr', 'doubleclick.net', // Ads 'googlesyndication.com', 'adsystem.amazon.com', 'amazon-adsystem.com', // Social media widgets 'connect.facebook.net', 'platform.twitter.com', 'apis.google.com/js/platform.js' ]; } private shouldBlockResource(resourceType: string, url: string): boolean { if (!this.config.enable_resource_blocking) return false; // Block based on resource type if (['image', 'media', 'font'].includes(resourceType) && this.config.optimization_level === 'aggressive') { return true; } // Block based on URL patterns return this.resourceBlockList.some(pattern => url.includes(pattern)); } private setupPoolMaintenance(): void { this.poolMaintenanceInterval = setInterval(() => { this.performPoolMaintenance(); }, 60000); // Every minute } private async performPoolMaintenance(): Promise<void> { const now = Date.now(); const instancesToClose: string[] = []; // Find idle instances that have timed out for (const [id, instance] of this.browserPool.entries()) { if (instance.status === 'idle' && now - instance.last_used > this.config.idle_timeout && this.browserPool.size > this.config.min_instances) { instancesToClose.push(id); } } // Close timed out instances for (const id of instancesToClose) { await this.closeBrowserInstance(id); } // Ensure minimum pool size while (this.browserPool.size < this.config.min_instances) { try { await this.createOptimizedBrowserInstance(); } catch (error) { console.error('โŒ Failed to maintain minimum pool size:', error); break; } } if (instancesToClose.length > 0) { console.log(`๐Ÿงน Pool maintenance: closed ${instancesToClose.length} idle instances`); } } private async closeBrowserInstance(instanceId: string): Promise<void> { const instance = this.browserPool.get(instanceId); if (!instance) return; try { instance.status = 'closing'; await instance.browser.close(); this.browserPool.delete(instanceId); console.log(`๐Ÿ”š Closed browser instance ${instanceId}`); } catch (error) { console.error(`โŒ Error closing browser instance ${instanceId}:`, error); } } private generatePerformanceReport(metrics: BrowserPerformanceMetrics): { recommendations: string[]; score: number; } { const recommendations: string[] = []; let score = 100; // Analyze navigation time if (metrics.navigation_time > 5000) { recommendations.push('Navigation time is high - consider optimizing page load resources'); score -= 15; } else if (metrics.navigation_time > 3000) { recommendations.push('Navigation time could be improved'); score -= 5; } // Analyze memory usage if (metrics.memory_usage > 100) { recommendations.push('High memory usage detected - consider cleanup'); score -= 20; } else if (metrics.memory_usage > 50) { recommendations.push('Memory usage is moderate - monitor closely'); score -= 5; } // Analyze resource count if (metrics.resource_count > 100) { recommendations.push('High resource count - enable resource blocking'); score -= 10; } // Analyze script execution time if (metrics.script_execution_time > 1000) { recommendations.push('Script execution is slow - optimize scripts'); score -= 10; } if (recommendations.length === 0) { recommendations.push('Performance is optimal'); } return { recommendations, score: Math.max(0, score) }; } /** * Public methods for configuration and control */ public updateConfig(newConfig: Partial<BrowserPoolConfig>): void { this.config = { ...this.config, ...newConfig }; console.log('๐Ÿ”ง Browser optimizer config updated'); } public getPerformanceMetrics(): BrowserPerformanceMetrics[] { return [...this.performanceMetrics]; } public clearMetrics(): void { this.performanceMetrics = []; console.log('๐Ÿงน Browser performance metrics cleared'); } public async closeAllBrowsers(): Promise<void> { console.log('๐Ÿ”š Closing all browser instances...'); const closePromises = Array.from(this.browserPool.keys()).map(id => this.closeBrowserInstance(id) ); await Promise.all(closePromises); if (this.poolMaintenanceInterval) { clearInterval(this.poolMaintenanceInterval); } console.log('โœ… All browser instances closed'); } } // Default configuration export const DEFAULT_BROWSER_CONFIG: BrowserPoolConfig = { min_instances: 2, max_instances: 5, idle_timeout: 300000, // 5 minutes launch_timeout: 30000, // 30 seconds page_timeout: 30000, // 30 seconds enable_resource_blocking: true, optimization_level: 'standard' };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rkm097git/euconquisto-composer-mcp-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server