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'
};