Skip to main content
Glama

Personupplysning MCP Server

performance-tests.ts14.3 kB
/** * Performance Test Suite for Personupplysning MCP Server * Tests load handling, cache efficiency, and response times */ import { companyDataService } from '../src/services/company-data-service.js'; import { performance } from 'perf_hooks'; interface PerformanceMetric { operation: string; samples: number; avgTime: number; minTime: number; maxTime: number; p50: number; p95: number; p99: number; successRate: number; throughput: number; } interface TestResult { testName: string; category: string; passed: boolean; metric?: PerformanceMetric; error?: string; } const results: TestResult[] = []; /** * Calculate percentiles from sorted array */ function percentile(arr: number[], p: number): number { const sorted = arr.slice().sort((a, b) => a - b); const index = Math.ceil((sorted.length * p) / 100) - 1; return sorted[Math.max(0, index)]; } /** * Calculate statistics from timing array */ function calculateStats(timings: number[], totalTime: number): PerformanceMetric { const successful = timings.filter(t => t > 0); const samples = timings.length; return { operation: '', samples, avgTime: successful.reduce((a, b) => a + b, 0) / successful.length || 0, minTime: Math.min(...successful) || 0, maxTime: Math.max(...successful) || 0, p50: percentile(successful, 50), p95: percentile(successful, 95), p99: percentile(successful, 99), successRate: (successful.length / samples) * 100, throughput: (successful.length / totalTime) * 1000 // ops/second }; } /** * Test 1: Local Search Performance (Database Query) */ async function testLocalSearchPerformance(): Promise<void> { console.log('⚡ Test 1: Local Search Performance\n'); const queries = [ 'spotify', 'ikea', 'volvo', 'scania', 'ericsson', 'h&m', 'astra', 'saab', 'electrolux', 'skf' ]; const iterations = 10; const timings: number[] = []; const startTime = performance.now(); for (let i = 0; i < iterations; i++) { for (const query of queries) { try { const queryStart = performance.now(); await companyDataService.searchCompanies(query, 10); const queryTime = performance.now() - queryStart; timings.push(queryTime); } catch (error) { timings.push(-1); // Mark as failure console.error(` Query failed: ${query}`); } } } const totalTime = performance.now() - startTime; const stats = calculateStats(timings, totalTime); stats.operation = 'Local Search'; console.log(` Samples: ${stats.samples}`); console.log(` Success Rate: ${stats.successRate.toFixed(2)}%`); console.log(` Avg Time: ${stats.avgTime.toFixed(2)}ms`); console.log(` P50 (Median): ${stats.p50.toFixed(2)}ms`); console.log(` P95: ${stats.p95.toFixed(2)}ms`); console.log(` P99: ${stats.p99.toFixed(2)}ms`); console.log(` Min/Max: ${stats.minTime.toFixed(2)}ms / ${stats.maxTime.toFixed(2)}ms`); console.log(` Throughput: ${stats.throughput.toFixed(2)} queries/sec\n`); // Target: < 100ms for local searches const passed = stats.p95 < 100; results.push({ testName: 'Local Search Performance', category: 'Performance', passed, metric: stats }); if (!passed) { console.log(` ⚠️ WARNING: P95 latency ${stats.p95.toFixed(2)}ms exceeds target of 100ms\n`); } else { console.log(` ✅ PASS: P95 latency within target (< 100ms)\n`); } } /** * Test 2: Cache Hit Performance */ async function testCacheHitPerformance(): Promise<void> { console.log('⚡ Test 2: Cache Hit Performance\n'); const orgNumber = '5560001712'; // Spotify const iterations = 50; const timings: number[] = []; const startTime = performance.now(); // First request to populate cache try { await companyDataService.getCompanyDetails(orgNumber); console.log(' Cache warmed up...\n'); } catch (error) { console.log(' ⚠️ Failed to warm up cache\n'); } // Subsequent requests should hit cache for (let i = 0; i < iterations; i++) { try { const queryStart = performance.now(); await companyDataService.getCompanyDetails(orgNumber); const queryTime = performance.now() - queryStart; timings.push(queryTime); } catch (error) { timings.push(-1); } } const totalTime = performance.now() - startTime; const stats = calculateStats(timings, totalTime); stats.operation = 'Cache Hit'; console.log(` Samples: ${stats.samples}`); console.log(` Success Rate: ${stats.successRate.toFixed(2)}%`); console.log(` Avg Time: ${stats.avgTime.toFixed(2)}ms`); console.log(` P50 (Median): ${stats.p50.toFixed(2)}ms`); console.log(` P95: ${stats.p95.toFixed(2)}ms`); console.log(` P99: ${stats.p99.toFixed(2)}ms`); console.log(` Min/Max: ${stats.minTime.toFixed(2)}ms / ${stats.maxTime.toFixed(2)}ms`); console.log(` Throughput: ${stats.throughput.toFixed(2)} requests/sec\n`); // Target: < 50ms for cached responses const passed = stats.p95 < 50; results.push({ testName: 'Cache Hit Performance', category: 'Performance', passed, metric: stats }); if (!passed) { console.log(` ⚠️ WARNING: P95 latency ${stats.p95.toFixed(2)}ms exceeds target of 50ms\n`); } else { console.log(` ✅ PASS: P95 latency within target (< 50ms)\n`); } } /** * Test 3: Concurrent Request Handling */ async function testConcurrentRequests(): Promise<void> { console.log('⚡ Test 3: Concurrent Request Handling\n'); const queries = Array(50).fill(null).map((_, i) => `company${i}`); const concurrency = 20; const timings: number[] = []; const startTime = performance.now(); // Process in batches for (let i = 0; i < queries.length; i += concurrency) { const batch = queries.slice(i, i + concurrency); const promises = batch.map(async (query) => { const queryStart = performance.now(); try { await companyDataService.searchCompanies(query, 5); return performance.now() - queryStart; } catch (error) { return -1; } }); const batchTimings = await Promise.all(promises); timings.push(...batchTimings); } const totalTime = performance.now() - startTime; const stats = calculateStats(timings, totalTime); stats.operation = 'Concurrent Requests'; console.log(` Total Requests: ${queries.length}`); console.log(` Concurrency Level: ${concurrency}`); console.log(` Success Rate: ${stats.successRate.toFixed(2)}%`); console.log(` Avg Time: ${stats.avgTime.toFixed(2)}ms`); console.log(` P95: ${stats.p95.toFixed(2)}ms`); console.log(` Throughput: ${stats.throughput.toFixed(2)} requests/sec\n`); // Target: > 90% success rate const passed = stats.successRate > 90; results.push({ testName: 'Concurrent Request Handling', category: 'Performance', passed, metric: stats }); if (!passed) { console.log(` ⚠️ WARNING: Success rate ${stats.successRate.toFixed(2)}% below target of 90%\n`); } else { console.log(` ✅ PASS: Success rate above target (> 90%)\n`); } } /** * Test 4: Cache Statistics and Hit Rate */ async function testCacheStatistics(): Promise<void> { console.log('⚡ Test 4: Cache Statistics\n'); try { const stats = await companyDataService.getCacheStats(); console.log(` Cached Company Details: ${stats.cached_company_details || 0}`); console.log(` Cached Document Lists: ${stats.cached_document_lists || 0}`); console.log(` Stored Financial Reports: ${stats.stored_financial_reports || 0}`); console.log(` Total API Requests: ${stats.total_api_requests || 0}`); console.log(` 24h Cache Hit Rate: ${stats.cache_hit_rate_24h?.toFixed(2) || 0}%\n`); // No strict target for cache hit rate initially, just informational const passed = true; results.push({ testName: 'Cache Statistics', category: 'Performance', passed, metric: { operation: 'Cache Stats', samples: 1, avgTime: 0, minTime: 0, maxTime: 0, p50: 0, p95: 0, p99: 0, successRate: 100, throughput: stats.cache_hit_rate_24h || 0 } }); console.log(` ✅ PASS: Cache statistics retrieved\n`); } catch (error: any) { console.log(` ❌ FAIL: ${error.message}\n`); results.push({ testName: 'Cache Statistics', category: 'Performance', passed: false, error: error.message }); } } /** * Test 5: Database Connection Pool Performance */ async function testDatabaseConnectionPool(): Promise<void> { console.log('⚡ Test 5: Database Connection Pool\n'); const parallelRequests = 50; const timings: number[] = []; const startTime = performance.now(); // Execute many queries simultaneously to test connection pooling const promises = Array(parallelRequests).fill(null).map(async (_, i) => { const queryStart = performance.now(); try { await companyDataService.searchCompanies(`test${i}`, 5); return performance.now() - queryStart; } catch (error) { return -1; } }); const results_arr = await Promise.all(promises); timings.push(...results_arr); const totalTime = performance.now() - startTime; const stats = calculateStats(timings, totalTime); stats.operation = 'DB Connection Pool'; console.log(` Parallel Requests: ${parallelRequests}`); console.log(` Success Rate: ${stats.successRate.toFixed(2)}%`); console.log(` Total Time: ${totalTime.toFixed(2)}ms`); console.log(` Avg Time per Request: ${stats.avgTime.toFixed(2)}ms`); console.log(` P95: ${stats.p95.toFixed(2)}ms\n`); // Target: All requests should complete successfully const passed = stats.successRate === 100; results.push({ testName: 'Database Connection Pool', category: 'Performance', passed, metric: stats }); if (!passed) { console.log(` ⚠️ WARNING: Some requests failed (${stats.successRate.toFixed(2)}% success rate)\n`); } else { console.log(` ✅ PASS: All parallel requests completed successfully\n`); } } /** * Test 6: Memory Usage Check */ async function testMemoryUsage(): Promise<void> { console.log('⚡ Test 6: Memory Usage\n'); const memBefore = process.memoryUsage(); console.log(' Memory Before:'); console.log(` Heap Used: ${(memBefore.heapUsed / 1024 / 1024).toFixed(2)} MB`); console.log(` Heap Total: ${(memBefore.heapTotal / 1024 / 1024).toFixed(2)} MB`); console.log(` RSS: ${(memBefore.rss / 1024 / 1024).toFixed(2)} MB\n`); // Execute many operations for (let i = 0; i < 100; i++) { await companyDataService.searchCompanies(`test${i}`, 10); } // Force garbage collection if available if (global.gc) { global.gc(); await new Promise(resolve => setTimeout(resolve, 1000)); } const memAfter = process.memoryUsage(); console.log(' Memory After 100 operations:'); console.log(` Heap Used: ${(memAfter.heapUsed / 1024 / 1024).toFixed(2)} MB`); console.log(` Heap Total: ${(memAfter.heapTotal / 1024 / 1024).toFixed(2)} MB`); console.log(` RSS: ${(memAfter.rss / 1024 / 1024).toFixed(2)} MB\n`); const heapGrowth = memAfter.heapUsed - memBefore.heapUsed; console.log(` Heap Growth: ${(heapGrowth / 1024 / 1024).toFixed(2)} MB\n`); // Target: Heap growth < 50MB for 100 operations const passed = heapGrowth < 50 * 1024 * 1024; results.push({ testName: 'Memory Usage', category: 'Performance', passed, metric: { operation: 'Memory Check', samples: 1, avgTime: heapGrowth / 1024 / 1024, minTime: 0, maxTime: 0, p50: 0, p95: 0, p99: 0, successRate: 100, throughput: 0 } }); if (!passed) { console.log(` ⚠️ WARNING: Heap growth ${(heapGrowth / 1024 / 1024).toFixed(2)}MB exceeds target of 50MB\n`); } else { console.log(` ✅ PASS: Heap growth within acceptable limits\n`); } } /** * Generate Performance Report */ function generateReport(): any { console.log('\n' + '='.repeat(80)); console.log('PERFORMANCE TEST REPORT'); console.log('='.repeat(80) + '\n'); const summary = { total: results.length, passed: results.filter(r => r.passed).length, failed: results.filter(r => !r.passed).length }; console.log('Summary:'); console.log(` Total Tests: ${summary.total}`); console.log(` Passed: ${summary.passed}`); console.log(` Failed: ${summary.failed}\n`); console.log('-'.repeat(80) + '\n'); // Show all metrics console.log('Performance Metrics:\n'); results.forEach((result, index) => { console.log(`${index + 1}. ${result.testName}`); if (result.metric) { const m = result.metric; console.log(` Operation: ${m.operation}`); console.log(` Success Rate: ${m.successRate.toFixed(2)}%`); if (m.avgTime > 0) { console.log(` Avg Time: ${m.avgTime.toFixed(2)}ms`); console.log(` P95: ${m.p95.toFixed(2)}ms`); } if (m.throughput > 0) { console.log(` Throughput: ${m.throughput.toFixed(2)} ops/sec`); } } console.log(` Result: ${result.passed ? '✅ PASS' : '❌ FAIL'}\n`); }); console.log('='.repeat(80) + '\n'); return { timestamp: new Date().toISOString(), summary, results }; } /** * Run All Performance Tests */ async function runPerformanceTests() { console.log('⚡ PERSONUPPLYSNING MCP SERVER - PERFORMANCE TEST SUITE\n'); console.log('='.repeat(80) + '\n'); try { await testLocalSearchPerformance(); await testCacheHitPerformance(); await testConcurrentRequests(); await testCacheStatistics(); await testDatabaseConnectionPool(); await testMemoryUsage(); const report = generateReport(); // Save report const fs = await import('fs/promises'); await fs.writeFile( 'testing-audit/performance-test-results.json', JSON.stringify(report, null, 2) ); console.log('📄 Full report saved to: testing-audit/performance-test-results.json\n'); process.exit(report.summary.failed > 0 ? 1 : 0); } catch (error) { console.error('❌ Test suite failed:', error); process.exit(1); } } // Run tests runPerformanceTests().catch(console.error);

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/isakskogstad/personupplysning-mcp'

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