Skip to main content
Glama
apolosan

Design Patterns MCP Server

by apolosan
performance.test.ts14 kB
/** * Performance Tests for Design Patterns MCP Server * Tests response times, memory usage, and throughput requirements */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { DatabaseManager } from '../../src/services/database-manager'; import { CacheService } from '../../src/services/cache'; import { PatternMatcher } from '../../src/services/pattern-matcher'; import { VectorOperationsService } from '../../src/services/vector-operations'; import { performance } from 'perf_hooks'; import { getTestDatabaseConfig } from '../helpers/test-db'; describe('Performance Tests', () => { let databaseManager: DatabaseManager; let cacheService: CacheService; let vectorOps: VectorOperationsService; let patternMatcher: PatternMatcher; const PERFORMANCE_THRESHOLDS = { MAX_RESPONSE_TIME_MS: 5000, // 5 seconds - increased for semantic search MAX_MEMORY_USAGE_MB: 150, // 150 MB - allow for test framework overhead MIN_THROUGHPUT_RPS: 10, // 10 requests per second MAX_DB_QUERY_TIME_MS: 500, // 500ms for database queries }; beforeAll(async () => { databaseManager = new DatabaseManager(getTestDatabaseConfig(true)); await databaseManager.initialize(); cacheService = new CacheService({ maxSize: 1000, defaultTTL: 3600000, }); vectorOps = new VectorOperationsService(databaseManager, { model: 'all-MiniLM-L6-v2', dimensions: 384, similarityThreshold: 0.01, maxResults: 10, cacheEnabled: true, }); patternMatcher = new PatternMatcher(databaseManager, vectorOps, { maxResults: 5, minConfidence: 0.3, useSemanticSearch: true, useKeywordSearch: true, useHybridSearch: true, semanticWeight: 0.7, keywordWeight: 0.3, }); }); afterAll(async () => { await databaseManager.close(); }); describe('Database Performance Tests', () => { it('should execute database queries within time limits', async () => { const testQueries = [ 'SELECT COUNT(*) as count FROM patterns', 'SELECT name, category FROM patterns LIMIT 10', 'SELECT * FROM patterns WHERE category = "Creational" LIMIT 5', 'SELECT p.name FROM patterns p LIMIT 20', ]; for (const sql of testQueries) { const startTime = performance.now(); const result = databaseManager.query(sql); const endTime = performance.now(); const executionTime = endTime - startTime; expect(executionTime).toBeLessThan(PERFORMANCE_THRESHOLDS.MAX_DB_QUERY_TIME_MS); expect(result).toBeDefined(); expect(Array.isArray(result)).toBe(true); } }); it('should handle concurrent database queries efficiently', async () => { const concurrentQueries = 5; const sql = 'SELECT name FROM patterns LIMIT 5'; const startTime = performance.now(); const promises = Array(concurrentQueries) .fill(null) .map(() => databaseManager.query(sql)); const results = await Promise.all(promises); const endTime = performance.now(); const totalTime = endTime - startTime; const avgTime = totalTime / concurrentQueries; expect(avgTime).toBeLessThan(PERFORMANCE_THRESHOLDS.MAX_DB_QUERY_TIME_MS); expect(results).toHaveLength(concurrentQueries); results.forEach(result => { expect(Array.isArray(result)).toBe(true); expect(result.length).toBeGreaterThan(0); }); }); it('should maintain performance with prepared statements', async () => { const sql = 'SELECT * FROM patterns WHERE id = ?'; const iterations = 10; // First execution (statement preparation) const firstStart = performance.now(); databaseManager.query(sql, ['pattern_1']); const firstEnd = performance.now(); const firstTime = firstEnd - firstStart; // Subsequent executions (prepared statement reuse) const subsequentTimes: number[] = []; for (let i = 0; i < iterations; i++) { const start = performance.now(); databaseManager.query(sql, ['test_pattern']); // Use same parameter for reuse const end = performance.now(); subsequentTimes.push(end - start); } const avgSubsequentTime = subsequentTimes.reduce((sum, time) => sum + time, 0) / iterations; expect(avgSubsequentTime).toBeLessThan(firstTime); expect(avgSubsequentTime).toBeLessThan(PERFORMANCE_THRESHOLDS.MAX_DB_QUERY_TIME_MS); }); }); describe('Cache Performance Tests', () => { it('should provide fast cache operations', () => { const testData = { id: 1, name: 'Test Pattern', category: 'Test' }; const testKey = 'test:pattern:1'; // Test cache set performance const setStart = performance.now(); cacheService.set(testKey, testData, 60000); // 1 minute TTL const setEnd = performance.now(); const setTime = setEnd - setStart; // Test cache get performance const getStart = performance.now(); cacheService.get(testKey); const getEnd = performance.now(); const getTime = getEnd - getStart; }); it('should handle cache hits and misses efficiently', () => { const hitKey = 'test:hit'; const missKey = 'test:miss'; const testData = { data: 'test' }; // Setup cache hit cacheService.set(hitKey, testData); const iterations = 100; let hitTime = 0; let missTime = 0; // Test cache hits for (let i = 0; i < iterations; i++) { const start = performance.now(); cacheService.get(hitKey); const end = performance.now(); hitTime += end - start; } // Test cache misses for (let i = 0; i < iterations; i++) { const start = performance.now(); cacheService.get(`${missKey}_${i}`); const end = performance.now(); missTime += end - start; } const avgHitTime = hitTime / iterations; const avgMissTime = missTime / iterations; // Cache hits might not always be faster than misses for very small operations // The important thing is both are reasonably fast expect(avgHitTime).toBeLessThan(1); // Reasonable hit time expect(avgMissTime).toBeLessThan(1); // Reasonable miss time }); it('should maintain performance under load', () => { const operations = 1000; const testData = { id: 1, name: 'Load Test' }; const startTime = performance.now(); // Perform mixed operations for (let i = 0; i < operations; i++) { const key = `load_test:${i}`; cacheService.set(key, { ...testData, index: i }, 30000); if (i % 10 === 0) { // Every 10th operation, test get cacheService.get(key); } if (i % 50 === 0) { // Every 50th operation, test delete cacheService.delete(key); } } const endTime = performance.now(); const totalTime = endTime - startTime; const avgTime = totalTime / operations; expect(avgTime).toBeLessThan(0.1); // Should maintain fast performance expect(totalTime).toBeLessThan(100); // Should complete quickly }); }); describe('Memory Usage Tests', () => { it('should not exceed memory usage limits during operations', () => { const initialMemory = process.memoryUsage(); // Perform memory-intensive operations const testData = Array(1000) .fill(null) .map((_, i) => ({ id: i, name: `Pattern ${i}`, description: 'A'.repeat(100), // 100 chars per description category: 'Test', })); // Cache operations testData.forEach((data, index) => { cacheService.set(`memory_test:${index}`, data, 60000); }); // Database operations const dbQueries = Array(50) .fill(null) .map((_, i) => `SELECT name FROM patterns LIMIT ${i + 1}`); dbQueries.forEach(sql => { databaseManager.query(sql); }); const finalMemory = process.memoryUsage(); const memoryIncrease = finalMemory.heapUsed - initialMemory.heapUsed; const memoryIncreaseMB = memoryIncrease / (1024 * 1024); expect(memoryIncreaseMB).toBeLessThan(PERFORMANCE_THRESHOLDS.MAX_MEMORY_USAGE_MB); expect(finalMemory.heapUsed).toBeLessThan( PERFORMANCE_THRESHOLDS.MAX_MEMORY_USAGE_MB * 1024 * 1024 ); }); }); describe('Throughput Tests', () => { it('should handle sustained operation rate', () => { const duration = 5000; // 5 seconds const targetOperations = (PERFORMANCE_THRESHOLDS.MIN_THROUGHPUT_RPS * duration) / 1000; const startTime = performance.now(); let operationCount = 0; while (performance.now() - startTime < duration) { const key = `throughput_test:${operationCount}`; cacheService.set(key, { data: operationCount }, 10000); cacheService.get(key); operationCount++; // Small delay to prevent infinite loop if (operationCount % 100 === 0) { // Allow event loop to process } } const actualDuration = performance.now() - startTime; const actualOPS = operationCount / (actualDuration / 1000); expect(actualOPS).toBeGreaterThanOrEqual(PERFORMANCE_THRESHOLDS.MIN_THROUGHPUT_RPS); expect(operationCount).toBeGreaterThan(targetOperations * 0.8); // At least 80% of target }); }); describe('System Resource Tests', () => { it('should monitor system resources effectively', () => { const memoryUsage = process.memoryUsage(); const cpuUsage = process.cpuUsage(); const uptime = process.uptime(); // Basic resource checks expect(memoryUsage.heapUsed).toBeGreaterThan(0); expect(uptime).toBeGreaterThan(0); expect(typeof cpuUsage.user).toBe('number'); expect(typeof cpuUsage.system).toBe('number'); // Memory should be reasonable const memoryMB = memoryUsage.heapUsed / (1024 * 1024); expect(memoryMB).toBeLessThan(PERFORMANCE_THRESHOLDS.MAX_MEMORY_USAGE_MB); }); }); describe('Pattern Matching Performance Tests', () => { const testQueries = [ 'I need to create objects without specifying exact classes', 'How to implement a singleton pattern', 'Need to add logging without changing existing code', 'Want to create families of related objects', 'Need to communicate between objects efficiently', ]; it('should respond to pattern matching queries within time limits', async () => { for (const query of testQueries) { const startTime = performance.now(); const result = await patternMatcher.findMatchingPatterns({ id: `test-${Date.now()}-${Math.random()}`, query, maxResults: 5, programmingLanguage: 'typescript', }); const endTime = performance.now(); const responseTime = endTime - startTime; expect(responseTime).toBeLessThan(PERFORMANCE_THRESHOLDS.MAX_RESPONSE_TIME_MS); expect(result.length).toBeGreaterThan(0); } }); it('should handle concurrent pattern matching requests', async () => { const concurrentRequests = 5; const promises = testQueries.slice(0, concurrentRequests).map((query, index) => patternMatcher.findMatchingPatterns({ id: `concurrent-test-${Date.now()}-${index}`, query, maxResults: 3, programmingLanguage: 'javascript', }) ); const startTime = performance.now(); const results = await Promise.all(promises); const endTime = performance.now(); const totalTime = endTime - startTime; const avgTime = totalTime / concurrentRequests; expect(avgTime).toBeLessThan(PERFORMANCE_THRESHOLDS.MAX_RESPONSE_TIME_MS); expect(results.length).toBe(concurrentRequests); results.forEach(result => { expect(result.length).toBeGreaterThan(0); }); }); it('should maintain performance under sustained load', async () => { const iterations = 10; const times: number[] = []; for (let i = 0; i < iterations; i++) { const query = testQueries[i % testQueries.length]; const startTime = performance.now(); await patternMatcher.findMatchingPatterns({ id: `sustained-test-${Date.now()}-${i}`, query, maxResults: 3, }); const endTime = performance.now(); times.push(endTime - startTime); } const avgTime = times.reduce((a, b) => a + b, 0) / times.length; const maxTime = Math.max(...times); expect(avgTime).toBeLessThan(PERFORMANCE_THRESHOLDS.MAX_RESPONSE_TIME_MS); expect(maxTime).toBeLessThan(PERFORMANCE_THRESHOLDS.MAX_RESPONSE_TIME_MS * 1.5); // Allow some variance }); it('should cache pattern matching results effectively', async () => { const query = 'factory pattern implementation'; const options = { maxResults: 5, programmingLanguage: 'typescript' }; // First request (should cache) const startTime1 = performance.now(); const result1 = await patternMatcher.findMatchingPatterns({ id: `cache-test-1-${Date.now()}`, query, ...options, }); const time1 = performance.now() - startTime1; // Second request (should use cache) const startTime2 = performance.now(); const result2 = await patternMatcher.findMatchingPatterns({ id: `cache-test-2-${Date.now()}`, query, ...options, }); const time2 = performance.now() - startTime2; // Results should be identical expect(result1.length).toBe(result2.length); expect(result1[0]?.pattern?.name).toBe(result2[0]?.pattern?.name); // Second request should be significantly faster (at least 2x speedup) expect(time1 / time2).toBeGreaterThan(2); }); }); });

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/apolosan/design_patterns_mcp'

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