Skip to main content
Glama

documcp

by tosin2013
memory-load-testing.test.ts25.6 kB
/** * Memory System Performance and Load Testing * Tests performance, scalability, and resource usage of memory system * Part of Issue #57 - Memory System Performance and Load Testing */ import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; import { performance } from 'perf_hooks'; import { MemoryManager } from '../../src/memory/manager.js'; import { EnhancedMemoryManager } from '../../src/memory/enhanced-manager.js'; import { IncrementalLearningSystem } from '../../src/memory/learning.js'; import { KnowledgeGraph } from '../../src/memory/knowledge-graph.js'; import { initializeMemory, rememberAnalysis, rememberRecommendation, getSimilarProjects, } from '../../src/memory/integration.js'; interface PerformanceMetrics { operationTime: number; memoryUsed: number; operationsPerSecond: number; throughput: number; } describe('Memory System Performance and Load Testing', () => { let tempDir: string; let memoryManager: MemoryManager; beforeEach(async () => { tempDir = path.join( os.tmpdir(), `memory-performance-test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, ); await fs.mkdir(tempDir, { recursive: true }); memoryManager = new MemoryManager(tempDir); await memoryManager.initialize(); }); afterEach(async () => { try { await fs.rm(tempDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } }); function measurePerformance<T>( operation: () => Promise<T>, ): Promise<{ result: T; metrics: PerformanceMetrics }> { return new Promise(async (resolve) => { const startTime = performance.now(); const startMemory = process.memoryUsage(); const result = await operation(); const endTime = performance.now(); const endMemory = process.memoryUsage(); const operationTime = endTime - startTime; const memoryUsed = endMemory.heapUsed - startMemory.heapUsed; resolve({ result, metrics: { operationTime, memoryUsed, operationsPerSecond: 1000 / operationTime, throughput: 1000 / operationTime, }, }); }); } describe('Basic Operations Performance', () => { test('should perform single memory operations efficiently', async () => { memoryManager.setContext({ projectId: 'performance-single' }); const testData = { projectId: 'performance-single', language: { primary: 'typescript' }, framework: { name: 'react' }, stats: { files: 100, lines: 10000 }, }; const { metrics: createMetrics } = await measurePerformance(async () => { return await memoryManager.remember('analysis', testData); }); const memoryId = (await memoryManager.remember('analysis', testData)).id; const { metrics: readMetrics } = await measurePerformance(async () => { return await memoryManager.recall(memoryId); }); const { metrics: searchMetrics } = await measurePerformance(async () => { return await memoryManager.search({ projectId: 'performance-single' }); }); // Performance expectations (adjust based on system capabilities) expect(createMetrics.operationTime).toBeLessThan(100); // 100ms expect(readMetrics.operationTime).toBeLessThan(50); // 50ms expect(searchMetrics.operationTime).toBeLessThan(100); // 100ms // Memory usage should be reasonable expect(createMetrics.memoryUsed).toBeLessThan(10 * 1024 * 1024); // 10MB expect(readMetrics.memoryUsed).toBeLessThan(1 * 1024 * 1024); // 1MB console.log('Single Operation Performance:'); console.log( `Create: ${createMetrics.operationTime.toFixed(2)}ms, Memory: ${( createMetrics.memoryUsed / 1024 ).toFixed(2)}KB`, ); console.log( `Read: ${readMetrics.operationTime.toFixed(2)}ms, Memory: ${( readMetrics.memoryUsed / 1024 ).toFixed(2)}KB`, ); console.log( `Search: ${searchMetrics.operationTime.toFixed(2)}ms, Memory: ${( searchMetrics.memoryUsed / 1024 ).toFixed(2)}KB`, ); }); test('should handle batch operations efficiently', async () => { memoryManager.setContext({ projectId: 'performance-batch' }); const batchSize = 100; const testData = Array.from({ length: batchSize }, (_, i) => ({ projectId: 'performance-batch', index: i, language: { primary: i % 2 === 0 ? 'typescript' : 'javascript' }, framework: { name: i % 3 === 0 ? 'react' : i % 3 === 1 ? 'vue' : 'angular' }, stats: { files: 10 + i, lines: 1000 + i * 100 }, })); const { metrics: batchCreateMetrics } = await measurePerformance(async () => { const promises = testData.map((data) => memoryManager.remember('analysis', data)); return await Promise.all(promises); }); const { metrics: batchSearchMetrics } = await measurePerformance(async () => { return await memoryManager.search({ projectId: 'performance-batch' }); }); // Batch operations should be efficient expect(batchCreateMetrics.operationTime).toBeLessThan(5000); // 5 seconds for 100 items expect(batchSearchMetrics.operationTime).toBeLessThan(1000); // 1 second to search 100 items // Calculate throughput const createThroughput = batchSize / (batchCreateMetrics.operationTime / 1000); const searchThroughput = batchSize / (batchSearchMetrics.operationTime / 1000); expect(createThroughput).toBeGreaterThan(20); // At least 20 ops/sec expect(searchThroughput).toBeGreaterThan(100); // At least 100 searches/sec console.log('Batch Operation Performance:'); console.log( `Create ${batchSize} items: ${batchCreateMetrics.operationTime.toFixed( 2, )}ms (${createThroughput.toFixed(2)} ops/sec)`, ); console.log( `Search ${batchSize} items: ${batchSearchMetrics.operationTime.toFixed( 2, )}ms (${searchThroughput.toFixed(2)} ops/sec)`, ); }); }); describe('Scalability Testing', () => { test('should scale linearly with data size', async () => { memoryManager.setContext({ projectId: 'scalability-test' }); const testSizes = [10, 50, 100, 500]; const results: Array<{ size: number; createTime: number; searchTime: number }> = []; for (const size of testSizes) { const testData = Array.from({ length: size }, (_, i) => ({ projectId: 'scalability-test', index: i, data: `test-data-${i}`, timestamp: new Date().toISOString(), })); // Measure creation time const { metrics: createMetrics } = await measurePerformance(async () => { const promises = testData.map((data) => memoryManager.remember('analysis', data)); return await Promise.all(promises); }); // Measure search time const { metrics: searchMetrics } = await measurePerformance(async () => { return await memoryManager.search({ projectId: 'scalability-test' }); }); results.push({ size, createTime: createMetrics.operationTime, searchTime: searchMetrics.operationTime, }); } // Verify roughly linear scaling (allow for some variance) for (let i = 1; i < results.length; i++) { const prev = results[i - 1]; const curr = results[i]; const sizeRatio = curr.size / prev.size; const createTimeRatio = curr.createTime / prev.createTime; const searchTimeRatio = curr.searchTime / prev.searchTime; // Create time should scale roughly linearly (within 3x of size ratio) expect(createTimeRatio).toBeLessThan(sizeRatio * 3); // Search time should not degrade too badly (within 2x of size ratio) expect(searchTimeRatio).toBeLessThan(sizeRatio * 2); } console.log('Scalability Results:'); results.forEach((result) => { console.log( `Size ${result.size}: Create ${result.createTime.toFixed( 2, )}ms, Search ${result.searchTime.toFixed(2)}ms`, ); }); }); test('should handle large individual memories efficiently', async () => { memoryManager.setContext({ projectId: 'large-memory-test' }); const sizes = [ { name: 'small', data: 'x'.repeat(1000) }, // 1KB { name: 'medium', data: 'x'.repeat(10000) }, // 10KB { name: 'large', data: 'x'.repeat(100000) }, // 100KB { name: 'xlarge', data: 'x'.repeat(1000000) }, // 1MB ]; const results: Array<{ name: string; createTime: number; readTime: number }> = []; for (const size of sizes) { const testData = { projectId: 'large-memory-test', size: size.name, content: size.data, metadata: { size: size.data.length }, }; // Measure creation time const { result: memory, metrics: createMetrics } = await measurePerformance(async () => { return await memoryManager.remember('analysis', testData); }); // Measure read time const { metrics: readMetrics } = await measurePerformance(async () => { return await memoryManager.recall(memory.id); }); results.push({ name: size.name, createTime: createMetrics.operationTime, readTime: readMetrics.operationTime, }); // Large memories should still be handled within reasonable time expect(createMetrics.operationTime).toBeLessThan(5000); // 5 seconds expect(readMetrics.operationTime).toBeLessThan(1000); // 1 second } console.log('Large Memory Performance:'); results.forEach((result) => { console.log( `${result.name}: Create ${result.createTime.toFixed(2)}ms, Read ${result.readTime.toFixed( 2, )}ms`, ); }); }); }); describe('Concurrent Operations Performance', () => { test('should handle concurrent read/write operations', async () => { memoryManager.setContext({ projectId: 'concurrent-test' }); // Pre-populate with some data const initialData = Array.from({ length: 50 }, (_, i) => ({ projectId: 'concurrent-test', index: i, data: `initial-data-${i}`, })); const initialMemories = await Promise.all( initialData.map((data) => memoryManager.remember('analysis', data)), ); const concurrentOperations = 20; const { metrics: concurrentMetrics } = await measurePerformance(async () => { const operations = Array.from({ length: concurrentOperations }, async (_, i) => { if (i % 3 === 0) { // Create new memory return await memoryManager.remember('analysis', { projectId: 'concurrent-test', index: 100 + i, data: `concurrent-data-${i}`, }); } else if (i % 3 === 1) { // Read existing memory const randomMemory = initialMemories[Math.floor(Math.random() * initialMemories.length)]; return await memoryManager.recall(randomMemory.id); } else { // Search memories return await memoryManager.search({ projectId: 'concurrent-test' }); } }); return await Promise.all(operations); }); expect(concurrentMetrics.operationTime).toBeLessThan(3000); // 3 seconds for 20 concurrent ops const throughput = concurrentOperations / (concurrentMetrics.operationTime / 1000); expect(throughput).toBeGreaterThan(5); // At least 5 concurrent ops/sec console.log('Concurrent Operations Performance:'); console.log( `${concurrentOperations} concurrent ops: ${concurrentMetrics.operationTime.toFixed( 2, )}ms (${throughput.toFixed(2)} ops/sec)`, ); }); test('should maintain performance under sustained load', async () => { memoryManager.setContext({ projectId: 'sustained-load-test' }); const testDuration = 3000; // 3 seconds const operationInterval = 100; // Every 100ms const results: number[] = []; const startTime = Date.now(); let operationCount = 0; while (Date.now() - startTime < testDuration) { const { metrics } = await measurePerformance(async () => { return await memoryManager.remember('analysis', { projectId: 'sustained-load-test', index: operationCount++, timestamp: Date.now(), data: `sustained-load-data-${operationCount}`, }); }); results.push(metrics.operationTime); // Wait for next interval await new Promise((resolve) => setTimeout(resolve, operationInterval)); } const avgTime = results.reduce((sum, time) => sum + time, 0) / results.length; const maxTime = Math.max(...results); const minTime = Math.min(...results); // Performance should remain consistent under sustained load expect(avgTime).toBeLessThan(200); // Average operation time < 200ms expect(maxTime).toBeLessThan(1000); // No single operation > 1 second // Performance degradation should be minimal const firstHalf = results.slice(0, Math.floor(results.length / 2)); const secondHalf = results.slice(Math.floor(results.length / 2)); const firstHalfAvg = firstHalf.reduce((sum, time) => sum + time, 0) / firstHalf.length; const secondHalfAvg = secondHalf.reduce((sum, time) => sum + time, 0) / secondHalf.length; const degradation = secondHalfAvg / firstHalfAvg; expect(degradation).toBeLessThan(2); // Less than 2x degradation console.log('Sustained Load Performance:'); console.log( `Operations: ${results.length}, Avg: ${avgTime.toFixed(2)}ms, Min: ${minTime.toFixed( 2, )}ms, Max: ${maxTime.toFixed(2)}ms`, ); console.log(`Performance degradation: ${((degradation - 1) * 100).toFixed(1)}%`); }); }); describe('Memory Resource Usage', () => { test('should manage memory usage efficiently', async () => { memoryManager.setContext({ projectId: 'memory-usage-test' }); const initialMemory = process.memoryUsage(); const memorySnapshots: Array<{ count: number; heapUsed: number }> = []; // Add memories in batches and monitor memory usage for (let batch = 0; batch < 10; batch++) { const batchSize = 100; const batchData = Array.from({ length: batchSize }, (_, i) => ({ projectId: 'memory-usage-test', batch, index: i, data: 'x'.repeat(1000), // 1KB per memory timestamp: new Date().toISOString(), })); await Promise.all(batchData.map((data) => memoryManager.remember('analysis', data))); const currentMemory = process.memoryUsage(); memorySnapshots.push({ count: (batch + 1) * batchSize, heapUsed: currentMemory.heapUsed - initialMemory.heapUsed, }); // Force garbage collection if available if (global.gc) { global.gc(); } } // Memory usage should be reasonable const finalMemoryUsage = memorySnapshots[memorySnapshots.length - 1]; const memoryPerItem = finalMemoryUsage.heapUsed / finalMemoryUsage.count; expect(memoryPerItem).toBeLessThan(50 * 1024); // Less than 50KB per memory item (including overhead) expect(finalMemoryUsage.heapUsed).toBeLessThan(100 * 1024 * 1024); // Less than 100MB total console.log('Memory Usage Analysis:'); console.log( `Total items: ${finalMemoryUsage.count}, Total memory: ${( finalMemoryUsage.heapUsed / 1024 / 1024 ).toFixed(2)}MB`, ); console.log(`Memory per item: ${(memoryPerItem / 1024).toFixed(2)}KB`); }); test('should not leak memory on cleanup operations', async () => { memoryManager.setContext({ projectId: 'memory-leak-test' }); const initialMemory = process.memoryUsage(); // Create and delete memories multiple times for (let cycle = 0; cycle < 5; cycle++) { const memories = []; // Create memories for (let i = 0; i < 100; i++) { const memory = await memoryManager.remember('analysis', { projectId: 'memory-leak-test', cycle, index: i, data: 'x'.repeat(1000), }); memories.push(memory); } // Delete all memories for (const memory of memories) { await memoryManager.forget(memory.id); } // Force garbage collection if (global.gc) { global.gc(); } } const finalMemory = process.memoryUsage(); const memoryDifference = finalMemory.heapUsed - initialMemory.heapUsed; // Memory usage should return close to initial levels expect(memoryDifference).toBeLessThan(15 * 1024 * 1024); // Less than 15MB difference console.log('Memory Leak Test:'); console.log(`Memory difference: ${(memoryDifference / 1024 / 1024).toFixed(2)}MB`); }); }); describe('Enhanced Components Performance', () => { test('should benchmark enhanced memory manager performance', async () => { const enhancedTempDir = path.join(tempDir, 'enhanced'); await fs.mkdir(enhancedTempDir, { recursive: true }); const enhancedManager = new EnhancedMemoryManager(enhancedTempDir); await enhancedManager.initialize(); enhancedManager.setContext({ projectId: 'enhanced-performance' }); const projectFeatures: import('../../src/memory/learning.js').ProjectFeatures = { language: 'typescript', framework: 'react', size: 'medium', complexity: 'moderate', hasTests: true, hasCI: true, hasDocs: true, isOpenSource: true, }; const baseRecommendation = { recommended: 'docusaurus', confidence: 0.8, score: 0.85, }; // Benchmark enhanced recommendation const { metrics: enhancedMetrics } = await measurePerformance(async () => { return await enhancedManager.getEnhancedRecommendation( '/test/enhanced-performance', baseRecommendation, projectFeatures, ); }); expect(enhancedMetrics.operationTime).toBeLessThan(5000); // 5 seconds // Benchmark intelligent analysis const analysisData = { language: 'typescript', framework: 'react', size: 'medium', hasTests: true, hasCI: true, }; const { metrics: analysisMetrics } = await measurePerformance(async () => { return await enhancedManager.getIntelligentAnalysis( '/test/enhanced-performance', analysisData, ); }); expect(analysisMetrics.operationTime).toBeLessThan(3000); // 3 seconds console.log('Enhanced Components Performance:'); console.log(`Enhanced recommendation: ${enhancedMetrics.operationTime.toFixed(2)}ms`); console.log(`Intelligent analysis: ${analysisMetrics.operationTime.toFixed(2)}ms`); }); test('should benchmark learning system performance', async () => { const learningTempDir = path.join(tempDir, 'learning'); await fs.mkdir(learningTempDir, { recursive: true }); const tempLearningManager = new MemoryManager(learningTempDir); await tempLearningManager.initialize(); const learningSystem = new IncrementalLearningSystem(tempLearningManager); await learningSystem.initialize(); const projectFeatures: import('../../src/memory/learning.js').ProjectFeatures = { language: 'python', framework: 'django', size: 'large', complexity: 'complex', hasTests: true, hasCI: true, hasDocs: false, isOpenSource: true, }; const baseRecommendation = { recommended: 'sphinx', confidence: 0.7, }; // Add training data through memory manager (learning system learns from stored memories) for (let i = 0; i < 50; i++) { await tempLearningManager.remember('analysis', { ...projectFeatures, index: i, feedback: { rating: 3 + (i % 3), helpful: i % 2 === 0, comments: `Training feedback ${i}`, }, }); } // Benchmark improved recommendation const { metrics: improveMetrics } = await measurePerformance(async () => { return await learningSystem.getImprovedRecommendation(projectFeatures, baseRecommendation); }); expect(improveMetrics.operationTime).toBeLessThan(1000); // 1 second // Benchmark pattern detection const { metrics: patternMetrics } = await measurePerformance(async () => { return await learningSystem.getPatterns(); }); expect(patternMetrics.operationTime).toBeLessThan(2000); // 2 seconds console.log('Learning System Performance:'); console.log(`Improved recommendation: ${improveMetrics.operationTime.toFixed(2)}ms`); console.log(`Pattern detection: ${patternMetrics.operationTime.toFixed(2)}ms`); }); test('should benchmark knowledge graph performance', async () => { const graphTempDir = path.join(tempDir, 'graph'); await fs.mkdir(graphTempDir, { recursive: true }); const tempGraphManager = new MemoryManager(graphTempDir); await tempGraphManager.initialize(); const knowledgeGraph = new KnowledgeGraph(tempGraphManager); await knowledgeGraph.initialize(); // Add nodes and edges const nodeCount = 100; const edgeCount = 200; const { metrics: buildMetrics } = await measurePerformance(async () => { // Add nodes for (let i = 0; i < nodeCount; i++) { knowledgeGraph.addNode({ id: `node-${i}`, type: i % 3 === 0 ? 'project' : i % 3 === 1 ? 'technology' : 'pattern', label: `Node ${i}`, weight: 1.0, properties: { name: `Node ${i}`, category: i % 5 === 0 ? 'frontend' : 'backend', }, }); } // Add edges for (let i = 0; i < edgeCount; i++) { const sourceId = `node-${Math.floor(Math.random() * nodeCount)}`; const targetId = `node-${Math.floor(Math.random() * nodeCount)}`; if (sourceId !== targetId) { knowledgeGraph.addEdge({ source: sourceId, target: targetId, type: i % 2 === 0 ? 'uses' : 'similar_to', weight: Math.random(), properties: {}, confidence: Math.random(), }); } } }); expect(buildMetrics.operationTime).toBeLessThan(5000); // 5 seconds to build graph // Benchmark pathfinding const { metrics: pathMetrics } = await measurePerformance(async () => { return knowledgeGraph.findPath('node-0', 'node-50'); }); expect(pathMetrics.operationTime).toBeLessThan(500); // 500ms for pathfinding // Benchmark node queries (using available methods) const { metrics: queryMetrics } = await measurePerformance(async () => { return knowledgeGraph.getAllNodes(); }); expect(queryMetrics.operationTime).toBeLessThan(1000); // 1 second for node queries console.log('Knowledge Graph Performance:'); console.log( `Build graph (${nodeCount} nodes, ${edgeCount} edges): ${buildMetrics.operationTime.toFixed( 2, )}ms`, ); console.log(`Find path: ${pathMetrics.operationTime.toFixed(2)}ms`); console.log(`Nodes query: ${queryMetrics.operationTime.toFixed(2)}ms`); }); }); describe('Integration Performance', () => { test('should benchmark MCP integration performance', async () => { // Test memory integration functions const analysisData = { projectId: 'integration-performance', language: { primary: 'go' }, framework: { name: 'gin' }, stats: { files: 200, lines: 50000 }, }; const { metrics: analysisMetrics } = await measurePerformance(async () => { return await rememberAnalysis('/test/integration-performance', analysisData); }); const { metrics: recommendationMetrics } = await measurePerformance(async () => { return await rememberRecommendation('analysis-id', { recommended: 'hugo', confidence: 0.9, }); }); const { metrics: similarMetrics } = await measurePerformance(async () => { return await getSimilarProjects(analysisData, 5); }); expect(analysisMetrics.operationTime).toBeLessThan(1000); // 1 second expect(recommendationMetrics.operationTime).toBeLessThan(1000); // 1 second expect(similarMetrics.operationTime).toBeLessThan(2000); // 2 seconds console.log('MCP Integration Performance:'); console.log(`Remember analysis: ${analysisMetrics.operationTime.toFixed(2)}ms`); console.log(`Remember recommendation: ${recommendationMetrics.operationTime.toFixed(2)}ms`); console.log(`Get similar projects: ${similarMetrics.operationTime.toFixed(2)}ms`); }); }); });

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/tosin2013/documcp'

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