- 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
218 lines
7.3 KiB
PHP
218 lines
7.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Queue\ValueObjects;
|
|
|
|
use App\Framework\Core\ValueObjects\Percentage;
|
|
|
|
final readonly class QueueMetrics
|
|
{
|
|
public function __construct(
|
|
public string $queueName,
|
|
public int $totalJobs,
|
|
public int $pendingJobs,
|
|
public int $runningJobs,
|
|
public int $completedJobs,
|
|
public int $failedJobs,
|
|
public int $deadLetterJobs,
|
|
public float $averageExecutionTimeMs,
|
|
public float $averageMemoryUsageMB,
|
|
public float $throughputJobsPerHour,
|
|
public Percentage $successRate,
|
|
public string $measuredAt,
|
|
public array $additionalMetrics = []
|
|
) {}
|
|
|
|
public static function calculate(
|
|
string $queueName,
|
|
array $jobMetrics,
|
|
string $timeWindow = '1 hour'
|
|
): self {
|
|
$total = count($jobMetrics);
|
|
$pending = 0;
|
|
$running = 0;
|
|
$completed = 0;
|
|
$failed = 0;
|
|
$deadLetter = 0;
|
|
|
|
$totalExecutionTime = 0.0;
|
|
$totalMemoryUsage = 0.0;
|
|
$executionCount = 0;
|
|
|
|
$windowStart = strtotime("-{$timeWindow}");
|
|
$jobsInWindow = 0;
|
|
|
|
foreach ($jobMetrics as $metrics) {
|
|
match($metrics->status) {
|
|
'pending' => $pending++,
|
|
'running' => $running++,
|
|
'completed' => $completed++,
|
|
'failed' => $failed++,
|
|
'dead_letter' => $deadLetter++,
|
|
default => null
|
|
};
|
|
|
|
if ($metrics->executionTimeMs > 0) {
|
|
$totalExecutionTime += $metrics->executionTimeMs;
|
|
$totalMemoryUsage += $metrics->getMemoryUsageMB();
|
|
$executionCount++;
|
|
}
|
|
|
|
if (strtotime($metrics->createdAt) >= $windowStart) {
|
|
$jobsInWindow++;
|
|
}
|
|
}
|
|
|
|
$avgExecutionTime = $executionCount > 0 ? $totalExecutionTime / $executionCount : 0.0;
|
|
$avgMemoryUsage = $executionCount > 0 ? $totalMemoryUsage / $executionCount : 0.0;
|
|
$throughput = $jobsInWindow / (strtotime($timeWindow) / 3600);
|
|
|
|
$successRate = $total > 0 ?
|
|
Percentage::from(($completed / $total) * 100) :
|
|
Percentage::from(100.0);
|
|
|
|
return new self(
|
|
queueName: $queueName,
|
|
totalJobs: $total,
|
|
pendingJobs: $pending,
|
|
runningJobs: $running,
|
|
completedJobs: $completed,
|
|
failedJobs: $failed,
|
|
deadLetterJobs: $deadLetter,
|
|
averageExecutionTimeMs: $avgExecutionTime,
|
|
averageMemoryUsageMB: $avgMemoryUsage,
|
|
throughputJobsPerHour: $throughput,
|
|
successRate: $successRate,
|
|
measuredAt: date('Y-m-d H:i:s'),
|
|
additionalMetrics: []
|
|
);
|
|
}
|
|
|
|
public function withAdditionalMetrics(array $metrics): self
|
|
{
|
|
return new self(
|
|
queueName: $this->queueName,
|
|
totalJobs: $this->totalJobs,
|
|
pendingJobs: $this->pendingJobs,
|
|
runningJobs: $this->runningJobs,
|
|
completedJobs: $this->completedJobs,
|
|
failedJobs: $this->failedJobs,
|
|
deadLetterJobs: $this->deadLetterJobs,
|
|
averageExecutionTimeMs: $this->averageExecutionTimeMs,
|
|
averageMemoryUsageMB: $this->averageMemoryUsageMB,
|
|
throughputJobsPerHour: $this->throughputJobsPerHour,
|
|
successRate: $this->successRate,
|
|
measuredAt: $this->measuredAt,
|
|
additionalMetrics: array_merge($this->additionalMetrics, $metrics)
|
|
);
|
|
}
|
|
|
|
public function getFailureRate(): Percentage
|
|
{
|
|
if ($this->totalJobs === 0) {
|
|
return Percentage::from(0.0);
|
|
}
|
|
|
|
return Percentage::from(($this->failedJobs / $this->totalJobs) * 100);
|
|
}
|
|
|
|
public function getAverageExecutionTimeSeconds(): float
|
|
{
|
|
return $this->averageExecutionTimeMs / 1000.0;
|
|
}
|
|
|
|
public function getHealthScore(): Percentage
|
|
{
|
|
// Composite health score based on multiple factors
|
|
$successWeight = 40; // Success rate weight
|
|
$throughputWeight = 30; // Throughput weight
|
|
$performanceWeight = 20; // Performance weight
|
|
$stabilityWeight = 10; // Stability weight
|
|
|
|
// Success rate score (0-100)
|
|
$successScore = $this->successRate->getValue();
|
|
|
|
// Throughput score (normalized, assuming 100 jobs/hour is excellent)
|
|
$throughputScore = min(100, ($this->throughputJobsPerHour / 100) * 100);
|
|
|
|
// Performance score (inverse of execution time, assuming 1000ms is baseline)
|
|
$performanceScore = $this->averageExecutionTimeMs > 0 ?
|
|
max(0, 100 - (($this->averageExecutionTimeMs / 1000) * 10)) : 100;
|
|
|
|
// Stability score (low pending/running job ratio)
|
|
$activeJobs = $this->pendingJobs + $this->runningJobs;
|
|
$stabilityScore = $this->totalJobs > 0 ?
|
|
max(0, 100 - (($activeJobs / $this->totalJobs) * 100)) : 100;
|
|
|
|
$weightedScore = (
|
|
($successScore * $successWeight) +
|
|
($throughputScore * $throughputWeight) +
|
|
($performanceScore * $performanceWeight) +
|
|
($stabilityScore * $stabilityWeight)
|
|
) / 100;
|
|
|
|
return Percentage::from($weightedScore);
|
|
}
|
|
|
|
public function isHealthy(): bool
|
|
{
|
|
return $this->getHealthScore()->getValue() >= 70.0;
|
|
}
|
|
|
|
public function getBottleneckIndicators(): array
|
|
{
|
|
$indicators = [];
|
|
|
|
// High pending jobs
|
|
if ($this->pendingJobs > ($this->totalJobs * 0.3)) {
|
|
$indicators[] = 'high_pending_jobs';
|
|
}
|
|
|
|
// High failure rate
|
|
if ($this->getFailureRate()->getValue() > 10.0) {
|
|
$indicators[] = 'high_failure_rate';
|
|
}
|
|
|
|
// Slow execution
|
|
if ($this->averageExecutionTimeMs > 5000) {
|
|
$indicators[] = 'slow_execution';
|
|
}
|
|
|
|
// High memory usage
|
|
if ($this->averageMemoryUsageMB > 100) {
|
|
$indicators[] = 'high_memory_usage';
|
|
}
|
|
|
|
// Low throughput
|
|
if ($this->throughputJobsPerHour < 10) {
|
|
$indicators[] = 'low_throughput';
|
|
}
|
|
|
|
return $indicators;
|
|
}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'queue_name' => $this->queueName,
|
|
'total_jobs' => $this->totalJobs,
|
|
'pending_jobs' => $this->pendingJobs,
|
|
'running_jobs' => $this->runningJobs,
|
|
'completed_jobs' => $this->completedJobs,
|
|
'failed_jobs' => $this->failedJobs,
|
|
'dead_letter_jobs' => $this->deadLetterJobs,
|
|
'average_execution_time_ms' => $this->averageExecutionTimeMs,
|
|
'average_execution_time_seconds' => $this->getAverageExecutionTimeSeconds(),
|
|
'average_memory_usage_mb' => $this->averageMemoryUsageMB,
|
|
'throughput_jobs_per_hour' => $this->throughputJobsPerHour,
|
|
'success_rate' => $this->successRate->getValue(),
|
|
'failure_rate' => $this->getFailureRate()->getValue(),
|
|
'health_score' => $this->getHealthScore()->getValue(),
|
|
'is_healthy' => $this->isHealthy(),
|
|
'bottleneck_indicators' => $this->getBottleneckIndicators(),
|
|
'measured_at' => $this->measuredAt,
|
|
'additional_metrics' => $this->additionalMetrics
|
|
];
|
|
}
|
|
} |