Skip to main content
Glama
performance.ts8.07 kB
/* * This file is part of BrowserLoop. * * BrowserLoop is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * BrowserLoop is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with BrowserLoop. If not, see <https://www.gnu.org/licenses/>. */ /** * Performance testing and benchmarking utilities */ import type { ScreenshotService } from './screenshot-service.js'; import type { ScreenshotOptions } from './types.js'; export interface PerformanceMetrics { /** Total execution time in milliseconds */ totalTime: number; /** Time breakdown by operation */ breakdown: { browserInit?: number; navigation?: number; screenshot?: number; processing?: number; }; /** Memory usage metrics */ memory: { heapUsed: number; heapTotal: number; external: number; rss: number; }; /** Result size in bytes */ resultSize: number; /** Throughput (screenshots per second) */ throughput?: number; } export interface BenchmarkOptions { /** Number of iterations to run */ iterations: number; /** Warmup iterations before measurement */ warmup?: number; /** URLs to test (will cycle through them) */ testUrls: string[]; /** Screenshot options to use */ screenshotOptions?: Partial<ScreenshotOptions>; /** Whether to run concurrent tests */ concurrent?: boolean; /** Concurrency level (if concurrent is true) */ concurrency?: number; } export interface BenchmarkResult { /** Individual test metrics */ metrics: PerformanceMetrics[]; /** Statistical summary */ summary: { avg: number; min: number; max: number; median: number; p95: number; p99: number; stdDev: number; }; /** Throughput metrics */ throughput: { screenshotsPerSecond: number; avgBytesPerSecond: number; }; /** Test configuration */ config: BenchmarkOptions; } export class PerformanceTester { private service: ScreenshotService; constructor(service: ScreenshotService) { this.service = service; } /** * Measure performance of a single screenshot operation */ async measureScreenshot( options: ScreenshotOptions ): Promise<PerformanceMetrics> { const startTime = Date.now(); const startMemory = process.memoryUsage(); // Measure navigation time const navStart = Date.now(); const result = await this.service.takeScreenshot(options); const navEnd = Date.now(); const endTime = Date.now(); const endMemory = process.memoryUsage(); return { totalTime: endTime - startTime, breakdown: { navigation: navEnd - navStart, }, memory: { heapUsed: endMemory.heapUsed - startMemory.heapUsed, heapTotal: endMemory.heapTotal, external: endMemory.external, rss: endMemory.rss, }, resultSize: Buffer.from(result.data, 'base64').length, }; } /** * Run a comprehensive benchmark */ async runBenchmark(options: BenchmarkOptions): Promise<BenchmarkResult> { const { iterations, warmup = 3, testUrls, screenshotOptions = {}, concurrent = false, concurrency = 3, } = options; // Warmup phase console.log(`Running ${warmup} warmup iterations...`); for (let i = 0; i < warmup; i++) { const url = testUrls[i % testUrls.length]; if (url) { await this.service.takeScreenshot({ url, ...screenshotOptions }); } } console.log(`Running ${iterations} benchmark iterations...`); const startTime = Date.now(); const metrics: PerformanceMetrics[] = []; if (concurrent) { // Run concurrent tests const batches = Math.ceil(iterations / concurrency); for (let batch = 0; batch < batches; batch++) { const batchPromises: Promise<PerformanceMetrics>[] = []; const batchSize = Math.min( concurrency, iterations - batch * concurrency ); for (let i = 0; i < batchSize; i++) { const iterationIndex = batch * concurrency + i; const url = testUrls[iterationIndex % testUrls.length]; if (url) { batchPromises.push( this.measureScreenshot({ url, ...screenshotOptions }) ); } } const batchResults = await Promise.all(batchPromises); metrics.push(...batchResults); } } else { // Run sequential tests for (let i = 0; i < iterations; i++) { const url = testUrls[i % testUrls.length]; if (url) { const metric = await this.measureScreenshot({ url, ...screenshotOptions, }); metrics.push(metric); } } } const endTime = Date.now(); const totalTestTime = endTime - startTime; // Calculate statistics const times = metrics.map((m) => m.totalTime); const sizes = metrics.map((m) => m.resultSize); const summary = this.calculateStatistics(times); const totalSize = sizes.reduce((sum, size) => sum + size, 0); const throughput = { screenshotsPerSecond: (iterations * 1000) / totalTestTime, avgBytesPerSecond: (totalSize * 1000) / totalTestTime, }; return { metrics, summary, throughput, config: options, }; } /** * Calculate statistical summary */ private calculateStatistics(values: number[]) { if (values.length === 0) { return { avg: 0, min: 0, max: 0, median: 0, p95: 0, p99: 0, stdDev: 0, }; } const sorted = [...values].sort((a, b) => a - b); const sum = sorted.reduce((a, b) => a + b, 0); const avg = sum / sorted.length; const variance = sorted.reduce((acc, val) => acc + (val - avg) ** 2, 0) / sorted.length; const stdDev = Math.sqrt(variance); return { avg: Math.round(avg * 100) / 100, min: sorted[0] ?? 0, max: sorted[sorted.length - 1] ?? 0, median: sorted[Math.floor(sorted.length / 2)] ?? 0, p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0, p99: sorted[Math.floor(sorted.length * 0.99)] ?? 0, stdDev: Math.round(stdDev * 100) / 100, }; } /** * Compare performance between different configurations */ async compareConfigurations( configs: Array<{ name: string; options: BenchmarkOptions }> ): Promise<void> { console.log('Running performance comparison...\n'); const results: Array<{ name: string; result: BenchmarkResult }> = []; for (const config of configs) { console.log(`Testing configuration: ${config.name}`); const result = await this.runBenchmark(config.options); results.push({ name: config.name, result }); console.log( `Completed ${config.name}: ${result.summary.avg}ms avg, ${result.throughput.screenshotsPerSecond.toFixed(2)} shots/sec\n` ); } // Print comparison table console.log('Performance Comparison Results:'); console.log('================================'); console.log('Config Name\t\tAvg Time\tMin Time\tMax Time\tThroughput'); console.log('----------\t\t--------\t--------\t--------\t----------'); for (const { name, result } of results) { console.log( `${name.padEnd(15)}\t${result.summary.avg}ms\t\t${result.summary.min}ms\t\t${result.summary.max}ms\t\t${result.throughput.screenshotsPerSecond.toFixed(2)} shots/sec` ); } } } /** * Create a performance testing instance */ export function createPerformanceTester( service: ScreenshotService ): PerformanceTester { return new PerformanceTester(service); }

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/mattiasw/browserloop'

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