Skip to main content
Glama
benchmarks.test.js7.78 kB
/** * Performance Benchmark Tests * * Measures and asserts performance characteristics: * - Search latency * - Memory usage * - Throughput */ import { describe, it, expect, beforeAll } from 'vitest' import { connect } from '@lancedb/lancedb' import path from 'path' import fs from 'fs' const DB_PATH = path.join(process.env.HOME, '.apple-tools-mcp', 'lance_index') const indexExists = fs.existsSync(DB_PATH) let db = null // Helper to measure execution time async function measureTime(fn) { const start = performance.now() const result = await fn() const duration = performance.now() - start return { result, duration } } // Helper to measure memory usage function measureMemory() { const usage = process.memoryUsage() return { heapUsed: Math.round(usage.heapUsed / 1024 / 1024), heapTotal: Math.round(usage.heapTotal / 1024 / 1024), rss: Math.round(usage.rss / 1024 / 1024) } } describe.skipIf(!indexExists)('Performance: Search Latency', () => { beforeAll(async () => { if (indexExists) { db = await connect(DB_PATH) } }) it('should complete email table query in <100ms', async () => { const tables = await db.tableNames() if (!tables.includes('emails')) { console.log(' → Skipped: emails table not found') return } const tbl = await db.openTable('emails') const { duration } = await measureTime(async () => { return await tbl.query().limit(100).toArray() }) console.log(` → Email query (100 rows): ${duration.toFixed(1)}ms`) expect(duration).toBeLessThan(100) }) it('should complete messages table query in <100ms', async () => { const tables = await db.tableNames() if (!tables.includes('messages')) { console.log(' → Skipped: messages table not found') return } const tbl = await db.openTable('messages') const { duration } = await measureTime(async () => { return await tbl.query().limit(100).toArray() }) console.log(` → Messages query (100 rows): ${duration.toFixed(1)}ms`) expect(duration).toBeLessThan(100) }) it('should complete calendar table query in <100ms', async () => { const tables = await db.tableNames() if (!tables.includes('calendar')) { console.log(' → Skipped: calendar table not found') return } const tbl = await db.openTable('calendar') const { duration } = await measureTime(async () => { return await tbl.query().limit(100).toArray() }) console.log(` → Calendar query (100 rows): ${duration.toFixed(1)}ms`) expect(duration).toBeLessThan(100) }) it('should handle large result sets efficiently (<500ms for 1000 rows)', async () => { const tables = await db.tableNames() if (!tables.includes('messages')) { console.log(' → Skipped: messages table not found') return } const tbl = await db.openTable('messages') const { duration, result } = await measureTime(async () => { return await tbl.query().limit(1000).toArray() }) console.log(` → Large query (${result.length} rows): ${duration.toFixed(1)}ms`) expect(duration).toBeLessThan(500) }) }) describe.skipIf(!indexExists)('Performance: Memory Usage', () => { beforeAll(async () => { if (indexExists && !db) { db = await connect(DB_PATH) } }) it('should maintain reasonable heap usage during queries', async () => { const tables = await db.tableNames() if (!tables.includes('emails')) { console.log(' → Skipped: emails table not found') return } const beforeMem = measureMemory() const tbl = await db.openTable('emails') for (let i = 0; i < 10; i++) { await tbl.query().limit(100).toArray() } const afterMem = measureMemory() const heapGrowth = afterMem.heapUsed - beforeMem.heapUsed console.log(` → Heap before: ${beforeMem.heapUsed}MB, after: ${afterMem.heapUsed}MB`) console.log(` → Heap growth: ${heapGrowth}MB`) // Allow up to 100MB growth for 10 queries expect(heapGrowth).toBeLessThan(100) }) it('should not leak memory on repeated queries', async () => { const tables = await db.tableNames() if (!tables.includes('messages')) { console.log(' → Skipped: messages table not found') return } // Force GC if available if (global.gc) global.gc() const tbl = await db.openTable('messages') const measurements = [] for (let i = 0; i < 5; i++) { await tbl.query().limit(500).toArray() measurements.push(measureMemory().heapUsed) } console.log(` → Memory samples: ${measurements.join(', ')}MB`) // Check that memory doesn't grow linearly const first = measurements[0] const last = measurements[measurements.length - 1] const growth = last - first // Allow up to 50MB total growth expect(growth).toBeLessThan(50) }) }) describe.skipIf(!indexExists)('Performance: Throughput', () => { beforeAll(async () => { if (indexExists && !db) { db = await connect(DB_PATH) } }) it('should handle 50 sequential queries in <5s', async () => { const tables = await db.tableNames() if (!tables.includes('emails')) { console.log(' → Skipped: emails table not found') return } const tbl = await db.openTable('emails') const { duration } = await measureTime(async () => { for (let i = 0; i < 50; i++) { await tbl.query().limit(10).toArray() } }) const avgLatency = duration / 50 console.log(` → 50 queries in ${duration.toFixed(0)}ms (avg: ${avgLatency.toFixed(1)}ms)`) expect(duration).toBeLessThan(5000) }) it('should maintain consistent latency under load', async () => { const tables = await db.tableNames() if (!tables.includes('messages')) { console.log(' → Skipped: messages table not found') return } const tbl = await db.openTable('messages') const latencies = [] for (let i = 0; i < 20; i++) { const { duration } = await measureTime(async () => { await tbl.query().limit(50).toArray() }) latencies.push(duration) } const avg = latencies.reduce((a, b) => a + b, 0) / latencies.length const max = Math.max(...latencies) const min = Math.min(...latencies) const variance = max - min console.log(` → Latency: min=${min.toFixed(1)}ms, avg=${avg.toFixed(1)}ms, max=${max.toFixed(1)}ms`) console.log(` → Variance: ${variance.toFixed(1)}ms`) // Max should be less than 5x average (no major outliers) expect(max).toBeLessThan(avg * 5) }) }) describe('Performance: Utility Functions', () => { it('should parse dates quickly (<1ms for 100 dates)', async () => { const { parseNaturalDate } = await import('../../search.js') const dates = [ 'today', 'yesterday', 'last week', 'next Monday', '2024-01-15', 'January 15, 2024', 'Jan 15' ] const { duration } = await measureTime(() => { for (let i = 0; i < 100; i++) { dates.forEach(d => parseNaturalDate(d)) } }) console.log(` → 700 date parses in ${duration.toFixed(1)}ms`) // chrono-node date parsing can be slower on first calls due to lazy loading expect(duration).toBeLessThan(500) }) it('should validate inputs quickly (<1ms for 1000 validations)', async () => { const { validateSearchQuery, validateLimit } = await import('../../lib/validators.js') const { duration } = await measureTime(() => { for (let i = 0; i < 1000; i++) { validateSearchQuery('test query ' + i) validateLimit(i % 100) } }) console.log(` → 2000 validations in ${duration.toFixed(1)}ms`) expect(duration).toBeLessThan(50) }) })

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/sfls1397/Apple-Tools-MCP'

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