Files
michaelschiemer/tests/Unit/Framework/View/Caching/CachingPerformanceTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

413 lines
17 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Cache\CacheResult;
use App\Framework\View\Caching\Analysis\CacheStrategy;
use App\Framework\View\Caching\Analysis\SmartTemplateAnalyzer;
use App\Framework\View\Caching\CacheManager;
use App\Framework\View\Caching\FragmentCache;
use App\Framework\View\Caching\TemplateContext;
use App\Framework\View\Loading\TemplateLoader;
use App\Framework\Core\PathProvider;
describe('Caching Performance Benchmarks', function () {
beforeEach(function () {
// Create test templates directory
$this->testDir = '/home/michael/dev/michaelschiemer/tests/tmp/caching_perf_' . uniqid();
if (!is_dir($this->testDir)) {
mkdir($this->testDir, 0755, true);
}
// Create PathProvider
$this->pathProvider = new PathProvider($this->testDir);
// Create Cache mock
$this->cache = Mockery::mock(Cache::class);
// Create TemplateLoader
$this->loader = new TemplateLoader(
pathProvider: $this->pathProvider,
cache: $this->cache,
templates: [],
templatePath: '',
cacheEnabled: true
);
// Create Analyzer
$this->analyzer = new SmartTemplateAnalyzer($this->loader);
// Create FragmentCache mock
$this->fragmentCache = Mockery::mock(FragmentCache::class);
// Create CacheManager
$this->cacheManager = new CacheManager(
$this->cache,
$this->analyzer,
$this->fragmentCache
);
// Store results for reporting
$this->perfResults = [];
});
afterEach(function () {
// Cleanup test directory
if (is_dir($this->testDir)) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->testDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $file) {
$file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath());
}
rmdir($this->testDir);
}
Mockery::close();
// Print performance summary
if (!empty($this->perfResults)) {
echo "\n\n=== Performance Summary ===\n";
foreach ($this->perfResults as $test => $results) {
echo "\n{$test}:\n";
// Check if this is timing stats or other metrics
if (isset($results['min'])) {
echo " Min: " . number_format($results['min'], 2) . " ms\n";
echo " Max: " . number_format($results['max'], 2) . " ms\n";
echo " Avg: " . number_format($results['avg'], 2) . " ms\n";
echo " Median: " . number_format($results['median'], 2) . " ms\n";
echo " P95: " . number_format($results['p95'], 2) . " ms\n";
echo " P99: " . number_format($results['p99'], 2) . " ms\n";
}
if (isset($results['memory'])) {
echo " Memory: " . number_format($results['memory'], 2) . " MB\n";
}
if (isset($results['throughput_per_sec'])) {
echo " Throughput: " . number_format($results['throughput_per_sec'], 0) . " renders/sec\n";
}
}
echo "\n";
}
});
describe('Cache Hit vs Miss Performance', function () {
it('benchmarks cache hit performance (FULL_PAGE strategy)', function () {
// Create large static template
$content = '<html><head><title>Static Page</title></head><body>' . str_repeat('<p>Static content paragraph.</p>', 100) . '</body></html>';
$filePath = $this->testDir . '/pages/static.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) mkdir($dir, 0755, true);
file_put_contents($filePath, $content);
$context = new TemplateContext(
template: 'pages/static',
data: ['title' => 'Test']
);
$renderedContent = $content;
$measurements = [];
// Warm up
$this->cache->shouldReceive('get')->andReturn(CacheResult::fromItems(CacheItem::miss(CacheKey::fromString('test'))));
$this->cache->shouldReceive('set')->andReturn(true);
$this->cacheManager->render($context, fn() => $renderedContent);
// Measure cache miss (10 iterations)
$missMeasurements = [];
for ($i = 0; $i < 10; $i++) {
$this->cache->shouldReceive('get')->andReturn(CacheResult::fromItems(CacheItem::miss(CacheKey::fromString("test_{$i}"))));
$this->cache->shouldReceive('set')->andReturn(true);
$start = microtime(true);
$this->cacheManager->render($context, fn() => $renderedContent);
$missMeasurements[] = (microtime(true) - $start) * 1000;
}
// Measure cache hit (100 iterations)
$hitMeasurements = [];
$cacheKey = CacheKey::fromString('page:pages/static:' . md5(serialize(['title' => 'Test'])));
$cacheItem = CacheItem::hit($cacheKey, $renderedContent);
$this->cache->shouldReceive('get')->andReturn(CacheResult::fromItems($cacheItem));
for ($i = 0; $i < 100; $i++) {
$start = microtime(true);
$this->cacheManager->render($context, fn() => $renderedContent);
$hitMeasurements[] = (microtime(true) - $start) * 1000;
}
// Calculate statistics
$missStats = calculateStats($missMeasurements);
$hitStats = calculateStats($hitMeasurements);
$this->perfResults['FULL_PAGE Cache Miss'] = $missStats;
$this->perfResults['FULL_PAGE Cache Hit'] = $hitStats;
// Assert performance characteristics (info-gathering, not strict benchmarks with mocks)
// Note: Mocked cache doesn't show real performance gains, but tests the workflow
expect($hitStats['avg'])->toBeLessThan(10.0); // Should complete in reasonable time
expect($missStats['avg'])->toBeLessThan(10.0); // Should complete in reasonable time
});
it('benchmarks cache hit performance (COMPONENT strategy)', function () {
// Create component template
$content = '<button class="btn btn-primary">' . str_repeat('Click me ', 10) . '</button>';
$filePath = $this->testDir . '/components/button.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) mkdir($dir, 0755, true);
file_put_contents($filePath, $content);
$context = new TemplateContext(
template: 'components/button',
data: ['text' => 'Submit']
);
$renderedContent = $content;
// Measure cache miss (10 iterations)
$missMeasurements = [];
for ($i = 0; $i < 10; $i++) {
$this->cache->shouldReceive('get')->andReturn(CacheResult::fromItems(CacheItem::miss(CacheKey::fromString("test_{$i}"))));
$this->cache->shouldReceive('set')->andReturn(true);
$start = microtime(true);
$this->cacheManager->render($context, fn() => $renderedContent);
$missMeasurements[] = (microtime(true) - $start) * 1000;
}
// Measure cache hit (100 iterations)
$hitMeasurements = [];
$cacheKey = CacheKey::fromString('component:button:' . md5(serialize(['text' => 'Submit'])));
$cacheItem = CacheItem::hit($cacheKey, $renderedContent);
$this->cache->shouldReceive('get')->andReturn(CacheResult::fromItems($cacheItem));
for ($i = 0; $i < 100; $i++) {
$start = microtime(true);
$this->cacheManager->render($context, fn() => $renderedContent);
$hitMeasurements[] = (microtime(true) - $start) * 1000;
}
$missStats = calculateStats($missMeasurements);
$hitStats = calculateStats($hitMeasurements);
$this->perfResults['COMPONENT Cache Miss'] = $missStats;
$this->perfResults['COMPONENT Cache Hit'] = $hitStats;
// Info-gathering: verify workflow completes in reasonable time
expect($hitStats['avg'])->toBeLessThan(10.0);
expect($missStats['avg'])->toBeLessThan(10.0);
});
});
describe('SmartTemplateAnalyzer Performance', function () {
it('benchmarks template analysis performance', function () {
// Create various templates
$templates = [
'pages/simple.view.php' => '<html><body>Simple static page</body></html>',
'pages/dynamic.view.php' => '<html><body>Hello {{ user.name }}, time: {{ now }}</body></html>',
'components/card.view.php' => '<div class="card"><h3>{{ title }}</h3><p>{{ content }}</p></div>',
'partials/nav.view.php' => '<nav><ul>' . str_repeat('<li>{{ item }}</li>', 5) . '</ul></nav>',
];
foreach ($templates as $path => $content) {
$filePath = $this->testDir . '/' . $path;
$dir = dirname($filePath);
if (!is_dir($dir)) mkdir($dir, 0755, true);
file_put_contents($filePath, $content);
}
// Measure analysis performance
$measurements = [];
foreach ($templates as $path => $content) {
$templateName = str_replace('.view.php', '', $path);
$start = microtime(true);
$analysis = $this->analyzer->analyze($templateName);
$measurements[] = (microtime(true) - $start) * 1000;
}
// Repeat for statistical significance (25 iterations per template)
for ($i = 0; $i < 24; $i++) {
foreach ($templates as $path => $content) {
$templateName = str_replace('.view.php', '', $path);
$start = microtime(true);
$this->analyzer->analyze($templateName);
$measurements[] = (microtime(true) - $start) * 1000;
}
}
$stats = calculateStats($measurements);
$this->perfResults['Template Analysis'] = $stats;
// Analysis should be fast (< 5ms average)
expect($stats['avg'])->toBeLessThan(5.0);
expect($stats['p99'])->toBeLessThan(10.0);
});
});
describe('Memory Usage Benchmarks', function () {
it('benchmarks memory usage across caching strategies', function () {
$memoryBefore = memory_get_usage(true);
// Create large templates
$largeContent = str_repeat('<div class="item">Static content item</div>', 1000);
// FULL_PAGE template
$filePath = $this->testDir . '/pages/large.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) mkdir($dir, 0755, true);
file_put_contents($filePath, $largeContent);
$context = new TemplateContext(template: 'pages/large', data: []);
// Mock cache responses
$this->cache->shouldReceive('get')->andReturn(CacheResult::fromItems(CacheItem::miss(CacheKey::fromString('test'))));
$this->cache->shouldReceive('set')->andReturn(true);
// Render 100 times
for ($i = 0; $i < 100; $i++) {
$this->cacheManager->render($context, fn() => $largeContent);
}
$memoryAfter = memory_get_usage(true);
$memoryUsed = ($memoryAfter - $memoryBefore) / 1024 / 1024; // MB
$this->perfResults['Memory Usage (100 renders)']['memory'] = $memoryUsed;
// Memory usage should be reasonable (< 50MB for 100 renders with test overhead)
expect($memoryUsed)->toBeLessThan(50.0);
});
});
describe('Throughput Benchmarks', function () {
it('benchmarks rendering throughput with caching', function () {
// Create template
$content = '<html><body>' . str_repeat('<p>Paragraph content</p>', 50) . '</body></html>';
$filePath = $this->testDir . '/pages/throughput.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) mkdir($dir, 0755, true);
file_put_contents($filePath, $content);
$context = new TemplateContext(template: 'pages/throughput', data: []);
// Simulate cache hits
$cacheKey = CacheKey::fromString('page:pages/throughput:' . md5(serialize([])));
$cacheItem = CacheItem::hit($cacheKey, $content);
$this->cache->shouldReceive('get')->andReturn(CacheResult::fromItems($cacheItem));
// Measure throughput
$iterations = 1000;
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$this->cacheManager->render($context, fn() => $content);
}
$duration = microtime(true) - $start;
$throughput = $iterations / $duration;
$this->perfResults['Throughput (cached)']['throughput_per_sec'] = $throughput;
echo "\nThroughput: " . number_format($throughput, 0) . " renders/sec\n";
// Info-gathering: verify reasonable throughput (> 500 renders/sec)
expect($throughput)->toBeGreaterThan(500);
});
it('benchmarks rendering throughput without caching', function () {
// Create template
$content = '<html><body>' . str_repeat('<p>Paragraph content</p>', 50) . '</body></html>';
$filePath = $this->testDir . '/pages/throughput2.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) mkdir($dir, 0755, true);
file_put_contents($filePath, $content);
$context = new TemplateContext(template: 'pages/throughput2', data: []);
// Simulate cache misses
$this->cache->shouldReceive('get')->andReturn(CacheResult::fromItems(CacheItem::miss(CacheKey::fromString('test'))));
$this->cache->shouldReceive('set')->andReturn(true);
// Measure throughput
$iterations = 100; // Fewer iterations for uncached
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$this->cacheManager->render($context, fn() => $content);
}
$duration = microtime(true) - $start;
$throughput = $iterations / $duration;
$this->perfResults['Throughput (uncached)']['throughput_per_sec'] = $throughput;
echo "\nThroughput (uncached): " . number_format($throughput, 0) . " renders/sec\n";
// Info-gathering: verify reasonable throughput (> 100 renders/sec)
expect($throughput)->toBeGreaterThan(100);
});
});
describe('Concurrent Load Simulation', function () {
it('benchmarks performance under concurrent load', function () {
// Create template
$content = '<html><body>Concurrent test content</body></html>';
$filePath = $this->testDir . '/pages/concurrent.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) mkdir($dir, 0755, true);
file_put_contents($filePath, $content);
$context = new TemplateContext(template: 'pages/concurrent', data: []);
// Simulate cache hits
$cacheKey = CacheKey::fromString('page:pages/concurrent:' . md5(serialize([])));
$cacheItem = CacheItem::hit($cacheKey, $content);
$this->cache->shouldReceive('get')->andReturn(CacheResult::fromItems($cacheItem));
// Simulate 10 concurrent requests with 100 renders each
$measurements = [];
for ($request = 0; $request < 10; $request++) {
$requestMeasurements = [];
for ($i = 0; $i < 100; $i++) {
$start = microtime(true);
$this->cacheManager->render($context, fn() => $content);
$requestMeasurements[] = (microtime(true) - $start) * 1000;
}
$measurements = array_merge($measurements, $requestMeasurements);
}
$stats = calculateStats($measurements);
$this->perfResults['Concurrent Load (10 requests)'] = $stats;
// Performance should remain stable under load
expect($stats['avg'])->toBeLessThan(5.0); // Average < 5ms (with test overhead)
expect($stats['p99'])->toBeLessThan(100.0); // 99% < 100ms
});
});
});
// Helper function for statistics calculation
function calculateStats(array $measurements): array
{
sort($measurements);
$count = count($measurements);
return [
'count' => $count,
'min' => min($measurements),
'max' => max($measurements),
'avg' => array_sum($measurements) / $count,
'median' => $measurements[(int)($count / 2)],
'p95' => $measurements[(int)($count * 0.95)],
'p99' => $measurements[(int)($count * 0.99)],
'stddev' => sqrt(array_sum(array_map(fn($x) => pow($x - (array_sum($measurements) / $count), 2), $measurements)) / $count)
];
}