Skip to main content
Glama
stress.perf.test.js13.6 kB
/** * Stress tests for system limits * Tests: high load, sustained operations, edge conditions */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { benchmark, PerformanceReporter, LatencyHistogram, getMemoryUsage, wait } from './helpers/benchmark.js' import { generateEmails, generateMessages, generateCalendarEvents, generateSearchQueries, generateEmbeddingTexts } from './helpers/data-generators.js' import { createPerformanceMocks } from './helpers/mocks.js' describe('Stress Tests', { timeout: 120000 }, () => { let mocks let reporter beforeEach(() => { vi.clearAllMocks() mocks = createPerformanceMocks() reporter = new PerformanceReporter('Stress Tests') }) afterEach(() => { vi.restoreAllMocks() }) describe('High Volume Operations', () => { it('should handle 10,000 emails in index', async () => { const emails = generateEmails(10000) const BATCH_SIZE = 32 const startTime = performance.now() let processed = 0 for (let i = 0; i < emails.length; i += BATCH_SIZE) { const batch = emails.slice(i, i + BATCH_SIZE) await mocks.embedder.embedder(batch.map(e => e.subject)) processed += batch.length } const duration = performance.now() - startTime console.log(`\nIndexed 10,000 emails in ${(duration / 1000).toFixed(2)}s`) console.log(`Throughput: ${(10000 / (duration / 1000)).toFixed(1)} emails/sec`) expect(duration).toBeLessThan(30000) // Under 30s }) it('should handle 5,000 messages in index', async () => { const messages = generateMessages(5000) const BATCH_SIZE = 32 const startTime = performance.now() for (let i = 0; i < messages.length; i += BATCH_SIZE) { const batch = messages.slice(i, i + BATCH_SIZE) await mocks.embedder.embedder(batch.map(m => m.text)) } const duration = performance.now() - startTime console.log(`\nIndexed 5,000 messages in ${(duration / 1000).toFixed(2)}s`) expect(duration).toBeLessThan(20000) }) it('should handle 1,000 calendar events', async () => { const events = generateCalendarEvents(1000) const startTime = performance.now() for (let i = 0; i < events.length; i += 32) { const batch = events.slice(i, i + 32) await mocks.embedder.embedder(batch.map(e => e.title)) } const duration = performance.now() - startTime console.log(`\nIndexed 1,000 calendar events in ${(duration / 1000).toFixed(2)}s`) expect(duration).toBeLessThan(10000) }) }) describe('Sustained Load', () => { it('should maintain performance over 100 consecutive searches', async () => { const queries = generateSearchQueries(100) const histogram = new LatencyHistogram(5) for (const query of queries) { const start = performance.now() await mocks.embedder.embedder([query]) await new Promise(r => setTimeout(r, 5)) // Simulate search histogram.record(performance.now() - start) } console.log('\nLatency over 100 searches:') histogram.printHistogram() console.log(`Mean: ${histogram.getMean().toFixed(2)}ms`) expect(histogram.getMean()).toBeLessThan(50) }) it('should maintain performance over 500 tool calls', async () => { const latencies = [] for (let i = 0; i < 500; i++) { const start = performance.now() await mocks.embedder.embedder([`query ${i}`]) latencies.push(performance.now() - start) } // Check for performance degradation const first100 = latencies.slice(0, 100) const last100 = latencies.slice(-100) const avgFirst = first100.reduce((a, b) => a + b, 0) / first100.length const avgLast = last100.reduce((a, b) => a + b, 0) / last100.length console.log(`\nFirst 100 avg: ${avgFirst.toFixed(2)}ms`) console.log(`Last 100 avg: ${avgLast.toFixed(2)}ms`) console.log(`Degradation: ${((avgLast / avgFirst - 1) * 100).toFixed(1)}%`) // Should not degrade significantly expect(avgLast).toBeLessThan(avgFirst * 2) }) }) describe('Concurrent Stress', () => { it('should handle 20 concurrent operations', async () => { const concurrency = 20 const operation = async (id) => { await mocks.embedder.embedder([`query ${id}`]) const results = generateEmails(10) return results.length } const startTime = performance.now() const results = await Promise.all( Array(concurrency).fill(null).map((_, i) => operation(i)) ) const duration = performance.now() - startTime console.log(`\n${concurrency} concurrent operations completed in ${duration.toFixed(2)}ms`) expect(results.every(r => r === 10)).toBe(true) expect(duration).toBeLessThan(5000) }) it('should handle burst of 50 requests', async () => { const burstSize = 50 const startTime = performance.now() await Promise.all( Array(burstSize).fill(null).map((_, i) => mocks.embedder.embedder([`burst query ${i}`]) ) ) const duration = performance.now() - startTime console.log(`\n${burstSize} burst requests completed in ${duration.toFixed(2)}ms`) expect(duration).toBeLessThan(2000) }) }) describe('Data Volume Limits', () => { it('should handle maximum result set (1000 items)', async () => { const results = generateEmails(1000) const startTime = performance.now() // Format all results const formatted = results.map(r => ({ from: r.from, subject: r.subject, date: r.date, preview: r.body.substring(0, 100) })) const output = JSON.stringify(formatted) const duration = performance.now() - startTime console.log(`\nFormatted 1000 results in ${duration.toFixed(2)}ms`) console.log(`Output size: ${(output.length / 1024).toFixed(1)}KB`) expect(duration).toBeLessThan(500) }) it('should handle very long search queries', async () => { const longQuery = 'search '.repeat(100) + 'important email' const startTime = performance.now() await mocks.embedder.embedder([longQuery]) const duration = performance.now() - startTime console.log(`\nLong query (${longQuery.length} chars) processed in ${duration.toFixed(2)}ms`) expect(duration).toBeLessThan(100) }) it('should handle large email bodies', async () => { const largeEmails = generateEmails(10, { bodySize: 'large' }) for (const email of largeEmails) { email.body = 'word '.repeat(10000) // ~50KB body } const startTime = performance.now() for (const email of largeEmails) { await mocks.embedder.embedder([email.subject + ' ' + email.body.substring(0, 1000)]) } const duration = performance.now() - startTime console.log(`\n10 large emails processed in ${duration.toFixed(2)}ms`) expect(duration).toBeLessThan(2000) }) }) describe('Memory Under Stress', () => { it('should maintain memory bounds during heavy load', async () => { const memSamples = [] const operations = 200 for (let i = 0; i < operations; i++) { await mocks.embedder.embedder([`stress query ${i}`]) generateEmails(50) // Create temporary data if (i % 20 === 0) { memSamples.push(getMemoryUsage().heapUsed) } } console.log('\nMemory samples during stress:') console.log(` ${memSamples.map(m => m.toFixed(1)).join('MB → ')}MB`) const maxMem = Math.max(...memSamples) const minMem = Math.min(...memSamples) console.log(` Range: ${minMem.toFixed(1)}MB - ${maxMem.toFixed(1)}MB`) expect(maxMem).toBeLessThan(500) }) }) describe('Recovery', () => { it('should recover from temporary slowdowns', async () => { const latencies = [] for (let i = 0; i < 50; i++) { const start = performance.now() // Simulate occasional slowdown if (i >= 20 && i < 30) { await wait(50) // Slow period } await mocks.embedder.embedder([`query ${i}`]) latencies.push(performance.now() - start) } const before = latencies.slice(0, 20) const during = latencies.slice(20, 30) const after = latencies.slice(30) const avgBefore = before.reduce((a, b) => a + b, 0) / before.length const avgDuring = during.reduce((a, b) => a + b, 0) / during.length const avgAfter = after.reduce((a, b) => a + b, 0) / after.length console.log(`\nBefore slowdown: ${avgBefore.toFixed(2)}ms`) console.log(`During slowdown: ${avgDuring.toFixed(2)}ms`) console.log(`After recovery: ${avgAfter.toFixed(2)}ms`) // Should recover to normal performance (within 1ms for very fast operations, or 1.5x for slower) const recoveryThreshold = Math.max(avgBefore * 2, 1) expect(avgAfter).toBeLessThan(recoveryThreshold) }) it('should handle error recovery gracefully', async () => { let errorCount = 0 let successCount = 0 for (let i = 0; i < 100; i++) { try { if (i % 10 === 0) { throw new Error('Simulated error') } await mocks.embedder.embedder([`query ${i}`]) successCount++ } catch (e) { errorCount++ } } console.log(`\nSuccesses: ${successCount}, Errors: ${errorCount}`) expect(successCount).toBe(90) expect(errorCount).toBe(10) }) }) describe('Edge Cases Under Load', () => { it('should handle empty queries gracefully', async () => { const queries = ['', ' ', '\n', '\t', ...generateSearchQueries(10)] for (const query of queries) { await mocks.embedder.embedder([query || 'fallback']) } expect(true).toBe(true) // No crashes }) it('should handle mixed data types', async () => { const mixedData = [ ...generateEmails(50), ...generateMessages(50), ...generateCalendarEvents(20) ] const startTime = performance.now() for (const item of mixedData) { const text = item.subject || item.text || item.title || '' await mocks.embedder.embedder([text]) } const duration = performance.now() - startTime console.log(`\nProcessed ${mixedData.length} mixed items in ${duration.toFixed(2)}ms`) expect(duration).toBeLessThan(10000) }) }) describe('Throughput Limits', () => { it('should measure maximum throughput', async () => { const testDuration = 5000 // 5 seconds const startTime = performance.now() let operations = 0 while (performance.now() - startTime < testDuration) { await mocks.embedder.embedder([`query ${operations}`]) operations++ } const actualDuration = performance.now() - startTime const throughput = operations / (actualDuration / 1000) console.log(`\nMax throughput: ${throughput.toFixed(1)} ops/sec`) console.log(`Total operations: ${operations}`) expect(throughput).toBeGreaterThan(100) // At least 100 ops/sec }) }) describe('Stress Test Summary', () => { it('should generate stress test summary', async () => { const results = { indexing: { emails: 0, messages: 0, events: 0 }, searching: { queries: 0, avgLatency: 0 }, memory: { peak: 0, final: 0 } } // Indexing stress const emails = generateEmails(1000) const messages = generateMessages(500) const events = generateCalendarEvents(200) for (let i = 0; i < emails.length; i += 32) { await mocks.embedder.embedder(emails.slice(i, i + 32).map(e => e.subject)) } results.indexing.emails = emails.length for (let i = 0; i < messages.length; i += 32) { await mocks.embedder.embedder(messages.slice(i, i + 32).map(m => m.text)) } results.indexing.messages = messages.length for (let i = 0; i < events.length; i += 32) { await mocks.embedder.embedder(events.slice(i, i + 32).map(e => e.title)) } results.indexing.events = events.length // Search stress const queries = generateSearchQueries(100) const latencies = [] for (const query of queries) { const start = performance.now() await mocks.embedder.embedder([query]) latencies.push(performance.now() - start) } results.searching.queries = queries.length results.searching.avgLatency = latencies.reduce((a, b) => a + b, 0) / latencies.length // Memory results.memory.final = getMemoryUsage().heapUsed console.log('\n=== STRESS TEST SUMMARY ===') console.log('\nIndexing:') console.log(` Emails: ${results.indexing.emails}`) console.log(` Messages: ${results.indexing.messages}`) console.log(` Events: ${results.indexing.events}`) console.log('\nSearching:') console.log(` Queries: ${results.searching.queries}`) console.log(` Avg Latency: ${results.searching.avgLatency.toFixed(2)}ms`) console.log('\nMemory:') console.log(` Final Heap: ${results.memory.final.toFixed(2)}MB`) console.log('\n===========================') expect(results.indexing.emails).toBe(1000) expect(results.searching.avgLatency).toBeLessThan(50) }) }) afterAll(() => { reporter.report() }) })

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