Skip to main content
Glama
performance.test.tsโ€ข21.9 kB
import { describe, it } from 'node:test'; import { strict as assert } from 'node:assert'; /** * Performance tests for pagination and large dataset tools * * Tests performance characteristics of: * - Pagination logic and efficiency * - Large dataset processing * - Memory usage optimization * - Response time validation * - Batch processing efficiency */ describe('Performance Tests', () => { describe('Pagination Performance', () => { it('should validate efficient pagination logic', () => { const TOTAL_ITEMS = 4579; // MCP test cases const PAGE_SIZE = 100; const EXPECTED_PAGES = Math.ceil(TOTAL_ITEMS / PAGE_SIZE); // Simulate pagination state tracking const paginationTracker = { currentPage: 0, totalPages: EXPECTED_PAGES, itemsProcessed: 0, startTime: Date.now() }; // Simulate processing all pages for (let page = 0; page < EXPECTED_PAGES; page++) { const itemsInPage = Math.min(PAGE_SIZE, TOTAL_ITEMS - (page * PAGE_SIZE)); paginationTracker.itemsProcessed += itemsInPage; paginationTracker.currentPage = page; } const processingTime = Date.now() - paginationTracker.startTime; assert.equal(paginationTracker.itemsProcessed, TOTAL_ITEMS, 'should process all items'); assert.equal(paginationTracker.currentPage, EXPECTED_PAGES - 1, 'should reach last page'); assert.ok(processingTime < 100, 'pagination logic should be fast'); }); it('should validate token-based pagination efficiency', () => { const mockTokenPagination = { tokens: ['token1', 'token2', 'token3', 'token4', null], // null indicates end pageSize: 100, totalProcessed: 0, requestCount: 0, maxRequests: 50 }; // Simulate token-based pagination let currentTokenIndex = 0; while (currentTokenIndex < mockTokenPagination.tokens.length && mockTokenPagination.tokens[currentTokenIndex] !== null && mockTokenPagination.requestCount < mockTokenPagination.maxRequests) { mockTokenPagination.totalProcessed += mockTokenPagination.pageSize; mockTokenPagination.requestCount++; currentTokenIndex++; } assert.ok(mockTokenPagination.requestCount <= mockTokenPagination.maxRequests, 'should not exceed max request limit'); assert.ok(mockTokenPagination.totalProcessed > 0, 'should process items'); assert.equal(mockTokenPagination.requestCount, 4, 'should make expected number of requests'); }); it('should validate pagination memory efficiency', () => { const LARGE_DATASET_SIZE = 10000; const BATCH_SIZE = 100; const MEMORY_LIMIT_MB = 50; // Simulate memory-efficient batch processing const memoryUsageSimulation = { currentBatchSize: 0, maxBatchSize: BATCH_SIZE, totalProcessed: 0, estimatedMemoryMB: 0, batchCount: 0 }; for (let i = 0; i < LARGE_DATASET_SIZE; i++) { memoryUsageSimulation.currentBatchSize++; memoryUsageSimulation.estimatedMemoryMB = (memoryUsageSimulation.currentBatchSize * 1024) / (1024 * 1024); // 1KB per item if (memoryUsageSimulation.currentBatchSize >= BATCH_SIZE || i === LARGE_DATASET_SIZE - 1) { // Process batch memoryUsageSimulation.totalProcessed += memoryUsageSimulation.currentBatchSize; memoryUsageSimulation.batchCount++; memoryUsageSimulation.currentBatchSize = 0; memoryUsageSimulation.estimatedMemoryMB = 0; // Reset after processing } } assert.equal(memoryUsageSimulation.totalProcessed, LARGE_DATASET_SIZE, 'should process all items'); assert.ok(memoryUsageSimulation.estimatedMemoryMB < MEMORY_LIMIT_MB, 'should stay within memory limits'); assert.equal(memoryUsageSimulation.batchCount, Math.ceil(LARGE_DATASET_SIZE / BATCH_SIZE), 'should use correct number of batches'); }); it('should validate concurrent pagination handling', () => { const CONCURRENT_REQUESTS = 5; const ITEMS_PER_REQUEST = 100; const concurrentPagination = { activeRequests: 0, maxConcurrentRequests: CONCURRENT_REQUESTS, completedRequests: 0, totalItems: 0, errors: 0, startTime: Date.now() }; // Simulate concurrent request processing (synchronous for testing) const processRequest = (requestId: number) => { if (concurrentPagination.activeRequests < concurrentPagination.maxConcurrentRequests) { concurrentPagination.activeRequests++; // Simulate immediate request completion (no async) concurrentPagination.activeRequests--; concurrentPagination.completedRequests++; concurrentPagination.totalItems += ITEMS_PER_REQUEST; return true; } return false; }; // Queue multiple requests let successfulRequests = 0; for (let i = 0; i < 10; i++) { if (processRequest(i)) { successfulRequests++; } } assert.ok(successfulRequests <= 10, 'should process requests'); assert.equal(concurrentPagination.activeRequests, 0, 'should complete all requests'); assert.equal(concurrentPagination.completedRequests, successfulRequests, 'should track completed requests'); }); }); describe('Large Dataset Processing', () => { it('should validate efficient data structure usage', () => { const LARGE_SUITE_COUNT = 1000; const startTime = Date.now(); // Test Map vs Array performance for lookups const suiteMap = new Map(); const suiteArray: any[] = []; // Populate data structures for (let i = 0; i < LARGE_SUITE_COUNT; i++) { const suite = { id: i, title: `Suite ${i}`, parentSuiteId: i > 0 ? i - 1 : null }; suiteMap.set(i, suite); suiteArray.push(suite); } const populationTime = Date.now() - startTime; // Test lookup performance const lookupStartTime = Date.now(); const targetId = Math.floor(LARGE_SUITE_COUNT / 2); // Map lookup (O(1)) const mapResult = suiteMap.get(targetId); const mapLookupTime = Date.now() - lookupStartTime; // Array lookup (O(n)) const arrayLookupStartTime = Date.now(); const arrayResult = suiteArray.find(s => s.id === targetId); const arrayLookupTime = Date.now() - arrayLookupStartTime; assert.ok(mapResult, 'Map should find the item'); assert.ok(arrayResult, 'Array should find the item'); assert.ok(populationTime < 100, 'data structure population should be fast'); assert.ok(mapLookupTime <= arrayLookupTime, 'Map lookup should be faster than or equal to array lookup'); }); it('should validate memory-efficient string operations', () => { const LARGE_STRING_COUNT = 1000; const AVERAGE_STRING_LENGTH = 100; // Test efficient string concatenation const stringBuilderTest = { arrayJoinMethod: () => { const parts: string[] = []; for (let i = 0; i < LARGE_STRING_COUNT; i++) { parts.push('a'.repeat(AVERAGE_STRING_LENGTH)); } return parts.join(''); }, directConcatenation: () => { let result = ''; for (let i = 0; i < LARGE_STRING_COUNT; i++) { result += 'a'.repeat(AVERAGE_STRING_LENGTH); } return result; } }; const arrayJoinStart = Date.now(); const arrayJoinResult = stringBuilderTest.arrayJoinMethod(); const arrayJoinTime = Date.now() - arrayJoinStart; const directConcatStart = Date.now(); const directConcatResult = stringBuilderTest.directConcatenation(); const directConcatTime = Date.now() - directConcatStart; assert.equal(arrayJoinResult.length, directConcatResult.length, 'both methods should produce same length'); assert.ok(arrayJoinTime < 1000, 'array join should complete in reasonable time'); assert.ok(directConcatTime < 1000, 'direct concatenation should complete in reasonable time'); // Note: Array join is typically more efficient for large concatenations }); it('should validate efficient filtering and sorting', () => { const LARGE_DATASET = Array.from({ length: 5000 }, (_, i) => ({ id: i, title: `Item ${i}`, priority: ['LOW', 'MEDIUM', 'HIGH'][i % 3], score: Math.random() * 100, active: i % 2 === 0 })); const filteringStart = Date.now(); // Chain multiple operations efficiently const processedData = LARGE_DATASET .filter(item => item.active) // Filter first to reduce dataset .filter(item => item.priority === 'HIGH') // Then apply more specific filters .sort((a, b) => b.score - a.score) // Sort by score descending .slice(0, 100); // Take top 100 const filteringTime = Date.now() - filteringStart; assert.ok(processedData.length <= 100, 'should limit results to 100'); assert.ok(processedData.every(item => item.active), 'all results should be active'); assert.ok(processedData.every(item => item.priority === 'HIGH'), 'all results should be high priority'); assert.ok(filteringTime < 100, 'filtering and sorting should be fast'); // Validate sorting for (let i = 1; i < processedData.length; i++) { assert.ok(processedData[i - 1].score >= processedData[i].score, 'should be sorted by score descending'); } }); it('should validate efficient hierarchy processing', () => { const HIERARCHY_SIZE = 1000; const MAX_DEPTH = 10; // Generate hierarchical data const hierarchyData = Array.from({ length: HIERARCHY_SIZE }, (_, i) => ({ id: i + 1, title: `Node ${i + 1}`, parentId: i === 0 ? null : Math.floor((i + 1) / 2) // Binary tree structure })); const hierarchyProcessingStart = Date.now(); // Build hierarchy efficiently using Map for O(1) lookups const nodeMap = new Map(hierarchyData.map(node => [node.id, { ...node, children: [] as any[] }])); const roots: any[] = []; hierarchyData.forEach(node => { const nodeWithChildren = nodeMap.get(node.id); if (nodeWithChildren) { if (node.parentId === null) { roots.push(nodeWithChildren); } else { const parent = nodeMap.get(node.parentId); if (parent) { parent.children.push(nodeWithChildren); } } } }); // Calculate depth efficiently const calculateDepth = (node: any, currentDepth = 0): number => { if (node.children.length === 0) return currentDepth; return Math.max(...node.children.map((child: any) => calculateDepth(child, currentDepth + 1))); }; const maxDepth = Math.max(...roots.map(root => calculateDepth(root))); const hierarchyProcessingTime = Date.now() - hierarchyProcessingStart; assert.ok(roots.length > 0, 'should have root nodes'); assert.ok(maxDepth <= MAX_DEPTH, 'should not exceed maximum depth'); assert.ok(hierarchyProcessingTime < 200, 'hierarchy processing should be efficient'); }); }); describe('Response Time Validation', () => { it('should validate API response time expectations', () => { const responseTimeTargets = { listSuites: { target: 500, acceptable: 1000 }, // milliseconds getSuite: { target: 300, acceptable: 600 }, getTestCase: { target: 400, acceptable: 800 }, validateTestCase: { target: 2000, acceptable: 5000 }, generateDraft: { target: 3000, acceptable: 10000 } }; // Simulate response times const mockResponseTimes = { listSuites: 450, getSuite: 280, getTestCase: 380, validateTestCase: 1800, generateDraft: 2500 }; Object.entries(responseTimeTargets).forEach(([operation, targets]) => { const actualTime = mockResponseTimes[operation as keyof typeof mockResponseTimes]; assert.ok(actualTime <= targets.acceptable, `${operation} response time (${actualTime}ms) should be within acceptable limit (${targets.acceptable}ms)`); if (actualTime <= targets.target) { console.log(`โœ… ${operation}: ${actualTime}ms (target: ${targets.target}ms)`); } else { console.log(`โš ๏ธ ${operation}: ${actualTime}ms (exceeds target: ${targets.target}ms but within acceptable: ${targets.acceptable}ms)`); } }); }); it('should validate timeout handling', () => { const timeoutScenarios = [ { operation: 'quick_request', timeout: 5000, expectedTime: 2000, shouldTimeout: false }, { operation: 'slow_request', timeout: 3000, expectedTime: 5000, shouldTimeout: true }, { operation: 'normal_request', timeout: 10000, expectedTime: 1500, shouldTimeout: false } ]; timeoutScenarios.forEach(scenario => { const wouldTimeout = scenario.expectedTime > scenario.timeout; assert.equal(wouldTimeout, scenario.shouldTimeout, `${scenario.operation} timeout behavior should match expectation`); if (!wouldTimeout) { assert.ok(scenario.expectedTime < scenario.timeout, `${scenario.operation} should complete before timeout`); } }); }); it('should validate batch processing performance', () => { const TOTAL_ITEMS = 4579; const BATCH_SIZES = [50, 100, 200, 500]; const SIMULATED_REQUEST_TIME = 500; // milliseconds per request const batchPerformanceResults = BATCH_SIZES.map(batchSize => { const batchCount = Math.ceil(TOTAL_ITEMS / batchSize); const totalTime = batchCount * SIMULATED_REQUEST_TIME; const itemsPerSecond = TOTAL_ITEMS / (totalTime / 1000); return { batchSize, batchCount, totalTime, itemsPerSecond, efficiency: itemsPerSecond / batchSize // Items per second per batch size }; }); // Find optimal batch size const optimalBatch = batchPerformanceResults.reduce((best, current) => current.efficiency > best.efficiency ? current : best ); assert.ok(optimalBatch.batchSize >= 50, 'optimal batch size should be reasonable'); assert.ok(optimalBatch.batchSize <= 500, 'optimal batch size should not be excessive'); assert.ok(optimalBatch.totalTime < 60000, 'total processing time should be under 1 minute'); console.log(`๐Ÿ“Š Optimal batch size: ${optimalBatch.batchSize} (${optimalBatch.itemsPerSecond.toFixed(1)} items/sec)`); }); }); describe('Memory Usage Optimization', () => { it('should validate memory-efficient data processing', () => { const LARGE_DATASET_SIZE = 10000; const MEMORY_THRESHOLD_MB = 100; // Simulate memory usage tracking const memoryTracker = { currentUsageMB: 0, peakUsageMB: 0, itemsProcessed: 0, gcCollections: 0 }; // Simulate processing with memory monitoring for (let i = 0; i < LARGE_DATASET_SIZE; i++) { // Simulate item processing (1KB per item) memoryTracker.currentUsageMB += 0.001; memoryTracker.itemsProcessed++; // Update peak usage if (memoryTracker.currentUsageMB > memoryTracker.peakUsageMB) { memoryTracker.peakUsageMB = memoryTracker.currentUsageMB; } // Simulate garbage collection every 1000 items if (i % 1000 === 0 && i > 0) { memoryTracker.currentUsageMB *= 0.7; // Simulate 30% memory reclaim memoryTracker.gcCollections++; } } assert.equal(memoryTracker.itemsProcessed, LARGE_DATASET_SIZE, 'should process all items'); assert.ok(memoryTracker.peakUsageMB < MEMORY_THRESHOLD_MB, 'peak memory usage should be within threshold'); assert.ok(memoryTracker.gcCollections > 0, 'should trigger garbage collection'); }); it('should validate streaming data processing', () => { const STREAM_CHUNK_SIZE = 100; const TOTAL_CHUNKS = 50; const streamProcessor = { chunksProcessed: 0, currentChunkSize: 0, maxChunkSize: STREAM_CHUNK_SIZE, totalItemsProcessed: 0, memoryEfficient: true }; // Simulate streaming processing for (let chunk = 0; chunk < TOTAL_CHUNKS; chunk++) { streamProcessor.currentChunkSize = STREAM_CHUNK_SIZE; // Process chunk for (let item = 0; item < STREAM_CHUNK_SIZE; item++) { streamProcessor.totalItemsProcessed++; } // Clear chunk from memory streamProcessor.currentChunkSize = 0; streamProcessor.chunksProcessed++; } assert.equal(streamProcessor.chunksProcessed, TOTAL_CHUNKS, 'should process all chunks'); assert.equal(streamProcessor.totalItemsProcessed, TOTAL_CHUNKS * STREAM_CHUNK_SIZE, 'should process all items'); assert.equal(streamProcessor.currentChunkSize, 0, 'should clear chunk memory after processing'); assert.ok(streamProcessor.memoryEfficient, 'should maintain memory efficiency'); }); }); describe('Scalability Validation', () => { it('should validate performance scaling with data size', () => { const dataSizes = [100, 500, 1000, 5000, 10000]; const performanceResults: any[] = []; dataSizes.forEach(size => { const startTime = Date.now(); // Simulate data processing const data = Array.from({ length: size }, (_, i) => ({ id: i, value: Math.random() })); const processed = data .filter(item => item.value > 0.5) .map(item => ({ ...item, processed: true })) .sort((a, b) => a.value - b.value); const processingTime = Date.now() - startTime; const itemsPerMs = size / processingTime; performanceResults.push({ dataSize: size, processingTime, itemsPerMs, processedCount: processed.length }); }); // Validate that performance doesn't degrade exponentially for (let i = 1; i < performanceResults.length; i++) { const current = performanceResults[i]; const previous = performanceResults[i - 1]; const sizeRatio = current.dataSize / previous.dataSize; const timeRatio = current.processingTime / previous.processingTime; // Time should not increase more than 5x the data size increase (allow for variance) // Skip if processing time is very small (< 10ms) as measurements are unreliable if (current.processingTime >= 10 && previous.processingTime >= 10) { assert.ok(timeRatio <= sizeRatio * 5, `Processing time should scale reasonably with data size (${current.dataSize} items: ${current.processingTime}ms, ratio: ${timeRatio.toFixed(2)} vs size ratio: ${sizeRatio.toFixed(2)})`); } else { // For small processing times, just ensure it's not exponentially bad // Handle the case where previous processing time is 0 if (previous.processingTime === 0) { assert.ok(current.processingTime <= 50, `Processing time should remain reasonable for small datasets (${current.dataSize} items: ${current.processingTime}ms)`); } else { assert.ok(current.processingTime <= previous.processingTime * 10, `Processing time should not grow exponentially (${current.dataSize} items: ${current.processingTime}ms vs ${previous.dataSize} items: ${previous.processingTime}ms)`); } } } console.log('๐Ÿ“Š Performance scaling:'); performanceResults.forEach(result => { console.log(` ${result.dataSize} items: ${result.processingTime}ms (${result.itemsPerMs.toFixed(2)} items/ms)`); }); }); it('should validate concurrent processing limits', () => { const CONCURRENT_LIMITS = [1, 2, 5, 10, 20]; const TASK_COUNT = 100; const concurrencyResults = CONCURRENT_LIMITS.map(limit => { let completedTasks = 0; let maxActiveTasks = 0; // Simulate synchronous concurrent task processing while (completedTasks < TASK_COUNT) { const tasksToProcess = Math.min(limit, TASK_COUNT - completedTasks); maxActiveTasks = Math.max(maxActiveTasks, tasksToProcess); completedTasks += tasksToProcess; } const theoreticalMinTime = Math.ceil(TASK_COUNT / limit) * 10; // 10ms per task return { concurrencyLimit: limit, completedTasks, theoreticalMinTime, maxActiveTasks, efficiency: limit / Math.max(limit, 1) // Simple efficiency metric }; }); // Validate that all tasks were completed concurrencyResults.forEach(result => { assert.equal(result.completedTasks, TASK_COUNT, 'should complete all tasks'); assert.ok(result.maxActiveTasks <= result.concurrencyLimit, 'should not exceed concurrency limit'); }); }); }); });

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/maksimsarychau/mcp-zebrunner'

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