Skip to main content
Glama
regression-detection.test.js15.4 kB
import { vi } from "vitest"; import * as fs from "fs"; import * as path from "path"; import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); /** * Performance regression detection tests * These tests measure performance and fail if regression is detected */ describe("Performance Regression Detection", () => { let performanceBaseline; let performanceResults; const baselineFile = path.resolve(__dirname, "../baseline/performance-baseline.json"); const resultsFile = path.resolve(__dirname, "../results/performance-results.json"); beforeAll(async () => { // Create directories if they don't exist await fs.promises.mkdir(path.dirname(baselineFile), { recursive: true }); await fs.promises.mkdir(path.dirname(resultsFile), { recursive: true }); // Load performance baseline try { const baselineData = await fs.promises.readFile(baselineFile, "utf-8"); performanceBaseline = JSON.parse(baselineData); // Ensure thresholds exist (for backward compatibility) if (!performanceBaseline.thresholds) { performanceBaseline.thresholds = { regressionThreshold: 0.2, // 20% regression threshold memoryThreshold: 0.15, // 15% memory increase threshold throughputThreshold: 0.1, // 10% throughput decrease threshold }; // Update the baseline file with thresholds await fs.promises.writeFile(baselineFile, JSON.stringify(performanceBaseline, null, 2)); console.log("Added missing thresholds to performance baseline"); } } catch (_error) { // If no baseline exists, create an initial one performanceBaseline = { version: "1.0.0", timestamp: new Date().toISOString(), metrics: { apiResponseTime: { getPosts: { p50: 500, p95: 1000, p99: 2000 }, createPost: { p50: 800, p95: 1500, p99: 3000 }, uploadMedia: { p50: 2000, p95: 5000, p99: 10000 }, getUser: { p50: 300, p95: 600, p99: 1000 }, }, memoryUsage: { baseline: 50 * 1024 * 1024, // 50MB peak: 100 * 1024 * 1024, // 100MB }, throughput: { requestsPerSecond: 100, concurrentConnections: 10, }, }, thresholds: { regressionThreshold: 0.2, // 20% regression threshold memoryThreshold: 0.15, // 15% memory increase threshold throughputThreshold: 0.1, // 10% throughput decrease threshold }, }; await fs.promises.writeFile(baselineFile, JSON.stringify(performanceBaseline, null, 2)); console.log("Created initial performance baseline"); } performanceResults = { version: "1.2.0", timestamp: new Date().toISOString(), metrics: { apiResponseTime: {}, memoryUsage: {}, throughput: {}, }, }; }); afterAll(async () => { // Clear any remaining timers vi.clearAllTimers(); vi.useRealTimers(); // Save performance results await fs.promises.writeFile(resultsFile, JSON.stringify(performanceResults, null, 2)); }); describe("API Response Time Regression", () => { let mockClient; beforeAll(() => { // Use mock timers for consistent, fast testing vi.useFakeTimers(); // Use mock client with predictable timing for testing mockClient = { getPosts: () => new Promise((resolve) => { const delay = 500; // Fixed delay instead of random setTimeout(() => resolve([]), delay); }), createPost: () => new Promise((resolve) => { const delay = 800; // Fixed delay instead of random setTimeout(() => resolve({ id: 1 }), delay); }), uploadMedia: () => new Promise((resolve) => { const delay = 2000; // Fixed delay instead of random setTimeout(() => resolve({ id: 1 }), delay); }), getUser: () => new Promise((resolve) => { const delay = 300; // Fixed delay instead of random setTimeout(() => resolve({ id: 1 }), delay); }), }; }); afterAll(() => { vi.useRealTimers(); }); it("should not exceed baseline response time for getPosts", async () => { const iterations = 5; // Reduced iterations for faster tests const responseTimes = []; for (let i = 0; i < iterations; i++) { const startTime = process.hrtime.bigint(); const promise = mockClient.getPosts(); vi.advanceTimersByTime(500); // Advance fake timers await promise; const endTime = process.hrtime.bigint(); const responseTime = Number(endTime - startTime) / 1000000; // Convert to milliseconds responseTimes.push(responseTime); } const metrics = calculatePercentiles(responseTimes); performanceResults.metrics.apiResponseTime.getPosts = metrics; const baseline = performanceBaseline.metrics.apiResponseTime.getPosts; const threshold = performanceBaseline.thresholds?.regressionThreshold || 0.2; // Check for regression const p95Regression = (metrics.p95 - baseline.p95) / baseline.p95; const p99Regression = (metrics.p99 - baseline.p99) / baseline.p99; expect(p95Regression).toBeLessThan(threshold); expect(p99Regression).toBeLessThan(threshold); if (p95Regression > threshold || p99Regression > threshold) { throw new Error( `Performance regression detected in getPosts: P95=${metrics.p95}ms (baseline: ${baseline.p95}ms), P99=${metrics.p99}ms (baseline: ${baseline.p99}ms)`, ); } }, 5000); // Reduced timeout since we're using fake timers it("should not exceed baseline response time for createPost", async () => { const iterations = 5; // Reduced iterations for faster tests const responseTimes = []; for (let i = 0; i < iterations; i++) { const startTime = process.hrtime.bigint(); const promise = mockClient.createPost(); vi.advanceTimersByTime(800); // Advance fake timers await promise; const endTime = process.hrtime.bigint(); const responseTime = Number(endTime - startTime) / 1000000; responseTimes.push(responseTime); } const metrics = calculatePercentiles(responseTimes); performanceResults.metrics.apiResponseTime.createPost = metrics; const baseline = performanceBaseline.metrics.apiResponseTime.createPost; const threshold = performanceBaseline.thresholds?.regressionThreshold || 0.2; const p95Regression = (metrics.p95 - baseline.p95) / baseline.p95; expect(p95Regression).toBeLessThan(threshold); }, 30000); // 30 second timeout it("should not exceed baseline response time for uploadMedia", async () => { const iterations = 3; // Minimal iterations for slower operations const responseTimes = []; for (let i = 0; i < iterations; i++) { const startTime = process.hrtime.bigint(); const uploadPromise = mockClient.uploadMedia(); // Advance fake timers to resolve the setTimeout vi.advanceTimersByTime(2000); await uploadPromise; const endTime = process.hrtime.bigint(); const responseTime = Number(endTime - startTime) / 1000000; responseTimes.push(responseTime); } const metrics = calculatePercentiles(responseTimes); performanceResults.metrics.apiResponseTime.uploadMedia = metrics; const baseline = performanceBaseline.metrics.apiResponseTime.uploadMedia; const threshold = performanceBaseline.thresholds?.regressionThreshold || 0.2; const p95Regression = (metrics.p95 - baseline.p95) / baseline.p95; expect(p95Regression).toBeLessThan(threshold); }, 45000); // 45 second timeout }); describe("Memory Usage Regression", () => { it("should not exceed baseline memory usage", async () => { const initialMemory = process.memoryUsage(); // Simulate memory-intensive operations const operations = []; for (let i = 0; i < 10; i++) { // Reduced operations to avoid excessive memory usage operations.push(simulateMemoryIntensiveOperation()); } await Promise.all(operations); const finalMemory = process.memoryUsage(); const memoryIncrease = finalMemory.heapUsed - initialMemory.heapUsed; performanceResults.metrics.memoryUsage = { baseline: initialMemory.heapUsed, peak: finalMemory.heapUsed, increase: memoryIncrease, }; const baselineMemory = performanceBaseline.metrics.memoryUsage.baseline; const threshold = performanceBaseline.thresholds?.memoryThreshold || 0.15; const memoryRegression = memoryIncrease / baselineMemory; expect(memoryRegression).toBeLessThan(threshold); if (memoryRegression > threshold) { throw new Error( `Memory regression detected: ${memoryIncrease} bytes increase (${(memoryRegression * 100).toFixed( 2, )}% of baseline)`, ); } }); it("should detect memory leaks in long-running operations", async () => { const memorySnapshots = []; // Take memory snapshots during long-running operations for (let i = 0; i < 20; i++) { await simulateMemoryIntensiveOperation(); const memory = process.memoryUsage(); memorySnapshots.push(memory.heapUsed); // Allow garbage collection if (global.gc) { global.gc(); } // Small delay between operations await new Promise((resolve) => setTimeout(resolve, 100)); } // Check for consistent memory growth (potential leak) const memoryGrowth = analyzeMemoryGrowth(memorySnapshots); // Fail if memory consistently grows over 50% during test expect(memoryGrowth.trend).toBeLessThan(0.5); if (memoryGrowth.trend > 0.5) { throw new Error(`Potential memory leak detected: ${(memoryGrowth.trend * 100).toFixed(2)}% consistent growth`); } }); }); describe("Throughput Regression", () => { it("should maintain baseline throughput for concurrent requests", async () => { const concurrency = 10; const requestsPerBatch = 50; const batches = 3; const startTime = Date.now(); for (let batch = 0; batch < batches; batch++) { const concurrentPromises = []; for (let i = 0; i < concurrency; i++) { const batchPromises = []; for (let j = 0; j < requestsPerBatch / concurrency; j++) { batchPromises.push(simulateConcurrentRequest()); } concurrentPromises.push(Promise.all(batchPromises)); } await Promise.all(concurrentPromises); } const endTime = Date.now(); const totalRequests = requestsPerBatch * batches; const totalTime = (endTime - startTime) / 1000; // Convert to seconds const requestsPerSecond = totalRequests / totalTime; performanceResults.metrics.throughput = { requestsPerSecond, concurrentConnections: concurrency, totalRequests, totalTime, }; const baselineThroughput = performanceBaseline.metrics.throughput.requestsPerSecond; const threshold = performanceBaseline.thresholds?.throughputThreshold || 0.1; const throughputRegression = (baselineThroughput - requestsPerSecond) / baselineThroughput; expect(throughputRegression).toBeLessThan(threshold); if (throughputRegression > threshold) { throw new Error( `Throughput regression detected: ${requestsPerSecond.toFixed( 2, )} req/s (baseline: ${baselineThroughput} req/s)`, ); } }); }); describe("Resource Utilization Regression", () => { it("should not exceed CPU usage thresholds", async () => { const cpuUsageBefore = process.cpuUsage(); // CPU-intensive operations await simulateCPUIntensiveOperations(); const cpuUsageAfter = process.cpuUsage(cpuUsageBefore); const cpuTimeMs = (cpuUsageAfter.user + cpuUsageAfter.system) / 1000; // Convert to milliseconds // CPU usage should not exceed 5 seconds for our test operations expect(cpuTimeMs).toBeLessThan(5000); performanceResults.metrics.cpuUsage = { userTime: cpuUsageAfter.user, systemTime: cpuUsageAfter.system, totalTime: cpuTimeMs, }; }); it("should monitor file descriptor usage", async () => { // This test would monitor file descriptor leaks // Simulated for now, but would check actual FD usage in real implementation const initialFDs = 10; // Simulated baseline const finalFDs = 12; // Simulated final count const fdIncrease = finalFDs - initialFDs; // Should not increase file descriptors significantly during normal operations expect(fdIncrease).toBeLessThan(5); performanceResults.metrics.fileDescriptors = { initial: initialFDs, final: finalFDs, increase: fdIncrease, }; }); }); }); // Helper functions function calculatePercentiles(values) { const sorted = values.sort((a, b) => a - b); const len = sorted.length; return { p50: sorted[Math.floor(len * 0.5)], p95: sorted[Math.floor(len * 0.95)], p99: sorted[Math.floor(len * 0.99)], min: sorted[0], max: sorted[len - 1], avg: values.reduce((sum, val) => sum + val, 0) / len, }; } function simulateMemoryIntensiveOperation() { return new Promise((resolve) => { // Simulate memory allocation - reduced size const largeArray = new Array(1000).fill(0).map((_, i) => ({ id: i, data: `test-data-${i}`, timestamp: Date.now(), })); // Simulate some processing setTimeout(() => { // Clear reference to allow GC largeArray.length = 0; resolve(); }, 5); // Reduced delay }); } function simulateConcurrentRequest() { return new Promise((resolve) => { // Simulate API request delay setTimeout( () => { resolve({ success: true, timestamp: Date.now() }); }, Math.random() * 100 + 50, ); }); } function analyzeMemoryGrowth(snapshots) { if (snapshots.length < 2) return { trend: 0 }; // Simple linear regression to detect memory growth trend let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0; const n = snapshots.length; for (let i = 0; i < n; i++) { sumX += i; sumY += snapshots[i]; sumXY += i * snapshots[i]; sumXX += i * i; } const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX); const initialMemory = snapshots[0]; // Return trend as percentage of initial memory return { trend: Math.abs(slope * n) / initialMemory, slope, }; } async function simulateCPUIntensiveOperations() { // Simulate CPU-intensive work const iterations = 100000; const results = []; for (let i = 0; i < iterations; i++) { // Some mathematical operations const result = Math.sqrt(i) * Math.sin(i) + Math.cos(i * 2); results.push(result); } // Process results return results.reduce((sum, val) => sum + val, 0); }

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/docdyhr/mcp-wordpress'

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