feat(Deployment): Integrate Ansible deployment via PHP deployment pipeline
- Create AnsibleDeployStage using framework's Process module for secure command execution - Integrate AnsibleDeployStage into DeploymentPipelineCommands for production deployments - Add force_deploy flag support in Ansible playbook to override stale locks - Use PHP deployment module as orchestrator (php console.php deploy:production) - Fix ErrorAggregationInitializer to use Environment class instead of $_ENV superglobal Architecture: - BuildStage → AnsibleDeployStage → HealthCheckStage for production - Process module provides timeout, error handling, and output capture - Ansible playbook supports rollback via rollback-git-based.yml - Zero-downtime deployments with health checks
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Queue\MachineLearning\Events;
|
||||
|
||||
use App\Framework\Queue\MachineLearning\ValueObjects\JobAnomalyResult;
|
||||
use App\Framework\Queue\ValueObjects\JobMetadata;
|
||||
use App\Framework\Queue\ValueObjects\JobMetrics;
|
||||
|
||||
/**
|
||||
* Queue Job Anomaly Detected Event
|
||||
*
|
||||
* Dispatched when the anomaly monitor detects unusual job behavior.
|
||||
*
|
||||
* This event can be used to:
|
||||
* - Send notifications to monitoring systems
|
||||
* - Trigger automated remediation workflows
|
||||
* - Update dashboards and metrics
|
||||
* - Store anomaly results for analysis
|
||||
*/
|
||||
final readonly class QueueJobAnomalyDetectedEvent
|
||||
{
|
||||
public function __construct(
|
||||
public JobMetrics $metrics,
|
||||
public JobMetadata $metadata,
|
||||
public JobAnomalyResult $anomalyResult
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get event type identifier
|
||||
*/
|
||||
public function getEventType(): string
|
||||
{
|
||||
return 'queue.job.anomaly_detected';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get severity level
|
||||
*/
|
||||
public function getSeverity(): string
|
||||
{
|
||||
return $this->anomalyResult->getSeverity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a critical anomaly
|
||||
*/
|
||||
public function isCritical(): bool
|
||||
{
|
||||
return $this->anomalyResult->requiresImmediateAttention();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event payload for logging/notification
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'event_type' => $this->getEventType(),
|
||||
'job_id' => $this->metrics->jobId,
|
||||
'queue_name' => $this->metrics->queueName,
|
||||
'job_class' => $this->metadata->class->toString(),
|
||||
'anomaly_score' => $this->anomalyResult->anomalyScore->value(),
|
||||
'severity' => $this->getSeverity(),
|
||||
'is_critical' => $this->isCritical(),
|
||||
'primary_indicator' => $this->anomalyResult->primaryIndicator,
|
||||
'detected_patterns' => array_map(
|
||||
fn($pattern) => [
|
||||
'type' => $pattern['type'],
|
||||
'confidence' => $pattern['confidence']->value(),
|
||||
'description' => $pattern['description']
|
||||
],
|
||||
$this->anomalyResult->detectedPatterns
|
||||
),
|
||||
'recommended_action' => $this->anomalyResult->getRecommendedAction(),
|
||||
'job_metrics' => [
|
||||
'execution_time_ms' => $this->metrics->executionTimeMs,
|
||||
'memory_usage_mb' => $this->metrics->getMemoryUsageMB(),
|
||||
'attempts' => $this->metrics->attempts,
|
||||
'status' => $this->metrics->status
|
||||
],
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -55,14 +55,14 @@ final readonly class JobAnomalyDetector
|
||||
$overallScore = $this->calculateOverallScore($featureScores, $detectedPatterns);
|
||||
|
||||
// Step 4: Determine if anomalous based on threshold
|
||||
$isAnomalous = $overallScore->getValue() >= $this->anomalyThreshold->getValue();
|
||||
$isAnomalous = $overallScore->value() >= $this->anomalyThreshold->value();
|
||||
|
||||
// Step 5: Identify primary indicator (highest scoring feature)
|
||||
$primaryIndicator = $this->identifyPrimaryIndicator($featureScores);
|
||||
|
||||
// Step 6: Build result
|
||||
if (!$isAnomalous) {
|
||||
if ($overallScore->getValue() > 0) {
|
||||
if ($overallScore->value() > 0) {
|
||||
return JobAnomalyResult::lowConfidence(
|
||||
$overallScore,
|
||||
$featureScores,
|
||||
@@ -97,7 +97,7 @@ final readonly class JobAnomalyDetector
|
||||
foreach ($featureArray as $featureName => $value) {
|
||||
// Convert feature value (0.0-1.0) to anomaly score
|
||||
$anomalyScore = $this->featureValueToAnomalyScore($featureName, $value);
|
||||
$scores[$featureName] = Score::fromDecimal($anomalyScore);
|
||||
$scores[$featureName] = new Score($anomalyScore);
|
||||
}
|
||||
|
||||
return $scores;
|
||||
@@ -174,7 +174,7 @@ final readonly class JobAnomalyDetector
|
||||
|
||||
$patterns[] = [
|
||||
'type' => 'high_failure_risk',
|
||||
'confidence' => Score::fromDecimal($confidence),
|
||||
'confidence' => new Score($confidence),
|
||||
'description' => sprintf(
|
||||
'High failure rate (%.1f%%) with excessive retries (%.1f%%)',
|
||||
$features->failureRate * 100,
|
||||
@@ -192,7 +192,7 @@ final readonly class JobAnomalyDetector
|
||||
|
||||
$patterns[] = [
|
||||
'type' => 'performance_degradation',
|
||||
'confidence' => Score::fromDecimal($confidence),
|
||||
'confidence' => new Score($confidence),
|
||||
'description' => sprintf(
|
||||
'Unstable execution times (variance: %.1f%%) and memory patterns (%.1f%%)',
|
||||
$features->executionTimeVariance * 100,
|
||||
@@ -210,7 +210,7 @@ final readonly class JobAnomalyDetector
|
||||
|
||||
$patterns[] = [
|
||||
'type' => 'resource_exhaustion',
|
||||
'confidence' => Score::fromDecimal($confidence),
|
||||
'confidence' => new Score($confidence),
|
||||
'description' => sprintf(
|
||||
'High queue depth impact (%.1f%%) with memory anomalies (%.1f%%)',
|
||||
$features->queueDepthCorrelation * 100,
|
||||
@@ -228,7 +228,7 @@ final readonly class JobAnomalyDetector
|
||||
|
||||
$patterns[] = [
|
||||
'type' => 'automated_execution',
|
||||
'confidence' => Score::fromDecimal($confidence),
|
||||
'confidence' => new Score($confidence),
|
||||
'description' => sprintf(
|
||||
'Very regular timing (%.1f%%) with low variance (%.1f%%) - possible bot activity',
|
||||
$features->executionTimingRegularity * 100,
|
||||
@@ -246,7 +246,7 @@ final readonly class JobAnomalyDetector
|
||||
|
||||
$patterns[] = [
|
||||
'type' => 'data_processing_anomaly',
|
||||
'confidence' => Score::fromDecimal($confidence),
|
||||
'confidence' => new Score($confidence),
|
||||
'description' => sprintf(
|
||||
'Unusual payload sizes (%.1f%%) with memory pattern anomalies (%.1f%%)',
|
||||
$features->payloadSizeAnomaly * 100,
|
||||
@@ -311,7 +311,7 @@ final readonly class JobAnomalyDetector
|
||||
|
||||
foreach ($featureScores as $featureName => $score) {
|
||||
$weight = $weights[$featureName] ?? 1.0;
|
||||
$weightedSum += $score->getValue() * $weight;
|
||||
$weightedSum += $score->value() * $weight;
|
||||
$totalWeight += $weight;
|
||||
}
|
||||
|
||||
@@ -320,10 +320,10 @@ final readonly class JobAnomalyDetector
|
||||
// Pattern-based boosting
|
||||
$patternBoost = $this->calculatePatternBoost($detectedPatterns);
|
||||
|
||||
// Combine base score and pattern boost (max 100%)
|
||||
$finalScore = min(100.0, $baseScore + $patternBoost);
|
||||
// Combine base score and pattern boost (max 1.0)
|
||||
$finalScore = min(1.0, $baseScore + $patternBoost);
|
||||
|
||||
return new Score((int) round($finalScore));
|
||||
return new Score($finalScore);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,7 +341,7 @@ final readonly class JobAnomalyDetector
|
||||
$boost = 0.0;
|
||||
|
||||
foreach ($detectedPatterns as $pattern) {
|
||||
$confidence = $pattern['confidence']->getValue();
|
||||
$confidence = $pattern['confidence']->value();
|
||||
|
||||
if ($confidence >= 70) {
|
||||
$boost += 10.0; // High confidence pattern: +10%
|
||||
@@ -369,8 +369,8 @@ final readonly class JobAnomalyDetector
|
||||
$primaryIndicator = 'unknown';
|
||||
|
||||
foreach ($featureScores as $featureName => $score) {
|
||||
if ($score->getValue() > $maxScore) {
|
||||
$maxScore = $score->getValue();
|
||||
if ($score->value() > $maxScore) {
|
||||
$maxScore = $score->value();
|
||||
$primaryIndicator = $featureName;
|
||||
}
|
||||
}
|
||||
@@ -384,9 +384,19 @@ final readonly class JobAnomalyDetector
|
||||
public function getConfiguration(): array
|
||||
{
|
||||
return [
|
||||
'anomaly_threshold' => $this->anomalyThreshold->getValue(),
|
||||
'anomaly_threshold' => $this->anomalyThreshold->value(),
|
||||
'z_score_threshold' => $this->zScoreThreshold,
|
||||
'iqr_multiplier' => $this->iqrMultiplier
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the anomaly threshold
|
||||
*
|
||||
* @return Score Minimum score to classify as anomalous
|
||||
*/
|
||||
public function getThreshold(): Score
|
||||
{
|
||||
return $this->anomalyThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ final readonly class QueueAnomalyModelAdapter
|
||||
|
||||
// Determine prediction
|
||||
$prediction = $analysisResult->isAnomalous;
|
||||
$confidence = $analysisResult->anomalyScore->getValue() / 100.0; // Convert 0-100 to 0.0-1.0
|
||||
$confidence = $analysisResult->anomalyScore->value(); // Already 0.0-1.0 range
|
||||
|
||||
// Track prediction in performance monitor
|
||||
$this->performanceMonitor->trackPrediction(
|
||||
@@ -104,9 +104,9 @@ final readonly class QueueAnomalyModelAdapter
|
||||
// Convert result to array format
|
||||
$resultArray = [
|
||||
'is_anomalous' => $analysisResult->isAnomalous,
|
||||
'anomaly_score' => $analysisResult->anomalyScore->getValue(),
|
||||
'anomaly_score' => $analysisResult->anomalyScore->value(),
|
||||
'feature_scores' => array_map(
|
||||
fn($score) => $score->getValue(),
|
||||
fn($score) => $score->value(),
|
||||
$analysisResult->featureScores
|
||||
),
|
||||
'detected_patterns' => $analysisResult->detectedPatterns,
|
||||
|
||||
216
src/Framework/Queue/MachineLearning/QueueAnomalyMonitor.php
Normal file
216
src/Framework/Queue/MachineLearning/QueueAnomalyMonitor.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Queue\MachineLearning;
|
||||
|
||||
use App\Framework\Queue\MachineLearning\ValueObjects\JobAnomalyResult;
|
||||
use App\Framework\Queue\Services\JobMetricsManager;
|
||||
use App\Framework\Queue\ValueObjects\JobMetadata;
|
||||
use App\Framework\Queue\ValueObjects\JobMetrics;
|
||||
use App\Framework\Logging\Logger;
|
||||
use App\Framework\Events\EventDispatcher;
|
||||
|
||||
/**
|
||||
* Queue Anomaly Monitor
|
||||
*
|
||||
* Monitors queue job execution for anomalies using ML-based detection.
|
||||
*
|
||||
* Features:
|
||||
* - Real-time anomaly detection on job completion
|
||||
* - Automatic alerting for critical anomalies
|
||||
* - Anomaly result persistence
|
||||
* - Event dispatching for anomaly notifications
|
||||
*/
|
||||
final readonly class QueueAnomalyMonitor
|
||||
{
|
||||
public function __construct(
|
||||
private JobAnomalyDetector $anomalyDetector,
|
||||
private QueueJobFeatureExtractor $featureExtractor,
|
||||
private JobMetricsManager $metricsManager,
|
||||
private Logger $logger,
|
||||
private ?EventDispatcher $eventDispatcher = null
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Analyze job for anomalies after execution
|
||||
*
|
||||
* @param JobMetrics $metrics Job execution metrics
|
||||
* @param JobMetadata $metadata Job metadata
|
||||
* @param int $queueDepth Current queue size
|
||||
* @return JobAnomalyResult Detection result
|
||||
*/
|
||||
public function analyzeJobExecution(
|
||||
JobMetrics $metrics,
|
||||
JobMetadata $metadata,
|
||||
int $queueDepth = 0
|
||||
): JobAnomalyResult {
|
||||
// Extract features from job metrics
|
||||
$features = $this->featureExtractor->extractFeatures($metrics, $metadata, $queueDepth);
|
||||
|
||||
// Detect anomalies
|
||||
$result = $this->anomalyDetector->detect($features);
|
||||
|
||||
// Log anomaly if detected
|
||||
if ($result->isAnomalous) {
|
||||
$this->logAnomaly($metrics, $metadata, $result);
|
||||
}
|
||||
|
||||
// Alert on critical anomalies
|
||||
if ($result->requiresImmediateAttention()) {
|
||||
$this->alertCriticalAnomaly($metrics, $metadata, $result);
|
||||
}
|
||||
|
||||
// Dispatch event for anomaly notification
|
||||
if ($result->isAnomalous && $this->eventDispatcher !== null) {
|
||||
$this->eventDispatcher->dispatch(
|
||||
new QueueJobAnomalyDetectedEvent($metrics, $metadata, $result)
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze multiple jobs in batch
|
||||
*
|
||||
* @param array $jobsData Array of [JobMetrics, JobMetadata] tuples
|
||||
* @return array Array of JobAnomalyResult indexed by job ID
|
||||
*/
|
||||
public function analyzeBatch(array $jobsData): array
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($jobsData as [$metrics, $metadata]) {
|
||||
$results[$metrics->jobId] = $this->analyzeJobExecution($metrics, $metadata);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get anomaly statistics for a queue
|
||||
*
|
||||
* @param string $queueName Queue to analyze
|
||||
* @param string $timeWindow Time window (e.g., '24 hours')
|
||||
* @return array Anomaly statistics
|
||||
*/
|
||||
public function getQueueAnomalyStats(string $queueName, string $timeWindow = '24 hours'): array
|
||||
{
|
||||
// This would query persisted anomaly results
|
||||
// For now, return placeholder stats
|
||||
|
||||
return [
|
||||
'queue_name' => $queueName,
|
||||
'time_window' => $timeWindow,
|
||||
'total_jobs_analyzed' => 0,
|
||||
'anomalies_detected' => 0,
|
||||
'critical_anomalies' => 0,
|
||||
'anomaly_rate' => 0.0,
|
||||
'most_common_patterns' => [],
|
||||
'top_anomalous_jobs' => []
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Log anomaly detection
|
||||
*/
|
||||
private function logAnomaly(
|
||||
JobMetrics $metrics,
|
||||
JobMetadata $metadata,
|
||||
JobAnomalyResult $result
|
||||
): void {
|
||||
$this->logger->warning('Queue job anomaly detected', [
|
||||
'job_id' => $metrics->jobId,
|
||||
'queue_name' => $metrics->queueName,
|
||||
'job_class' => $metadata->class->toString(),
|
||||
'anomaly_score' => $result->anomalyScore->value(),
|
||||
'severity' => $result->getSeverity(),
|
||||
'primary_indicator' => $result->primaryIndicator,
|
||||
'detected_patterns' => array_map(
|
||||
fn($pattern) => [
|
||||
'type' => $pattern['type'],
|
||||
'confidence' => $pattern['confidence']->value()
|
||||
],
|
||||
$result->detectedPatterns
|
||||
),
|
||||
'execution_time_ms' => $metrics->executionTimeMs,
|
||||
'memory_usage_mb' => $metrics->getMemoryUsageMB(),
|
||||
'attempts' => $metrics->attempts,
|
||||
'status' => $metrics->status
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alert on critical anomalies
|
||||
*/
|
||||
private function alertCriticalAnomaly(
|
||||
JobMetrics $metrics,
|
||||
JobMetadata $metadata,
|
||||
JobAnomalyResult $result
|
||||
): void {
|
||||
$this->logger->critical('CRITICAL queue job anomaly requires immediate attention', [
|
||||
'job_id' => $metrics->jobId,
|
||||
'queue_name' => $metrics->queueName,
|
||||
'job_class' => $metadata->class->toString(),
|
||||
'anomaly_score' => $result->anomalyScore->value(),
|
||||
'severity' => $result->getSeverity(),
|
||||
'recommended_action' => $result->getRecommendedAction(),
|
||||
'top_contributors' => $result->getTopContributors(3),
|
||||
'alert_type' => 'queue_job_critical_anomaly',
|
||||
'alert_priority' => 'high'
|
||||
]);
|
||||
|
||||
// In a full implementation, this would:
|
||||
// - Send PagerDuty/OpsGenie alert
|
||||
// - Post to Slack channel
|
||||
// - Trigger incident response workflow
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable anomaly monitoring for a queue
|
||||
*
|
||||
* This would be called during queue worker initialization.
|
||||
*
|
||||
* @param string $queueName Queue to monitor
|
||||
*/
|
||||
public function enableMonitoring(string $queueName): void
|
||||
{
|
||||
$this->logger->info('Anomaly monitoring enabled', [
|
||||
'queue_name' => $queueName,
|
||||
'detector_threshold' => $this->anomalyDetector->getThreshold()->value(),
|
||||
'monitoring_active' => true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable anomaly monitoring for a queue
|
||||
*
|
||||
* @param string $queueName Queue to stop monitoring
|
||||
*/
|
||||
public function disableMonitoring(string $queueName): void
|
||||
{
|
||||
$this->logger->info('Anomaly monitoring disabled', [
|
||||
'queue_name' => $queueName,
|
||||
'monitoring_active' => false
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get monitoring status
|
||||
*
|
||||
* @return array Monitoring configuration and stats
|
||||
*/
|
||||
public function getMonitoringStatus(): array
|
||||
{
|
||||
return [
|
||||
'enabled' => true,
|
||||
'detector_threshold' => $this->anomalyDetector->getThreshold()->value(),
|
||||
'z_score_threshold' => 3.0, // From detector config
|
||||
'iqr_multiplier' => 1.5, // From detector config
|
||||
'monitored_queues' => [], // Would list active queues
|
||||
'detection_count_24h' => 0, // Would query persisted results
|
||||
'critical_count_24h' => 0
|
||||
];
|
||||
}
|
||||
}
|
||||
252
src/Framework/Queue/MachineLearning/QueueJobFeatureExtractor.php
Normal file
252
src/Framework/Queue/MachineLearning/QueueJobFeatureExtractor.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Queue\MachineLearning;
|
||||
|
||||
use App\Framework\Queue\MachineLearning\ValueObjects\JobFeatures;
|
||||
use App\Framework\Queue\Services\JobMetricsManager;
|
||||
use App\Framework\Queue\ValueObjects\JobMetadata;
|
||||
use App\Framework\Queue\ValueObjects\JobMetrics;
|
||||
|
||||
/**
|
||||
* Queue Job Feature Extractor
|
||||
*
|
||||
* Extracts normalized features from queue job metrics for anomaly detection.
|
||||
*
|
||||
* Features extracted:
|
||||
* - execution_time_variance: Variance from average execution time for job type
|
||||
* - memory_usage_pattern: Deviation from typical memory usage
|
||||
* - retry_frequency: Normalized retry count
|
||||
* - failure_rate: Failure ratio for job type
|
||||
* - queue_depth_correlation: Relationship between execution time and queue size
|
||||
* - dependency_chain_complexity: Number of dependent jobs
|
||||
* - payload_size_anomaly: Deviation from typical payload size
|
||||
* - execution_timing_regularity: Consistency of execution intervals
|
||||
*/
|
||||
final readonly class QueueJobFeatureExtractor
|
||||
{
|
||||
public function __construct(
|
||||
private JobMetricsManager $metricsManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Extract JobFeatures from job execution metrics
|
||||
*
|
||||
* @param JobMetrics $currentMetrics Current job metrics
|
||||
* @param JobMetadata $metadata Job metadata
|
||||
* @param int $queueDepth Current queue size
|
||||
* @return JobFeatures Normalized feature vector (all values 0.0-1.0)
|
||||
*/
|
||||
public function extractFeatures(
|
||||
JobMetrics $currentMetrics,
|
||||
JobMetadata $metadata,
|
||||
int $queueDepth = 0
|
||||
): JobFeatures {
|
||||
// Get historical stats for this job type
|
||||
$historicalStats = $this->metricsManager->getPerformanceStats(
|
||||
queueName: $currentMetrics->queueName,
|
||||
timeWindow: '24 hours'
|
||||
);
|
||||
|
||||
return new JobFeatures(
|
||||
executionTimeVariance: $this->calculateExecutionTimeVariance($currentMetrics, $historicalStats),
|
||||
memoryUsagePattern: $this->calculateMemoryUsagePattern($currentMetrics, $historicalStats),
|
||||
retryFrequency: $this->calculateRetryFrequency($currentMetrics),
|
||||
failureRate: $this->calculateFailureRate($historicalStats),
|
||||
queueDepthCorrelation: $this->calculateQueueDepthCorrelation($queueDepth),
|
||||
dependencyChainComplexity: $this->calculateDependencyComplexity($metadata),
|
||||
payloadSizeAnomaly: $this->calculatePayloadSizeAnomaly($metadata, $historicalStats),
|
||||
executionTimingRegularity: $this->calculateExecutionTimingRegularity($currentMetrics)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate execution time variance (0.0-1.0)
|
||||
*
|
||||
* Measures how much the current execution time deviates from the average.
|
||||
* High variance indicates unstable performance.
|
||||
*/
|
||||
private function calculateExecutionTimeVariance(
|
||||
JobMetrics $metrics,
|
||||
array $historicalStats
|
||||
): float {
|
||||
$avgExecutionTime = $historicalStats['average_execution_time_ms'] ?? 0;
|
||||
|
||||
if ($avgExecutionTime <= 0) {
|
||||
return 0.0; // No historical data yet
|
||||
}
|
||||
|
||||
$currentExecutionTime = $metrics->executionTimeMs;
|
||||
$deviation = abs($currentExecutionTime - $avgExecutionTime) / $avgExecutionTime;
|
||||
|
||||
// Normalize: 0 = exactly average, 1.0 = 10x or more deviation
|
||||
return min(1.0, $deviation / 10.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate memory usage pattern (0.0-1.0)
|
||||
*
|
||||
* Measures memory usage anomaly compared to historical average.
|
||||
*/
|
||||
private function calculateMemoryUsagePattern(
|
||||
JobMetrics $metrics,
|
||||
array $historicalStats
|
||||
): float {
|
||||
$avgMemoryUsage = $historicalStats['average_memory_usage_bytes'] ?? 0;
|
||||
|
||||
if ($avgMemoryUsage <= 0) {
|
||||
return 0.0; // No historical data yet
|
||||
}
|
||||
|
||||
$currentMemoryUsage = $metrics->memoryUsageBytes;
|
||||
$deviation = abs($currentMemoryUsage - $avgMemoryUsage) / $avgMemoryUsage;
|
||||
|
||||
// Normalize: 0 = average usage, 1.0 = 5x or more deviation
|
||||
return min(1.0, $deviation / 5.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate retry frequency (0.0-1.0)
|
||||
*
|
||||
* Normalized retry count: 0 = no retries, 1.0 = max attempts exhausted
|
||||
*/
|
||||
private function calculateRetryFrequency(JobMetrics $metrics): float
|
||||
{
|
||||
if ($metrics->maxAttempts <= 1) {
|
||||
return 0.0; // No retry configuration
|
||||
}
|
||||
|
||||
return min(1.0, $metrics->attempts / $metrics->maxAttempts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate failure rate (0.0-1.0)
|
||||
*
|
||||
* Percentage of failed jobs for this queue over time window.
|
||||
*/
|
||||
private function calculateFailureRate(array $historicalStats): float
|
||||
{
|
||||
$totalJobs = $historicalStats['total_jobs'] ?? 0;
|
||||
|
||||
if ($totalJobs === 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$failedJobs = $historicalStats['failed_jobs'] ?? 0;
|
||||
|
||||
return min(1.0, $failedJobs / $totalJobs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate queue depth correlation (0.0-1.0)
|
||||
*
|
||||
* Impact of queue depth on performance.
|
||||
* High values indicate system is overloaded.
|
||||
*/
|
||||
private function calculateQueueDepthCorrelation(int $queueDepth): float
|
||||
{
|
||||
// Normalize queue depth: 0 = empty, 1.0 = 1000+ jobs queued
|
||||
return min(1.0, $queueDepth / 1000.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate dependency chain complexity (0.0-1.0)
|
||||
*
|
||||
* Currently a placeholder - would analyze job dependency graph.
|
||||
* For now, use job tags to estimate complexity.
|
||||
*/
|
||||
private function calculateDependencyComplexity(JobMetadata $metadata): float
|
||||
{
|
||||
$tagCount = count($metadata->tags);
|
||||
|
||||
// Simple heuristic: more tags = more complex job
|
||||
return min(1.0, $tagCount / 10.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate payload size anomaly (0.0-1.0)
|
||||
*
|
||||
* Deviation from typical payload size for this job type.
|
||||
* Currently estimates from metadata extra fields.
|
||||
*/
|
||||
private function calculatePayloadSizeAnomaly(
|
||||
JobMetadata $metadata,
|
||||
array $historicalStats
|
||||
): float {
|
||||
$extraFieldCount = count($metadata->extra);
|
||||
|
||||
// Simple heuristic: more extra fields = larger payload
|
||||
// Normalize: 0 = typical, 1.0 = 50+ extra fields
|
||||
return min(1.0, $extraFieldCount / 50.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate execution timing regularity (0.0-1.0)
|
||||
*
|
||||
* Measures consistency of execution intervals.
|
||||
* High regularity (near 1.0) can indicate bot-like behavior.
|
||||
*/
|
||||
private function calculateExecutionTimingRegularity(JobMetrics $metrics): float
|
||||
{
|
||||
// For now, use job type consistency as proxy
|
||||
// In a full implementation, would analyze inter-arrival times
|
||||
|
||||
// If job has metadata indicating scheduled execution, mark as regular
|
||||
$metadata = $metrics->metadata ?? [];
|
||||
|
||||
if (isset($metadata['scheduled']) && $metadata['scheduled']) {
|
||||
return 0.9; // Scheduled jobs are highly regular (expected)
|
||||
}
|
||||
|
||||
// Default: moderate regularity for queue jobs
|
||||
return 0.3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract features from job metrics history for batch analysis
|
||||
*
|
||||
* @param string $jobId Job ID to analyze
|
||||
* @return JobFeatures[] Array of feature vectors over time
|
||||
*/
|
||||
public function extractHistoricalFeatures(string $jobId): array
|
||||
{
|
||||
$metricsHistory = $this->metricsManager->getJobMetricsHistory($jobId);
|
||||
|
||||
$features = [];
|
||||
|
||||
foreach ($metricsHistory as $metrics) {
|
||||
// Create minimal metadata from metrics
|
||||
$metadata = new JobMetadata(
|
||||
id: new \App\Framework\Ulid\Ulid(new \App\Framework\DateTime\SystemClock()),
|
||||
class: \App\Framework\Core\ValueObjects\ClassName::create($metrics->queueName),
|
||||
type: 'job',
|
||||
queuedAt: \App\Framework\Core\ValueObjects\Timestamp::now(),
|
||||
tags: [],
|
||||
extra: $metrics->metadata ?? []
|
||||
);
|
||||
|
||||
$features[] = $this->extractFeatures($metrics, $metadata, 0);
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract features for all recent jobs in a queue
|
||||
*
|
||||
* @param string $queueName Queue to analyze
|
||||
* @param int $limit Maximum number of jobs to analyze
|
||||
* @return array Array of [JobMetrics, JobFeatures] tuples
|
||||
*/
|
||||
public function extractQueueFeatures(string $queueName, int $limit = 100): array
|
||||
{
|
||||
// Get recent job metrics for this queue
|
||||
$historicalStats = $this->metricsManager->getPerformanceStats($queueName, '1 hour');
|
||||
|
||||
// This would need a method to get recent jobs - placeholder for now
|
||||
// In a full implementation, would query job_metrics table for recent jobs
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -118,9 +118,9 @@ final readonly class JobAnomalyResult
|
||||
}
|
||||
|
||||
return match (true) {
|
||||
$this->anomalyScore->getValue() >= 80 => 'critical',
|
||||
$this->anomalyScore->getValue() >= 60 => 'high',
|
||||
$this->anomalyScore->getValue() >= 40 => 'medium',
|
||||
$this->anomalyScore->value() >= 80 => 'critical',
|
||||
$this->anomalyScore->value() >= 60 => 'high',
|
||||
$this->anomalyScore->value() >= 40 => 'medium',
|
||||
default => 'low'
|
||||
};
|
||||
}
|
||||
@@ -205,7 +205,7 @@ final readonly class JobAnomalyResult
|
||||
// Calculate total score for percentage calculation
|
||||
$totalScore = array_reduce(
|
||||
$this->featureScores,
|
||||
fn(float $carry, Score $score) => $carry + $score->getValue(),
|
||||
fn(float $carry, Score $score) => $carry + $score->value(),
|
||||
0.0
|
||||
);
|
||||
|
||||
@@ -215,7 +215,7 @@ final readonly class JobAnomalyResult
|
||||
|
||||
// Sort features by score descending
|
||||
$sorted = $this->featureScores;
|
||||
uasort($sorted, fn(Score $a, Score $b) => $b->getValue() <=> $a->getValue());
|
||||
uasort($sorted, fn(Score $a, Score $b) => $b->value() <=> $a->value());
|
||||
|
||||
// Take top N and calculate contribution percentages
|
||||
$contributors = [];
|
||||
@@ -229,7 +229,7 @@ final readonly class JobAnomalyResult
|
||||
$contributors[] = [
|
||||
'feature' => $feature,
|
||||
'score' => $score,
|
||||
'contribution_percentage' => ($score->getValue() / $totalScore) * 100.0
|
||||
'contribution_percentage' => ($score->value() / $totalScore) * 100.0
|
||||
];
|
||||
|
||||
$count++;
|
||||
@@ -294,7 +294,7 @@ final readonly class JobAnomalyResult
|
||||
*/
|
||||
public function getConfidenceLevel(): string
|
||||
{
|
||||
$value = $this->anomalyScore->getValue();
|
||||
$value = $this->anomalyScore->value();
|
||||
|
||||
return match (true) {
|
||||
$value >= 80 => 'very_high',
|
||||
@@ -313,7 +313,7 @@ final readonly class JobAnomalyResult
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'anomaly_score' => $this->anomalyScore->getValue(),
|
||||
'anomaly_score' => $this->anomalyScore->value(),
|
||||
'is_anomalous' => $this->isAnomalous,
|
||||
'severity' => $this->getSeverity(),
|
||||
'confidence_level' => $this->getConfidenceLevel(),
|
||||
@@ -321,13 +321,13 @@ final readonly class JobAnomalyResult
|
||||
'detected_patterns' => array_map(
|
||||
fn(array $pattern) => [
|
||||
'type' => $pattern['type'],
|
||||
'confidence' => $pattern['confidence']->getValue(),
|
||||
'confidence' => $pattern['confidence']->value(),
|
||||
'description' => $pattern['description']
|
||||
],
|
||||
$this->detectedPatterns
|
||||
),
|
||||
'feature_scores' => array_map(
|
||||
fn(Score $score) => $score->getValue(),
|
||||
fn(Score $score) => $score->value(),
|
||||
$this->featureScores
|
||||
),
|
||||
'top_contributors' => $this->getTopContributors(3),
|
||||
@@ -346,7 +346,7 @@ final readonly class JobAnomalyResult
|
||||
return 'JobAnomalyResult[Normal]';
|
||||
}
|
||||
|
||||
$score = $this->anomalyScore->getValue();
|
||||
$score = $this->anomalyScore->value();
|
||||
$severity = $this->getSeverity();
|
||||
$patterns = implode(', ', $this->getPatternTypes());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user