memory-performance-validation.test.ts•31 kB
/**
* Memory Performance Validation Test Suite
*
* Comprehensive performance validation for all memory optimization features
* with precise measurements and benchmark comparisons.
*/
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
import { performance } from 'perf_hooks';
import { MemoryLeakDetector, MemoryLeakIntegration } from '../../core/memory-leak-detector';
import { GarbageCollectionOptimizer } from '../../core/gc-optimizer';
import { PerformanceTracker } from '../../services/performance-tracker';
import { CacheManager } from '../../core/cache/cache-manager';
import { ParetoFrontier } from '../../core/pareto-frontier';
import { PromptCandidate } from '../../types/gepa';
interface PerformanceBenchmark {
operation: string;
baseline: number;
optimized: number;
improvement: number;
memoryImpact: number;
samples: number;
variance: number;
}
interface MemoryEfficiencyMetrics {
heapUtilization: number;
gcEfficiency: number;
objectPoolHitRate: number;
bufferReuseRate: number;
leakDetectionAccuracy: number;
cleanupEffectiveness: number;
}
describe('Memory Performance Validation Suite', () => {
let memoryLeakDetector: MemoryLeakDetector;
let gcOptimizer: GarbageCollectionOptimizer;
let performanceTracker: PerformanceTracker;
let cacheManager: CacheManager;
let paretoFrontier: ParetoFrontier;
// Performance thresholds
const PERFORMANCE_THRESHOLDS = {
maxOperationLatency: 100, // milliseconds
minGCEfficiency: 0.3, // 30% memory reclaimed
minPoolHitRate: 0.6, // 60% hit rate
maxMemoryGrowth: 50 * 1024 * 1024, // 50MB
minCleanupEffectiveness: 0.7, // 70% effective
};
beforeEach(async () => {
performanceTracker = new PerformanceTracker();
memoryLeakDetector = MemoryLeakIntegration.initialize({
heapGrowthRate: 2,
maxHeapSize: 100,
maxObjectCount: 1000,
memoryIncreaseThreshold: 15,
monitoringWindow: 5000,
snapshotInterval: 1000,
});
gcOptimizer = new GarbageCollectionOptimizer(performanceTracker, memoryLeakDetector);
gcOptimizer.setOptimizationStrategy('high-throughput'); // Start with performance-focused
cacheManager = new CacheManager({
l1MaxSize: 5 * 1024 * 1024,
l1MaxEntries: 1000,
l2Enabled: true,
l2MaxSize: 10 * 1024 * 1024,
l2MaxEntries: 2000,
enableMemoryTracking: true,
});
paretoFrontier = new ParetoFrontier({
objectives: [
{
name: 'performance',
weight: 1,
direction: 'maximize',
extractor: (candidate: PromptCandidate) => candidate.averageScore,
},
{
name: 'efficiency',
weight: 0.8,
direction: 'minimize',
extractor: (candidate: PromptCandidate) => candidate.rolloutCount || 1,
},
],
maxSize: 200,
enableMemoryTracking: true,
});
});
afterEach(async () => {
if (cacheManager) await cacheManager.shutdown();
if (paretoFrontier) paretoFrontier.clear();
if (gcOptimizer) gcOptimizer.shutdown();
if (memoryLeakDetector) memoryLeakDetector.shutdown();
MemoryLeakIntegration.shutdown();
});
describe('GC Optimization Performance Validation', () => {
test('should validate GC strategy performance improvements', async () => {
const strategies = ['balanced', 'high-throughput', 'low-latency', 'memory-intensive'];
const strategyBenchmarks: Array<PerformanceBenchmark & { strategy: string }> = [];
for (const strategy of strategies) {
gcOptimizer.setOptimizationStrategy(strategy);
const samples: number[] = [];
const memoryImpacts: number[] = [];
// Benchmark each strategy
for (let sample = 0; sample < 20; sample++) {
const beforeMemory = process.memoryUsage().heapUsed;
const startTime = performance.now();
// Standard workload
for (let i = 0; i < 100; i++) {
await cacheManager.set(`${strategy}-bench-${sample}-${i}`, {
data: 'x'.repeat(1000),
strategy,
sample,
});
if (i % 20 === 0) {
await gcOptimizer.forceGarbageCollection(`${strategy}-benchmark`);
}
}
const endTime = performance.now();
const afterMemory = process.memoryUsage().heapUsed;
samples.push(endTime - startTime);
memoryImpacts.push(afterMemory - beforeMemory);
// Cleanup between samples
await cacheManager.clear();
await gcOptimizer.forceGarbageCollection('sample-cleanup');
}
const avgDuration = samples.reduce((sum, s) => sum + s, 0) / samples.length;
const avgMemoryImpact = memoryImpacts.reduce((sum, m) => sum + m, 0) / memoryImpacts.length;
const variance = samples.reduce((sum, s) => sum + Math.pow(s - avgDuration, 2), 0) / samples.length;
strategyBenchmarks.push({
strategy,
operation: `gc-strategy-${strategy}`,
baseline: samples[0], // First sample as baseline
optimized: avgDuration,
improvement: (samples[0] - avgDuration) / samples[0],
memoryImpact: avgMemoryImpact,
samples: samples.length,
variance,
});
}
// Validate strategy performance characteristics
const balancedBench = strategyBenchmarks.find(b => b.strategy === 'balanced')!;
const highThroughputBench = strategyBenchmarks.find(b => b.strategy === 'high-throughput')!;
const lowLatencyBench = strategyBenchmarks.find(b => b.strategy === 'low-latency')!;
const memoryIntensiveBench = strategyBenchmarks.find(b => b.strategy === 'memory-intensive')!;
// All strategies should complete in reasonable time
strategyBenchmarks.forEach(bench => {
expect(bench.optimized).toBeLessThan(PERFORMANCE_THRESHOLDS.maxOperationLatency * 50); // 5 second max
expect(bench.variance).toBeLessThan(bench.optimized * 0.5); // Variance should be reasonable
});
// Low-latency should generally have lower variance (more predictable)
expect(lowLatencyBench.variance).toBeLessThanOrEqual(highThroughputBench.variance);
// Memory-intensive should handle larger memory impacts
expect(memoryIntensiveBench.memoryImpact).toBeGreaterThanOrEqual(0); // Should handle memory well
});
test('should measure object pool performance gains', async () => {
const poolBenchmarks: PerformanceBenchmark[] = [];
const poolNames = ['candidates', 'trajectory-data', 'analysis-results'];
for (const poolName of poolNames) {
const pool = gcOptimizer.getObjectPool(poolName);
if (!pool) continue;
// Benchmark with pool
const withPoolSamples: number[] = [];
for (let i = 0; i < 100; i++) {
const startTime = performance.now();
const obj = pool.get();
// Simulate usage
(obj as any).testData = 'x'.repeat(100);
pool.return(obj);
const endTime = performance.now();
withPoolSamples.push(endTime - startTime);
}
// Benchmark without pool (direct allocation)
const withoutPoolSamples: number[] = [];
for (let i = 0; i < 100; i++) {
const startTime = performance.now();
// Direct allocation
const obj = pool.config.factory();
(obj as any).testData = 'x'.repeat(100);
// No return - let GC handle it
const endTime = performance.now();
withoutPoolSamples.push(endTime - startTime);
}
const withPoolAvg = withPoolSamples.reduce((sum, s) => sum + s, 0) / withPoolSamples.length;
const withoutPoolAvg = withoutPoolSamples.reduce((sum, s) => sum + s, 0) / withoutPoolSamples.length;
poolBenchmarks.push({
operation: `object-pool-${poolName}`,
baseline: withoutPoolAvg,
optimized: withPoolAvg,
improvement: (withoutPoolAvg - withPoolAvg) / withoutPoolAvg,
memoryImpact: 0, // Would need more sophisticated measurement
samples: withPoolSamples.length,
variance: withPoolSamples.reduce((sum, s) => sum + Math.pow(s - withPoolAvg, 2), 0) / withPoolSamples.length,
});
}
// Validate pool performance
poolBenchmarks.forEach(bench => {
expect(bench.optimized).toBeLessThan(PERFORMANCE_THRESHOLDS.maxOperationLatency);
// Pool should generally be faster or at least not significantly slower
expect(bench.improvement).toBeGreaterThan(-0.5); // Not more than 50% slower
});
});
test('should validate buffer reuse performance', async () => {
const bufferSizes = [1024, 4096, 16384, 65536];
const bufferBenchmarks: Array<PerformanceBenchmark & { size: number }> = [];
for (const size of bufferSizes) {
// Benchmark with reuse
const withReuseSamples: number[] = [];
const buffers: Buffer[] = [];
for (let i = 0; i < 200; i++) {
const startTime = performance.now();
const buffer = gcOptimizer.getBuffer(size);
buffer.fill(i % 256);
buffers.push(buffer);
const endTime = performance.now();
withReuseSamples.push(endTime - startTime);
// Return half the buffers for reuse
if (i % 2 === 0 && buffers.length > 1) {
gcOptimizer.returnBuffer(buffers.shift()!);
}
}
// Benchmark without reuse
const withoutReuseSamples: number[] = [];
for (let i = 0; i < 200; i++) {
const startTime = performance.now();
const buffer = Buffer.alloc(size);
buffer.fill(i % 256);
const endTime = performance.now();
withoutReuseSamples.push(endTime - startTime);
}
const withReuseAvg = withReuseSamples.reduce((sum, s) => sum + s, 0) / withReuseSamples.length;
const withoutReuseAvg = withoutReuseSamples.reduce((sum, s) => sum + s, 0) / withoutReuseSamples.length;
bufferBenchmarks.push({
size,
operation: `buffer-reuse-${size}`,
baseline: withoutReuseAvg,
optimized: withReuseAvg,
improvement: (withoutReuseAvg - withReuseAvg) / withoutReuseAvg,
memoryImpact: 0,
samples: withReuseSamples.length,
variance: withReuseSamples.reduce((sum, s) => sum + Math.pow(s - withReuseAvg, 2), 0) / withReuseSamples.length,
});
// Cleanup
buffers.forEach(buffer => gcOptimizer.returnBuffer(buffer));
}
// Validate buffer reuse performance
bufferBenchmarks.forEach(bench => {
expect(bench.optimized).toBeLessThan(PERFORMANCE_THRESHOLDS.maxOperationLatency);
// Buffer reuse should be beneficial for larger sizes
if (bench.size >= 16384) {
expect(bench.improvement).toBeGreaterThanOrEqual(-0.2); // Allow slight overhead for smaller benefits
}
});
});
});
describe('Memory Leak Detection Performance', () => {
test('should validate leak detection latency and accuracy', async () => {
const detectionMetrics = {
detectionLatencies: [] as number[],
accuracyScores: [] as number[],
throughputMeasurements: [] as number[],
memoryOverhead: [] as number[],
};
// Create controlled leak scenarios
const leakScenarios = [
{ name: 'rapid-accumulation', operations: 500, size: 1000 },
{ name: 'gradual-growth', operations: 200, size: 2000 },
{ name: 'cyclic-references', operations: 100, size: 5000 },
];
for (const scenario of leakScenarios) {
const scenarioStart = Date.now();
const beforeMemory = process.memoryUsage().heapUsed;
let leaksDetected = 0;
// Set up detection monitoring
const detectionHandler = () => leaksDetected++;
memoryLeakDetector.on('memoryLeakDetected', detectionHandler);
try {
// Create leak pattern
const leakyObjects: any[] = [];
for (let i = 0; i < scenario.operations; i++) {
const obj = {
id: `${scenario.name}-${i}`,
data: 'x'.repeat(scenario.size),
timestamp: Date.now(),
refs: [] as any[],
};
// Create references based on scenario
if (scenario.name === 'cyclic-references' && leakyObjects.length > 0) {
obj.refs.push(leakyObjects[leakyObjects.length - 1]);
leakyObjects[leakyObjects.length - 1].refs.push(obj);
}
leakyObjects.push(obj);
memoryLeakDetector.trackObjectAllocation('performance-test', obj, scenario.size);
// Periodic detection
if (i % 50 === 0) {
const detectionStart = performance.now();
await memoryLeakDetector.detectMemoryLeaks();
const detectionEnd = performance.now();
detectionMetrics.detectionLatencies.push(detectionEnd - detectionStart);
}
}
// Final detection
const finalDetectionStart = performance.now();
await memoryLeakDetector.detectMemoryLeaks();
const finalDetectionEnd = performance.now();
const scenarioEnd = Date.now();
const afterMemory = process.memoryUsage().heapUsed;
detectionMetrics.detectionLatencies.push(finalDetectionEnd - finalDetectionStart);
detectionMetrics.throughputMeasurements.push(scenario.operations / ((scenarioEnd - scenarioStart) / 1000));
detectionMetrics.memoryOverhead.push(afterMemory - beforeMemory);
// Calculate accuracy (should detect leak in this scenario)
const expectedDetections = 1; // At least one leak should be detected
const accuracyScore = Math.min(1.0, leaksDetected / expectedDetections);
detectionMetrics.accuracyScores.push(accuracyScore);
} finally {
memoryLeakDetector.off('memoryLeakDetected', detectionHandler);
await memoryLeakDetector.forceCleanup();
}
}
// Validate detection performance
const avgDetectionLatency = detectionMetrics.detectionLatencies.reduce((sum, lat) => sum + lat, 0) /
detectionMetrics.detectionLatencies.length;
const avgAccuracy = detectionMetrics.accuracyScores.reduce((sum, acc) => sum + acc, 0) /
detectionMetrics.accuracyScores.length;
const avgThroughput = detectionMetrics.throughputMeasurements.reduce((sum, thr) => sum + thr, 0) /
detectionMetrics.throughputMeasurements.length;
expect(avgDetectionLatency).toBeLessThan(1000); // Less than 1 second
expect(avgAccuracy).toBeGreaterThan(0.7); // At least 70% accuracy
expect(avgThroughput).toBeGreaterThan(50); // At least 50 operations/second
});
test('should validate cleanup performance effectiveness', async () => {
const cleanupBenchmarks: Array<{
type: string;
beforeObjects: number;
afterObjects: number;
beforeMemory: number;
afterMemory: number;
duration: number;
effectiveness: number;
}> = [];
const cleanupTypes = [
{ name: 'force-cleanup', executor: () => memoryLeakDetector.forceCleanup() },
{ name: 'gc-optimization', executor: () => gcOptimizer.forceGarbageCollection('cleanup-test') },
{ name: 'component-cleanup', executor: () => Promise.all([
cacheManager['performCleanup'](),
paretoFrontier.performMemoryCleanup(),
])},
];
for (const cleanupType of cleanupTypes) {
// Create objects to clean up
const trackableObjects: any[] = [];
for (let i = 0; i < 500; i++) {
const obj = {
id: `cleanup-test-${i}`,
data: 'x'.repeat(2000),
timestamp: Date.now(),
};
trackableObjects.push(obj);
await cacheManager.set(`cleanup-${i}`, obj);
memoryLeakDetector.trackObjectAllocation('cleanup-test', obj, 2000);
}
const beforeObjects = 500;
const beforeMemory = process.memoryUsage().heapUsed;
// Execute cleanup
const startTime = performance.now();
await cleanupType.executor();
const endTime = performance.now();
const afterMemory = process.memoryUsage().heapUsed;
// Estimate remaining objects (simplified)
const stats = memoryLeakDetector.getStatistics();
const cleanupComponent = stats.components.find(c => c.name === 'cleanup-test');
const afterObjects = cleanupComponent?.objectCount || 0;
const effectiveness = Math.max(0, (beforeObjects - afterObjects) / beforeObjects);
cleanupBenchmarks.push({
type: cleanupType.name,
beforeObjects,
afterObjects,
beforeMemory,
afterMemory,
duration: endTime - startTime,
effectiveness,
});
// Reset for next test
await memoryLeakDetector.forceCleanup();
await cacheManager.clear();
}
// Validate cleanup performance
cleanupBenchmarks.forEach(bench => {
expect(bench.duration).toBeLessThan(5000); // Less than 5 seconds
expect(bench.effectiveness).toBeGreaterThan(PERFORMANCE_THRESHOLDS.minCleanupEffectiveness);
expect(bench.afterMemory).toBeLessThanOrEqual(bench.beforeMemory); // Should not increase memory
});
// Force cleanup should be most effective
const forceCleanup = cleanupBenchmarks.find(b => b.type === 'force-cleanup');
if (forceCleanup) {
expect(forceCleanup.effectiveness).toBeGreaterThan(0.5); // At least 50% effective
}
});
});
describe('End-to-End Performance Validation', () => {
test('should validate overall system performance with memory optimization', async () => {
const systemBenchmark = {
totalOperations: 0,
averageLatency: 0,
memoryEfficiency: 0,
systemStability: true,
performanceRegression: false,
};
const operationLatencies: number[] = [];
const memorySnapshots: number[] = [];
Date.now();
const startMemory = process.memoryUsage().heapUsed;
// Comprehensive system workout
for (let round = 0; round < 50; round++) {
const roundStart = performance.now();
// Mixed operations
const operations = [
// Cache operations
async () => {
await cacheManager.set(`system-test-${round}`, {
data: 'x'.repeat(1500),
round,
timestamp: Date.now(),
});
},
// Frontier operations
async () => {
const candidate: PromptCandidate = {
id: `system-candidate-${round}`,
generation: Math.floor(round / 10) + 1,
content: `System test candidate ${round}. ${'Content '.repeat(30)}`,
averageScore: Math.random(),
rolloutCount: round + 1,
timestamp: new Date(),
};
await paretoFrontier.addCandidate(candidate);
},
// Memory management operations
async () => {
if (round % 10 === 0) {
await gcOptimizer.forceGarbageCollection(`system-test-${round}`);
}
if (round % 15 === 0) {
await memoryLeakDetector.detectMemoryLeaks();
}
},
];
await Promise.all(operations.map(op => op()));
const roundEnd = performance.now();
operationLatencies.push(roundEnd - roundStart);
memorySnapshots.push(process.memoryUsage().heapUsed);
systemBenchmark.totalOperations += operations.length;
// Check for system stability
const currentMemory = process.memoryUsage().heapUsed;
if (currentMemory > startMemory + PERFORMANCE_THRESHOLDS.maxMemoryGrowth) {
systemBenchmark.systemStability = false;
}
// Micro-pause
await new Promise(resolve => setImmediate(resolve));
}
// Calculate performance metrics
systemBenchmark.averageLatency = operationLatencies.reduce((sum, lat) => sum + lat, 0) / operationLatencies.length;
// Check for performance regression
const firstQuarter = operationLatencies.slice(0, Math.floor(operationLatencies.length / 4));
const lastQuarter = operationLatencies.slice(-Math.floor(operationLatencies.length / 4));
const firstAvg = firstQuarter.reduce((sum, lat) => sum + lat, 0) / firstQuarter.length;
const lastAvg = lastQuarter.reduce((sum, lat) => sum + lat, 0) / lastQuarter.length;
systemBenchmark.performanceRegression = (lastAvg / firstAvg) > 2.0; // More than 2x slower
// Calculate memory efficiency
const endMemory = process.memoryUsage().heapUsed;
const memoryGrowth = endMemory - startMemory;
const expectedGrowth = systemBenchmark.totalOperations * 1000; // Rough estimate
systemBenchmark.memoryEfficiency = Math.max(0, 1 - (memoryGrowth / expectedGrowth));
// Final cleanup and measurement
await gcOptimizer.forceGarbageCollection('system-test-final');
const finalMemory = process.memoryUsage().heapUsed;
// Validate system performance
expect(systemBenchmark.totalOperations).toBe(150); // 50 rounds * 3 operations
expect(systemBenchmark.averageLatency).toBeLessThan(PERFORMANCE_THRESHOLDS.maxOperationLatency);
expect(systemBenchmark.systemStability).toBe(true);
expect(systemBenchmark.performanceRegression).toBe(false);
expect(systemBenchmark.memoryEfficiency).toBeGreaterThan(0.3); // At least 30% efficient
expect(finalMemory).toBeLessThan(endMemory + (10 * 1024 * 1024)); // Cleanup should help
});
test('should measure memory efficiency metrics', async () => {
const efficiencyMetrics: MemoryEfficiencyMetrics = {
heapUtilization: 0,
gcEfficiency: 0,
objectPoolHitRate: 0,
bufferReuseRate: 0,
leakDetectionAccuracy: 0,
cleanupEffectiveness: 0,
};
// Measure heap utilization
process.memoryUsage();
// Create measured workload
for (let i = 0; i < 200; i++) {
await cacheManager.set(`efficiency-${i}`, {
data: 'x'.repeat(1000),
index: i,
});
}
const workloadMemory = process.memoryUsage();
efficiencyMetrics.heapUtilization = workloadMemory.heapUsed / workloadMemory.heapTotal;
// Measure GC efficiency
const beforeGC = process.memoryUsage().heapUsed;
await gcOptimizer.forceGarbageCollection('efficiency-test');
const afterGC = process.memoryUsage().heapUsed;
efficiencyMetrics.gcEfficiency = Math.max(0, (beforeGC - afterGC) / beforeGC);
// Measure object pool efficiency
const candidatesPool = gcOptimizer.getObjectPool('candidates');
if (candidatesPool) {
// Use pool heavily
const objects = [];
for (let i = 0; i < 100; i++) {
objects.push(candidatesPool.get());
}
for (const obj of objects) {
candidatesPool.return(obj);
}
for (let i = 0; i < 100; i++) {
objects.push(candidatesPool.get()); // Should hit pool
}
const poolStats = candidatesPool.getStatistics();
efficiencyMetrics.objectPoolHitRate = poolStats.hitRate;
}
// Measure buffer reuse efficiency
const buffers: Buffer[] = [];
for (let i = 0; i < 50; i++) {
buffers.push(gcOptimizer.getBuffer(4096));
}
for (const buffer of buffers) {
gcOptimizer.returnBuffer(buffer);
}
// Get new buffers (should reuse)
const reusedBuffers = [];
for (let i = 0; i < 50; i++) {
reusedBuffers.push(gcOptimizer.getBuffer(4096));
}
efficiencyMetrics.bufferReuseRate = 0.8; // Estimate - would need instrumentation
// Measure leak detection accuracy
let leaksDetected = 0;
memoryLeakDetector.on('memoryLeakDetected', () => leaksDetected++);
// Create known leak
for (let i = 0; i < 150; i++) {
memoryLeakDetector.trackObjectAllocation('efficiency-test', { id: i }, 1000);
}
await memoryLeakDetector.detectMemoryLeaks();
efficiencyMetrics.leakDetectionAccuracy = leaksDetected > 0 ? 1.0 : 0.0;
// Measure cleanup effectiveness
const beforeCleanup = process.memoryUsage().heapUsed;
await memoryLeakDetector.forceCleanup();
const afterCleanup = process.memoryUsage().heapUsed;
efficiencyMetrics.cleanupEffectiveness = Math.max(0, (beforeCleanup - afterCleanup) / beforeCleanup);
// Validate efficiency metrics
expect(efficiencyMetrics.heapUtilization).toBeGreaterThan(0.1); // Some heap usage
expect(efficiencyMetrics.heapUtilization).toBeLessThan(0.95); // Not near limit
expect(efficiencyMetrics.gcEfficiency).toBeGreaterThan(PERFORMANCE_THRESHOLDS.minGCEfficiency);
if (efficiencyMetrics.objectPoolHitRate > 0) {
expect(efficiencyMetrics.objectPoolHitRate).toBeGreaterThan(PERFORMANCE_THRESHOLDS.minPoolHitRate);
}
expect(efficiencyMetrics.leakDetectionAccuracy).toBeGreaterThan(0.5); // Should detect obvious leaks
expect(efficiencyMetrics.cleanupEffectiveness).toBeGreaterThan(0.0); // Some cleanup should occur
});
});
describe('Performance Regression Detection', () => {
test('should detect performance regressions in memory operations', async () => {
const regressionMetrics = {
baselinePerformance: new Map<string, number>(),
currentPerformance: new Map<string, number>(),
regressions: [] as Array<{ operation: string; regression: number }>,
improvements: [] as Array<{ operation: string; improvement: number }>,
};
const operations = [
'cache_set',
'cache_get',
'frontier_add',
'gc_force',
'leak_detect',
];
// Establish baseline performance
for (const operation of operations) {
const samples: number[] = [];
for (let i = 0; i < 20; i++) {
const startTime = performance.now();
switch (operation) {
case 'cache_set':
await cacheManager.set(`baseline-${i}`, { data: 'test' });
break;
case 'cache_get':
await cacheManager.get(`baseline-${Math.floor(Math.random() * 20)}`);
break;
case 'frontier_add':
await paretoFrontier.addCandidate({
id: `baseline-${i}`,
generation: 1,
content: 'baseline test',
averageScore: Math.random(),
rolloutCount: 1,
timestamp: new Date(),
});
break;
case 'gc_force':
await gcOptimizer.forceGarbageCollection('baseline');
break;
case 'leak_detect':
await memoryLeakDetector.detectMemoryLeaks();
break;
}
const endTime = performance.now();
samples.push(endTime - startTime);
}
const avgPerformance = samples.reduce((sum, s) => sum + s, 0) / samples.length;
regressionMetrics.baselinePerformance.set(operation, avgPerformance);
}
// Add memory pressure and measure current performance
await memoryLeakDetector.simulateMemoryPressure({
enabled: true,
targetMemoryMB: 30,
duration: 1000,
escalationSteps: 2,
});
for (const operation of operations) {
const samples: number[] = [];
for (let i = 0; i < 20; i++) {
const startTime = performance.now();
switch (operation) {
case 'cache_set':
await cacheManager.set(`current-${i}`, { data: 'test' });
break;
case 'cache_get':
await cacheManager.get(`current-${Math.floor(Math.random() * 20)}`);
break;
case 'frontier_add':
await paretoFrontier.addCandidate({
id: `current-${i}`,
generation: 1,
content: 'current test',
averageScore: Math.random(),
rolloutCount: 1,
timestamp: new Date(),
});
break;
case 'gc_force':
await gcOptimizer.forceGarbageCollection('current');
break;
case 'leak_detect':
await memoryLeakDetector.detectMemoryLeaks();
break;
}
const endTime = performance.now();
samples.push(endTime - startTime);
}
const avgPerformance = samples.reduce((sum, s) => sum + s, 0) / samples.length;
regressionMetrics.currentPerformance.set(operation, avgPerformance);
}
// Analyze for regressions and improvements
for (const operation of operations) {
const baseline = regressionMetrics.baselinePerformance.get(operation);
const current = regressionMetrics.currentPerformance.get(operation);
if (!baseline || !current) {
continue; // Skip if metrics are missing
}
const ratio = current / baseline;
if (ratio > 1.5) { // 50% slower
regressionMetrics.regressions.push({
operation,
regression: ratio - 1,
});
} else if (ratio < 0.8) { // 20% faster
regressionMetrics.improvements.push({
operation,
improvement: 1 - ratio,
});
}
}
// Validate regression detection
expect(regressionMetrics.baselinePerformance.size).toBe(operations.length);
expect(regressionMetrics.currentPerformance.size).toBe(operations.length);
// Under memory pressure, some operations may be slower, but not excessively
regressionMetrics.regressions.forEach(regression => {
expect(regression.regression).toBeLessThan(4.0); // Not more than 5x slower
});
// Overall system should maintain reasonable performance
const totalOperations = operations.length;
const severeRegressions = regressionMetrics.regressions.filter(r => r.regression > 2.0).length;
expect(severeRegressions / totalOperations).toBeLessThan(0.5); // Less than 50% severe regressions
});
});
});