- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
295 lines
9.0 KiB
PHP
295 lines
9.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Queue\Performance;
|
|
|
|
use App\Framework\Queue\Jobs\Job;
|
|
use App\Framework\Queue\Jobs\JobRequest;
|
|
use App\Framework\Queue\Jobs\JobResult;
|
|
use App\Framework\Queue\Jobs\JobStatus;
|
|
use App\Framework\Queue\Workers\Worker;
|
|
use App\Framework\Queue\Workers\WorkerCapacity;
|
|
use App\Framework\Queue\Workers\WorkerId;
|
|
use App\Framework\Queue\Workers\WorkerStatus;
|
|
use App\Framework\Queue\Queue\QueueName;
|
|
use App\Framework\Queue\Queue\JobPriority;
|
|
|
|
final readonly class PerformanceTestHelper
|
|
{
|
|
public static function createTestWorker(
|
|
string $id = null,
|
|
int $capacity = 10,
|
|
WorkerStatus $status = WorkerStatus::AVAILABLE
|
|
): Worker {
|
|
return new Worker(
|
|
id: new WorkerId($id ?? uniqid('worker_')),
|
|
queueNames: [new QueueName('test_queue')],
|
|
capacity: new WorkerCapacity($capacity),
|
|
status: $status,
|
|
lastHeartbeat: new \DateTimeImmutable(),
|
|
metadata: []
|
|
);
|
|
}
|
|
|
|
public static function createTestJob(
|
|
string $id = null,
|
|
JobPriority $priority = JobPriority::NORMAL,
|
|
array $payload = []
|
|
): Job {
|
|
return new Job(
|
|
id: $id ?? uniqid('job_'),
|
|
request: new JobRequest(
|
|
type: 'test_job',
|
|
payload: $payload ?: ['test' => 'data'],
|
|
queue: new QueueName('test_queue'),
|
|
priority: $priority
|
|
),
|
|
status: JobStatus::PENDING,
|
|
createdAt: new \DateTimeImmutable(),
|
|
attempts: 0
|
|
);
|
|
}
|
|
|
|
public static function createBulkJobs(int $count, JobPriority $priority = JobPriority::NORMAL): array
|
|
{
|
|
$jobs = [];
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$jobs[] = self::createTestJob(
|
|
id: "job_{$i}",
|
|
priority: $priority,
|
|
payload: ['batch_id' => $i, 'data' => str_repeat('x', 100)]
|
|
);
|
|
}
|
|
return $jobs;
|
|
}
|
|
|
|
public static function measureTime(callable $operation): float
|
|
{
|
|
$start = microtime(true);
|
|
$operation();
|
|
$end = microtime(true);
|
|
|
|
return ($end - $start) * 1000; // Return milliseconds
|
|
}
|
|
|
|
public static function measureTimeWithResult(callable $operation): array
|
|
{
|
|
$start = microtime(true);
|
|
$result = $operation();
|
|
$end = microtime(true);
|
|
|
|
return [
|
|
'result' => $result,
|
|
'time_ms' => ($end - $start) * 1000
|
|
];
|
|
}
|
|
|
|
public static function calculateStatistics(array $measurements): array
|
|
{
|
|
if (empty($measurements)) {
|
|
return [
|
|
'count' => 0,
|
|
'min' => 0,
|
|
'max' => 0,
|
|
'avg' => 0,
|
|
'median' => 0,
|
|
'p95' => 0,
|
|
'p99' => 0,
|
|
'stddev' => 0
|
|
];
|
|
}
|
|
|
|
sort($measurements);
|
|
$count = count($measurements);
|
|
|
|
$min = $measurements[0];
|
|
$max = $measurements[$count - 1];
|
|
$avg = array_sum($measurements) / $count;
|
|
|
|
$median = $count % 2 === 0
|
|
? ($measurements[$count / 2 - 1] + $measurements[$count / 2]) / 2
|
|
: $measurements[intval($count / 2)];
|
|
|
|
$p95Index = intval($count * 0.95) - 1;
|
|
$p99Index = intval($count * 0.99) - 1;
|
|
|
|
$p95 = $measurements[max(0, $p95Index)];
|
|
$p99 = $measurements[max(0, $p99Index)];
|
|
|
|
// Calculate standard deviation
|
|
$sumSquaredDiff = 0;
|
|
foreach ($measurements as $value) {
|
|
$sumSquaredDiff += pow($value - $avg, 2);
|
|
}
|
|
$stddev = sqrt($sumSquaredDiff / $count);
|
|
|
|
return [
|
|
'count' => $count,
|
|
'min' => round($min, 3),
|
|
'max' => round($max, 3),
|
|
'avg' => round($avg, 3),
|
|
'median' => round($median, 3),
|
|
'p95' => round($p95, 3),
|
|
'p99' => round($p99, 3),
|
|
'stddev' => round($stddev, 3)
|
|
];
|
|
}
|
|
|
|
public static function formatStatistics(array $stats, string $unit = 'ms'): string
|
|
{
|
|
return sprintf(
|
|
"Count: %d, Min: %.3f%s, Max: %.3f%s, Avg: %.3f%s, P95: %.3f%s, P99: %.3f%s, StdDev: %.3f%s",
|
|
$stats['count'],
|
|
$stats['min'], $unit,
|
|
$stats['max'], $unit,
|
|
$stats['avg'], $unit,
|
|
$stats['p95'], $unit,
|
|
$stats['p99'], $unit,
|
|
$stats['stddev'], $unit
|
|
);
|
|
}
|
|
|
|
public static function assertPerformance(
|
|
array $measurements,
|
|
float $expectedAvg,
|
|
float $expectedP95,
|
|
string $operation
|
|
): void {
|
|
$stats = self::calculateStatistics($measurements);
|
|
|
|
if ($stats['avg'] > $expectedAvg) {
|
|
throw new \AssertionError(
|
|
sprintf(
|
|
"%s average performance exceeded: expected ≤%.3fms, got %.3fms",
|
|
$operation,
|
|
$expectedAvg,
|
|
$stats['avg']
|
|
)
|
|
);
|
|
}
|
|
|
|
if ($stats['p95'] > $expectedP95) {
|
|
throw new \AssertionError(
|
|
sprintf(
|
|
"%s P95 performance exceeded: expected ≤%.3fms, got %.3fms",
|
|
$operation,
|
|
$expectedP95,
|
|
$stats['p95']
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public static function getMemoryUsage(): array
|
|
{
|
|
return [
|
|
'current_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
|
|
'peak_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
|
|
'current_real_mb' => round(memory_get_usage(false) / 1024 / 1024, 2),
|
|
'peak_real_mb' => round(memory_get_peak_usage(false) / 1024 / 1024, 2)
|
|
];
|
|
}
|
|
|
|
public static function warmupDatabase(\PDO $pdo): void
|
|
{
|
|
// Execute simple queries to warm up connections
|
|
$pdo->query('SELECT 1');
|
|
$pdo->query('SELECT COUNT(*) FROM workers');
|
|
$pdo->query('SELECT COUNT(*) FROM jobs');
|
|
}
|
|
|
|
public static function createConcurrentOperation(callable $operation, int $concurrency): \Generator
|
|
{
|
|
$operations = [];
|
|
|
|
for ($i = 0; $i < $concurrency; $i++) {
|
|
$operations[] = function() use ($operation, $i) {
|
|
return $operation($i);
|
|
};
|
|
}
|
|
|
|
foreach ($operations as $op) {
|
|
yield $op;
|
|
}
|
|
}
|
|
|
|
public static function simulateLoad(
|
|
callable $operation,
|
|
int $totalOperations,
|
|
int $concurrency,
|
|
float $durationSeconds = null
|
|
): array {
|
|
$results = [];
|
|
$startTime = microtime(true);
|
|
$endTime = $durationSeconds ? $startTime + $durationSeconds : PHP_FLOAT_MAX;
|
|
|
|
$operationsCompleted = 0;
|
|
$batch = 0;
|
|
|
|
while ($operationsCompleted < $totalOperations && microtime(true) < $endTime) {
|
|
$batchSize = min($concurrency, $totalOperations - $operationsCompleted);
|
|
$batchResults = [];
|
|
|
|
// Execute concurrent operations
|
|
for ($i = 0; $i < $batchSize; $i++) {
|
|
$result = self::measureTimeWithResult(function() use ($operation, $batch, $i) {
|
|
return $operation($batch * $concurrency + $i);
|
|
});
|
|
$batchResults[] = $result;
|
|
}
|
|
|
|
$results = array_merge($results, $batchResults);
|
|
$operationsCompleted += $batchSize;
|
|
$batch++;
|
|
|
|
// Small delay to prevent overwhelming the system
|
|
if (microtime(true) < $endTime) {
|
|
usleep(1000); // 1ms
|
|
}
|
|
}
|
|
|
|
return [
|
|
'results' => $results,
|
|
'operations_completed' => $operationsCompleted,
|
|
'duration_seconds' => microtime(true) - $startTime,
|
|
'throughput_ops_per_sec' => $operationsCompleted / (microtime(true) - $startTime)
|
|
];
|
|
}
|
|
|
|
public static function generatePerformanceReport(array $testResults): string
|
|
{
|
|
$report = "\n" . str_repeat("=", 80) . "\n";
|
|
$report .= "PERFORMANCE TEST REPORT\n";
|
|
$report .= str_repeat("=", 80) . "\n\n";
|
|
|
|
foreach ($testResults as $testName => $results) {
|
|
$report .= "Test: {$testName}\n";
|
|
$report .= str_repeat("-", 40) . "\n";
|
|
|
|
if (isset($results['statistics'])) {
|
|
$report .= "Statistics: " . self::formatStatistics($results['statistics']) . "\n";
|
|
}
|
|
|
|
if (isset($results['throughput'])) {
|
|
$report .= "Throughput: {$results['throughput']} ops/sec\n";
|
|
}
|
|
|
|
if (isset($results['memory'])) {
|
|
$report .= sprintf(
|
|
"Memory: Current: %.2fMB, Peak: %.2fMB\n",
|
|
$results['memory']['current_mb'],
|
|
$results['memory']['peak_mb']
|
|
);
|
|
}
|
|
|
|
if (isset($results['notes'])) {
|
|
$report .= "Notes: {$results['notes']}\n";
|
|
}
|
|
|
|
$report .= "\n";
|
|
}
|
|
|
|
return $report;
|
|
}
|
|
} |