Skip to main content
Glama

Curupira

by drzln
benchmark.test.ts15.7 kB
/** * Performance benchmarks for Curupira MCP Server * Measures response times, memory usage, and throughput */ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest' import { CurupiraServer } from '../../server.js' import { ChromeManager } from '../../chrome/manager.js' import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js' import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { performance } from 'perf_hooks' import { mockChromeClient, resetAllMocks, createCDPResponse } from '../setup.js' // Mock ChromeManager for consistent benchmarks vi.mock('../../chrome/manager.js', () => ({ ChromeManager: { getInstance: vi.fn(() => ({ getClient: () => mockChromeClient, connect: vi.fn().mockResolvedValue(undefined), disconnect: vi.fn().mockResolvedValue(undefined), getAllSessions: vi.fn().mockReturnValue([ { id: 'test-session', title: 'Test Page', url: 'https://example.com' } ]), })), }, })) interface BenchmarkResult { operation: string averageTime: number minTime: number maxTime: number throughput: number memoryUsed: number } describe('Performance Benchmarks', () => { let server: CurupiraServer let transport: InMemoryTransport let client: Client const results: BenchmarkResult[] = [] beforeAll(async () => { resetAllMocks() // Create server and client server = new CurupiraServer() transport = new InMemoryTransport() await server.connectTransport(transport.serverTransport) client = new Client({ name: 'benchmark-client', version: '1.0.0', }) await client.connect(transport.clientTransport) }) afterAll(async () => { await client?.close() await server?.close() // Print benchmark results console.log('\n=== Curupira Performance Benchmark Results ===\n') console.table(results.map(r => ({ Operation: r.operation, 'Avg Time (ms)': r.averageTime.toFixed(2), 'Min Time (ms)': r.minTime.toFixed(2), 'Max Time (ms)': r.maxTime.toFixed(2), 'Throughput (ops/sec)': r.throughput.toFixed(0), 'Memory (MB)': (r.memoryUsed / 1024 / 1024).toFixed(2), }))) }) beforeEach(() => { resetAllMocks() }) async function benchmark( name: string, operation: () => Promise<any>, iterations: number = 100 ): Promise<void> { const times: number[] = [] const memStart = process.memoryUsage().heapUsed // Warm up for (let i = 0; i < 10; i++) { await operation() } // Actual benchmark for (let i = 0; i < iterations; i++) { const start = performance.now() await operation() const end = performance.now() times.push(end - start) } const memEnd = process.memoryUsage().heapUsed const memoryUsed = memEnd - memStart const averageTime = times.reduce((a, b) => a + b, 0) / times.length const minTime = Math.min(...times) const maxTime = Math.max(...times) const throughput = 1000 / averageTime // operations per second results.push({ operation: name, averageTime, minTime, maxTime, throughput, memoryUsed, }) // Basic assertions to ensure reasonable performance expect(averageTime).toBeLessThan(100) // Should complete in less than 100ms expect(throughput).toBeGreaterThan(10) // At least 10 ops/sec } describe('Resource Operations', () => { it('should benchmark resources/list', async () => { // Mock responses for framework detection mockChromeClient.send .mockResolvedValue(createCDPResponse({ result: { value: true } })) await benchmark('resources/list', async () => { const response = await client.request({ method: 'resources/list', }) return response }) }) it('should benchmark resources/read for CDP resources', async () => { mockChromeClient.send .mockResolvedValueOnce(undefined) // Runtime.enable .mockResolvedValueOnce( createCDPResponse({ result: { value: { messages: Array(50).fill({ type: 'log', text: 'Test message', timestamp: Date.now(), }), }, }, }) ) await benchmark('resources/read (CDP)', async () => { const response = await client.request({ method: 'resources/read', params: { uri: 'cdp://runtime/console', }, }) return response }) }) it('should benchmark resources/read for React resources', async () => { mockChromeClient.send .mockResolvedValueOnce(undefined) // Runtime.enable .mockResolvedValueOnce( createCDPResponse({ result: { value: { components: Array(20).fill({ name: 'TestComponent', type: 'function', props: { id: 'test', value: 123 }, }), }, }, }) ) await benchmark('resources/read (React)', async () => { const response = await client.request({ method: 'resources/read', params: { uri: 'react://components', }, }) return response }) }) }) describe('Tool Operations', () => { it('should benchmark tools/list', async () => { await benchmark('tools/list', async () => { const response = await client.request({ method: 'tools/list', }) return response }) }) it('should benchmark evaluate tool', async () => { mockChromeClient.send .mockResolvedValueOnce(undefined) // Runtime.enable .mockResolvedValueOnce( createCDPResponse({ result: { type: 'string', value: 'Test Result', }, }) ) await benchmark('tools/call (evaluate)', async () => { const response = await client.request({ method: 'tools/call', params: { name: 'evaluate', arguments: { expression: 'document.title', }, }, }) return response }) }) it('should benchmark DOM query operations', async () => { mockChromeClient.send .mockResolvedValueOnce(undefined) // DOM.enable .mockResolvedValueOnce(undefined) // DOM.getDocument .mockResolvedValueOnce( createCDPResponse({ nodeId: 123, }) ) await benchmark('tools/call (dom_query)', async () => { const response = await client.request({ method: 'tools/call', params: { name: 'dom_query_selector', arguments: { selector: '.test-class', }, }, }) return response }) }) it('should benchmark React component finding', async () => { mockChromeClient.send .mockResolvedValueOnce(undefined) // Runtime.enable .mockResolvedValueOnce( createCDPResponse({ result: { value: { found: true, components: [ { id: 'comp-1', name: 'TestComponent', props: {} }, ], }, }, }) ) await benchmark('tools/call (react_find)', async () => { const response = await client.request({ method: 'tools/call', params: { name: 'react_find_component', arguments: { componentName: 'TestComponent', }, }, }) return response }) }) it('should benchmark network interception setup', async () => { mockChromeClient.send .mockResolvedValueOnce(undefined) // Network.enable .mockResolvedValueOnce(undefined) // Fetch.enable .mockResolvedValueOnce(undefined) // Network.setRequestInterception await benchmark('tools/call (network)', async () => { const response = await client.request({ method: 'tools/call', params: { name: 'network_enable_request_interception', arguments: {}, }, }) return response }) }) }) describe('Complex Operations', () => { it('should benchmark multiple resource reads', async () => { // Setup mocks for multiple resource types const setupMocks = () => { mockChromeClient.send // First resource .mockResolvedValueOnce(undefined) .mockResolvedValueOnce( createCDPResponse({ result: { value: { data: 'test1' } }, }) ) // Second resource .mockResolvedValueOnce(undefined) .mockResolvedValueOnce( createCDPResponse({ result: { value: { data: 'test2' } }, }) ) // Third resource .mockResolvedValueOnce(undefined) .mockResolvedValueOnce( createCDPResponse({ result: { value: { data: 'test3' } }, }) ) } await benchmark('multiple resources', async () => { setupMocks() const promises = [ client.request({ method: 'resources/read', params: { uri: 'cdp://runtime/console' }, }), client.request({ method: 'resources/read', params: { uri: 'cdp://performance/metrics' }, }), client.request({ method: 'resources/read', params: { uri: 'cdp://network/requests' }, }), ] await Promise.all(promises) }, 50) // Fewer iterations for complex operations }) it('should benchmark tool execution sequence', async () => { const setupMocks = () => { mockChromeClient.send // Navigate .mockResolvedValueOnce(undefined) .mockResolvedValueOnce(undefined) // Evaluate .mockResolvedValueOnce(undefined) .mockResolvedValueOnce( createCDPResponse({ result: { value: 'Page Title' }, }) ) // DOM query .mockResolvedValueOnce(undefined) .mockResolvedValueOnce(undefined) .mockResolvedValueOnce( createCDPResponse({ nodeId: 123 }) ) } await benchmark('tool sequence', async () => { setupMocks() // Navigate await client.request({ method: 'tools/call', params: { name: 'navigate', arguments: { url: 'https://example.com' }, }, }) // Evaluate await client.request({ method: 'tools/call', params: { name: 'evaluate', arguments: { expression: 'document.title' }, }, }) // DOM query await client.request({ method: 'tools/call', params: { name: 'dom_query_selector', arguments: { selector: 'body' }, }, }) }, 50) }) }) describe('Memory and Throughput Tests', () => { it('should handle high-frequency requests', async () => { mockChromeClient.send .mockResolvedValue( createCDPResponse({ result: { value: 'test' }, }) ) const iterations = 1000 const start = performance.now() const promises = [] for (let i = 0; i < iterations; i++) { promises.push( client.request({ method: 'tools/call', params: { name: 'evaluate', arguments: { expression: '1+1' }, }, }) ) // Batch requests to avoid overwhelming if (promises.length >= 50) { await Promise.all(promises) promises.length = 0 } } await Promise.all(promises) const end = performance.now() const totalTime = end - start const throughput = (iterations / totalTime) * 1000 console.log(`High-frequency test: ${throughput.toFixed(0)} ops/sec`) expect(throughput).toBeGreaterThan(100) // At least 100 ops/sec }) it('should not leak memory over extended operations', async () => { const memStart = process.memoryUsage().heapUsed // Perform many operations for (let i = 0; i < 100; i++) { mockChromeClient.send .mockResolvedValueOnce(undefined) .mockResolvedValueOnce( createCDPResponse({ result: { value: { // Large response data: Array(100).fill({ id: i, value: 'x'.repeat(1000) }), }, }, }) ) await client.request({ method: 'resources/read', params: { uri: 'cdp://runtime/console' }, }) // Force garbage collection if available if (global.gc) { global.gc() } } const memEnd = process.memoryUsage().heapUsed const memoryGrowth = (memEnd - memStart) / 1024 / 1024 // MB console.log(`Memory growth over 100 operations: ${memoryGrowth.toFixed(2)} MB`) expect(memoryGrowth).toBeLessThan(50) // Less than 50MB growth }) }) describe('Large Data Handling', () => { it('should handle large console message arrays', async () => { const largeMessages = Array(1000).fill({ type: 'log', text: 'x'.repeat(100), timestamp: Date.now(), }) mockChromeClient.send .mockResolvedValueOnce(undefined) .mockResolvedValueOnce( createCDPResponse({ result: { value: { messages: largeMessages }, }, }) ) const start = performance.now() const response = await client.request({ method: 'resources/read', params: { uri: 'cdp://runtime/console' }, }) const end = performance.now() const responseTime = end - start console.log(`Large data handling (1000 messages): ${responseTime.toFixed(2)}ms`) expect(responseTime).toBeLessThan(500) // Should handle in under 500ms expect(JSON.parse(response.contents[0].text).messages).toHaveLength(1000) }) it('should handle large DOM trees efficiently', async () => { const createLargeDOM = (depth: number, breadth: number): any => { if (depth === 0) return null return { nodeId: Math.random(), nodeName: 'DIV', children: Array(breadth).fill(null).map(() => createLargeDOM(depth - 1, breadth) ).filter(Boolean), } } mockChromeClient.send .mockResolvedValueOnce(undefined) .mockResolvedValueOnce( createCDPResponse({ root: createLargeDOM(5, 5), // 5^5 = 3125 nodes }) ) const start = performance.now() await client.request({ method: 'resources/read', params: { uri: 'cdp://dom/document' }, }) const end = performance.now() const responseTime = end - start console.log(`Large DOM tree handling (3125 nodes): ${responseTime.toFixed(2)}ms`) expect(responseTime).toBeLessThan(1000) // Should handle in under 1 second }) }) })

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/drzln/curupira'

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