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.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\MachineLearning;
use App\Framework\Config\Environment;
use App\Framework\DI\Container;
use App\Framework\DI\Attributes\Initializer;
use App\Framework\MachineLearning\ModelManagement\ModelPerformanceMonitor;
use App\Framework\MachineLearning\ModelManagement\ModelRegistry;
use App\Framework\Queue\Services\JobMetricsManagerInterface;
use App\Framework\Core\ValueObjects\Score;
use Psr\Log\LoggerInterface;
/**
* Job Anomaly Detection Initializer
*
* Registers ML-based job anomaly detection components in DI container.
*
* Registered Services:
* - JobFeatureExtractor: Extract 8 behavioral features from job sequences
* - JobAnomalyDetector: Statistical + heuristic anomaly detection
* - JobHistoryAnalyzer: Historical job pattern analysis coordinator
*
* Configuration:
* - Uses framework defaults for thresholds (customizable via environment)
* - Integrates with existing JobMetricsManager infrastructure
*/
final readonly class JobAnomalyDetectionInitializer
{
public function __construct(
private Container $container,
private Environment $environment,
private LoggerInterface $logger
) {}
#[Initializer]
public function initialize(Container $container): void
{
// Register JobFeatureExtractor
$container->singleton(JobFeatureExtractor::class, function() {
return new JobFeatureExtractor(
minConfidence: $this->getMinConfidence()
);
});
// Register JobAnomalyDetector
$container->singleton(JobAnomalyDetector::class, function() {
return new JobAnomalyDetector(
anomalyThreshold: $this->getAnomalyThreshold(),
zScoreThreshold: $this->getZScoreThreshold(),
iqrMultiplier: $this->getIqrMultiplier()
);
});
// Register JobHistoryAnalyzer
$container->singleton(JobHistoryAnalyzer::class, function(Container $container) {
return new JobHistoryAnalyzer(
metricsManager: $container->get(JobMetricsManagerInterface::class),
featureExtractor: $container->get(JobFeatureExtractor::class),
anomalyDetector: $container->get(JobAnomalyDetector::class)
);
});
}
/**
* Get minimum confidence threshold for feature extraction
*
* Default: 0.6 (60%)
* Environment: JOB_ANOMALY_MIN_CONFIDENCE
*/
private function getMinConfidence(): float
{
$envValue = getenv('JOB_ANOMALY_MIN_CONFIDENCE');
if ($envValue !== false) {
$value = (float) $envValue;
return max(0.0, min(1.0, $value)); // Clamp to 0.0-1.0
}
return 0.6;
}
/**
* Get anomaly detection threshold
*
* Default: 0.5 (50% - Score of 50)
* Environment: JOB_ANOMALY_THRESHOLD
*/
private function getAnomalyThreshold(): Score
{
$envValue = getenv('JOB_ANOMALY_THRESHOLD');
if ($envValue !== false) {
$value = (int) $envValue;
$value = max(0, min(100, $value)); // Clamp to 0-100
return new Score($value);
}
return new Score(50); // 50% threshold
}
/**
* Get Z-score threshold for statistical outlier detection
*
* Default: 3.0 (3 standard deviations)
* Environment: JOB_ANOMALY_ZSCORE_THRESHOLD
*/
private function getZScoreThreshold(): float
{
$envValue = getenv('JOB_ANOMALY_ZSCORE_THRESHOLD');
if ($envValue !== false) {
$value = (float) $envValue;
return max(1.0, min(5.0, $value)); // Clamp to 1.0-5.0
}
return 3.0;
}
/**
* Get IQR multiplier for outlier detection
*
* Default: 1.5 (standard IQR method)
* Environment: JOB_ANOMALY_IQR_MULTIPLIER
*/
private function getIqrMultiplier(): float
{
$envValue = getenv('JOB_ANOMALY_IQR_MULTIPLIER');
if ($envValue !== false) {
$value = (float) $envValue;
return max(1.0, min(3.0, $value)); // Clamp to 1.0-3.0
}
return 1.5;
}
/**
* Initialize Queue Anomaly Model Adapter for ML Model Management integration
*/
#[Initializer]
public function initializeModelAdapter(): QueueAnomalyModelAdapter
{
$this->logger->info('Initializing Queue Anomaly Model Adapter');
try {
// Get required dependencies from container
$registry = $this->container->get(ModelRegistry::class);
$performanceMonitor = $this->container->get(ModelPerformanceMonitor::class);
$anomalyDetector = $this->container->get(JobAnomalyDetector::class);
$adapter = new QueueAnomalyModelAdapter(
registry: $registry,
performanceMonitor: $performanceMonitor,
detector: $anomalyDetector
);
// Auto-register current model version if enabled
if ($this->environment->getBool('QUEUE_ML_AUTO_REGISTER', true)) {
try {
$metadata = $adapter->registerCurrentModel();
$this->logger->info('Queue anomaly model auto-registered', [
'model_name' => $metadata->modelName,
'version' => $metadata->version->toString(),
'type' => $metadata->modelType->value,
]);
} catch (\Exception $e) {
// Model might already exist, which is fine
$this->logger->debug('Queue anomaly model registration skipped', [
'reason' => $e->getMessage(),
]);
}
}
$this->logger->info('Queue Anomaly Model Adapter initialized successfully');
return $adapter;
} catch (\Throwable $e) {
$this->logger->error('Failed to initialize Queue Anomaly Model Adapter', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
}

View File

@@ -0,0 +1,392 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\MachineLearning;
use App\Framework\Queue\MachineLearning\ValueObjects\JobFeatures;
use App\Framework\Queue\MachineLearning\ValueObjects\JobAnomalyResult;
use App\Framework\Core\ValueObjects\Score;
/**
* Job Anomaly Detector - Statistical and Heuristic Job Behavior Analysis
*
* Detects anomalous job execution patterns using combination of:
* - Statistical outlier detection (Z-Score, IQR methods)
* - Heuristic pattern matching (high failure risk, performance degradation, etc.)
* - Multi-feature analysis with weighted scoring
*
* Detection Methods:
* 1. Z-Score Analysis: Identifies statistical outliers (>3 standard deviations)
* 2. IQR Analysis: Identifies outliers using interquartile range (>1.5 * IQR)
* 3. Heuristic Patterns: Rule-based threat classification
* 4. Weighted Feature Scoring: Combines feature scores with domain weights
*
* All confidence scores use framework's Core Score (0.0-1.0).
*/
final readonly class JobAnomalyDetector
{
/**
* @param Score $anomalyThreshold Minimum score to classify as anomalous (default: 0.5 = 50%)
* @param float $zScoreThreshold Z-score threshold for statistical outliers (default: 3.0)
* @param float $iqrMultiplier IQR multiplier for outlier detection (default: 1.5)
*/
public function __construct(
private Score $anomalyThreshold = new Score(50), // 50% threshold
private float $zScoreThreshold = 3.0,
private float $iqrMultiplier = 1.5
) {}
/**
* Detect anomalies in job execution features
*
* Combines statistical analysis and heuristic pattern matching
* to provide comprehensive anomaly detection.
*/
public function detect(JobFeatures $features): JobAnomalyResult
{
// Step 1: Calculate feature-specific anomaly scores
$featureScores = $this->calculateFeatureScores($features);
// Step 2: Detect heuristic patterns
$detectedPatterns = $this->detectPatterns($features);
// Step 3: Calculate overall anomaly score (weighted average)
$overallScore = $this->calculateOverallScore($featureScores, $detectedPatterns);
// Step 4: Determine if anomalous based on threshold
$isAnomalous = $overallScore->getValue() >= $this->anomalyThreshold->getValue();
// Step 5: Identify primary indicator (highest scoring feature)
$primaryIndicator = $this->identifyPrimaryIndicator($featureScores);
// Step 6: Build result
if (!$isAnomalous) {
if ($overallScore->getValue() > 0) {
return JobAnomalyResult::lowConfidence(
$overallScore,
$featureScores,
'Score below anomaly threshold'
);
}
return JobAnomalyResult::normal('No anomalies detected');
}
return JobAnomalyResult::anomalous(
$overallScore,
$featureScores,
$detectedPatterns,
$primaryIndicator
);
}
/**
* Calculate anomaly score for each feature
*
* Uses statistical thresholds and domain knowledge to score
* individual feature contributions to overall anomaly.
*
* @return array<string, Score>
*/
private function calculateFeatureScores(JobFeatures $features): array
{
$featureArray = $features->toArray();
$scores = [];
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);
}
return $scores;
}
/**
* Convert feature value to anomaly score using domain-specific thresholds
*
* Different features have different "normal" ranges and criticality.
*/
private function featureValueToAnomalyScore(string $featureName, float $value): float
{
return match ($featureName) {
// Critical features: Lower threshold for anomaly
'failure_rate' => $this->scoreWithThreshold($value, 0.1, 0.3), // >10% concerning, >30% critical
'retry_frequency' => $this->scoreWithThreshold($value, 0.2, 0.5), // >20% concerning, >50% critical
'memory_usage_pattern' => $this->scoreWithThreshold($value, 0.5, 0.7), // >50% concerning, >70% critical
// Important features: Medium threshold
'execution_time_variance' => $this->scoreWithThreshold($value, 0.4, 0.6),
'queue_depth_correlation' => $this->scoreWithThreshold($value, 0.6, 0.8),
'payload_size_anomaly' => $this->scoreWithThreshold($value, 0.6, 0.8),
// Informational features: Higher threshold
'dependency_chain_complexity' => $this->scoreWithThreshold($value, 0.7, 0.9),
'execution_timing_regularity' => $this->scoreWithThreshold($value, 0.8, 0.95), // Very high regularity = bot
default => $value // Fallback: use value directly
};
}
/**
* Score feature value using low/high thresholds
*
* Linear interpolation between thresholds:
* - value <= low: 0.0 (normal)
* - low < value < high: linear scale 0.0-0.5
* - value >= high: value * 1.0 (high anomaly)
*/
private function scoreWithThreshold(float $value, float $lowThreshold, float $highThreshold): float
{
if ($value <= $lowThreshold) {
return 0.0;
}
if ($value >= $highThreshold) {
return $value; // Use value directly for high anomalies
}
// Linear interpolation between low and high threshold
$range = $highThreshold - $lowThreshold;
$position = ($value - $lowThreshold) / $range;
return $position * 0.5; // Scale to 0.0-0.5 for medium concern
}
/**
* Detect heuristic anomaly patterns
*
* Uses JobFeatures built-in indicators for pattern matching.
*
* @return array<array{type: string, confidence: Score, description: string}>
*/
private function detectPatterns(JobFeatures $features): array
{
$patterns = [];
// Pattern 1: High Failure Risk
if ($features->indicatesHighFailureRisk()) {
$confidence = $this->calculatePatternConfidence([
$features->failureRate,
$features->retryFrequency
]);
$patterns[] = [
'type' => 'high_failure_risk',
'confidence' => Score::fromDecimal($confidence),
'description' => sprintf(
'High failure rate (%.1f%%) with excessive retries (%.1f%%)',
$features->failureRate * 100,
$features->retryFrequency * 100
)
];
}
// Pattern 2: Performance Degradation
if ($features->indicatesPerformanceDegradation()) {
$confidence = $this->calculatePatternConfidence([
$features->executionTimeVariance,
$features->memoryUsagePattern
]);
$patterns[] = [
'type' => 'performance_degradation',
'confidence' => Score::fromDecimal($confidence),
'description' => sprintf(
'Unstable execution times (variance: %.1f%%) and memory patterns (%.1f%%)',
$features->executionTimeVariance * 100,
$features->memoryUsagePattern * 100
)
];
}
// Pattern 3: Resource Exhaustion
if ($features->indicatesResourceExhaustion()) {
$confidence = $this->calculatePatternConfidence([
$features->queueDepthCorrelation,
$features->memoryUsagePattern
]);
$patterns[] = [
'type' => 'resource_exhaustion',
'confidence' => Score::fromDecimal($confidence),
'description' => sprintf(
'High queue depth impact (%.1f%%) with memory anomalies (%.1f%%)',
$features->queueDepthCorrelation * 100,
$features->memoryUsagePattern * 100
)
];
}
// Pattern 4: Automated Execution (Bot-like)
if ($features->indicatesAutomatedExecution()) {
$confidence = $this->calculatePatternConfidence([
$features->executionTimingRegularity,
1.0 - $features->executionTimeVariance // Inverted: low variance = higher confidence
]);
$patterns[] = [
'type' => 'automated_execution',
'confidence' => Score::fromDecimal($confidence),
'description' => sprintf(
'Very regular timing (%.1f%%) with low variance (%.1f%%) - possible bot activity',
$features->executionTimingRegularity * 100,
$features->executionTimeVariance * 100
)
];
}
// Pattern 5: Data Processing Anomaly
if ($features->indicatesDataProcessingAnomaly()) {
$confidence = $this->calculatePatternConfidence([
$features->payloadSizeAnomaly,
$features->memoryUsagePattern
]);
$patterns[] = [
'type' => 'data_processing_anomaly',
'confidence' => Score::fromDecimal($confidence),
'description' => sprintf(
'Unusual payload sizes (%.1f%%) with memory pattern anomalies (%.1f%%)',
$features->payloadSizeAnomaly * 100,
$features->memoryUsagePattern * 100
)
];
}
return $patterns;
}
/**
* Calculate pattern confidence from contributing feature values
*
* Uses average of feature values, weighted by their strength.
*/
private function calculatePatternConfidence(array $featureValues): float
{
if (empty($featureValues)) {
return 0.0;
}
// Average of all contributing features
$average = array_sum($featureValues) / count($featureValues);
// Boost confidence if multiple strong indicators
$strongIndicators = count(array_filter($featureValues, fn($v) => $v > 0.7));
$confidenceBoost = min(0.2, $strongIndicators * 0.1);
return min(1.0, $average + $confidenceBoost);
}
/**
* Calculate overall anomaly score
*
* Weighted average of feature scores with pattern-based boosting.
*
* @param array<string, Score> $featureScores
* @param array<array{type: string, confidence: Score}> $detectedPatterns
*/
private function calculateOverallScore(array $featureScores, array $detectedPatterns): Score
{
if (empty($featureScores)) {
return Score::zero();
}
// Feature weights (domain knowledge)
$weights = [
'failure_rate' => 2.0, // Most critical
'retry_frequency' => 1.8, // Very important
'memory_usage_pattern' => 1.5, // Important for resource issues
'execution_time_variance' => 1.3, // Performance indicator
'queue_depth_correlation' => 1.2, // Scalability indicator
'payload_size_anomaly' => 1.0, // Moderate importance
'dependency_chain_complexity' => 0.8, // Less critical
'execution_timing_regularity' => 0.7, // Informational
];
// Calculate weighted feature score
$weightedSum = 0.0;
$totalWeight = 0.0;
foreach ($featureScores as $featureName => $score) {
$weight = $weights[$featureName] ?? 1.0;
$weightedSum += $score->getValue() * $weight;
$totalWeight += $weight;
}
$baseScore = $totalWeight > 0 ? $weightedSum / $totalWeight : 0.0;
// Pattern-based boosting
$patternBoost = $this->calculatePatternBoost($detectedPatterns);
// Combine base score and pattern boost (max 100%)
$finalScore = min(100.0, $baseScore + $patternBoost);
return new Score((int) round($finalScore));
}
/**
* Calculate pattern boost to overall score
*
* Multiple patterns increase confidence in anomaly detection.
*/
private function calculatePatternBoost(array $detectedPatterns): float
{
if (empty($detectedPatterns)) {
return 0.0;
}
// Each high-confidence pattern adds to the boost
$boost = 0.0;
foreach ($detectedPatterns as $pattern) {
$confidence = $pattern['confidence']->getValue();
if ($confidence >= 70) {
$boost += 10.0; // High confidence pattern: +10%
} elseif ($confidence >= 50) {
$boost += 5.0; // Medium confidence: +5%
} else {
$boost += 2.0; // Low confidence: +2%
}
}
// Cap pattern boost at 30%
return min(30.0, $boost);
}
/**
* Identify primary indicator (feature with highest anomaly score)
*/
private function identifyPrimaryIndicator(array $featureScores): string
{
if (empty($featureScores)) {
return 'unknown';
}
$maxScore = 0.0;
$primaryIndicator = 'unknown';
foreach ($featureScores as $featureName => $score) {
if ($score->getValue() > $maxScore) {
$maxScore = $score->getValue();
$primaryIndicator = $featureName;
}
}
return $primaryIndicator;
}
/**
* Get detector configuration
*/
public function getConfiguration(): array
{
return [
'anomaly_threshold' => $this->anomalyThreshold->getValue(),
'z_score_threshold' => $this->zScoreThreshold,
'iqr_multiplier' => $this->iqrMultiplier
];
}
}

View File

@@ -0,0 +1,450 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\MachineLearning;
use App\Framework\Queue\MachineLearning\ValueObjects\JobExecutionSequence;
use App\Framework\Queue\MachineLearning\ValueObjects\JobFeatures;
/**
* Job Feature Extractor - Extract 8 Behavioral Features from Job Execution Sequences
*
* Analyzes job execution patterns and extracts normalized features
* for machine learning-based anomaly detection.
*
* Features extracted:
* 1. execution_time_variance - Execution time stability
* 2. memory_usage_pattern - Memory consumption patterns
* 3. retry_frequency - Retry attempt frequency
* 4. failure_rate - Job failure percentage
* 5. queue_depth_correlation - Queue workload impact
* 6. dependency_chain_complexity - Execution dependency complexity
* 7. payload_size_anomaly - Unusual payload sizes
* 8. execution_timing_regularity - Timing consistency
*
* All features normalized to 0.0-1.0 range.
*/
final readonly class JobFeatureExtractor
{
/**
* @param float $minConfidence Minimum confidence threshold for feature extraction (0.0-1.0)
*/
public function __construct(
private float $minConfidence = 0.6
) {
if ($minConfidence < 0.0 || $minConfidence > 1.0) {
throw new \InvalidArgumentException('minConfidence must be between 0.0 and 1.0');
}
}
/**
* Extract features from job execution sequence
*/
public function extract(JobExecutionSequence $sequence): JobFeatures
{
if ($sequence->isEmpty()) {
return JobFeatures::zero();
}
return new JobFeatures(
executionTimeVariance: $this->extractExecutionTimeVariance($sequence),
memoryUsagePattern: $this->extractMemoryUsagePattern($sequence),
retryFrequency: $this->extractRetryFrequency($sequence),
failureRate: $this->extractFailureRate($sequence),
queueDepthCorrelation: $this->extractQueueDepthCorrelation($sequence),
dependencyChainComplexity: $this->extractDependencyChainComplexity($sequence),
payloadSizeAnomaly: $this->extractPayloadSizeAnomaly($sequence),
executionTimingRegularity: $this->extractExecutionTimingRegularity($sequence)
);
}
/**
* Feature 1: Execution Time Variance (0.0-1.0)
*
* Measures stability of execution times.
* Higher values indicate inconsistent execution (possible performance issues).
*
* Calculation:
* - Coefficient of variation (CV) = standard_deviation / mean
* - Normalized using sigmoid: 1 / (1 + exp(-k * (CV - threshold)))
*/
private function extractExecutionTimeVariance(JobExecutionSequence $sequence): float
{
if ($sequence->count() < 2) {
return 0.0;
}
$mean = $sequence->getAverageExecutionTime();
if ($mean === 0.0) {
return 0.0;
}
$variance = $sequence->getExecutionTimeVariance();
$stdDev = sqrt($variance);
// Coefficient of Variation
$cv = $stdDev / $mean;
// Normalize using sigmoid (threshold at CV = 0.5)
return $this->normalize($cv, 0.0, 2.0);
}
/**
* Feature 2: Memory Usage Pattern (0.0-1.0)
*
* Detects anomalous memory consumption patterns.
* Higher values indicate unusual memory behavior.
*
* Calculation:
* - Memory variance relative to average
* - Combined with memory growth trend
*/
private function extractMemoryUsagePattern(JobExecutionSequence $sequence): float
{
if ($sequence->count() < 2) {
return 0.0;
}
$mean = $sequence->getAverageMemoryUsage();
if ($mean === 0.0) {
return 0.0;
}
$variance = $sequence->getMemoryUsageVariance();
$stdDev = sqrt($variance);
// Coefficient of Variation for memory
$cv = $stdDev / $mean;
// Check for memory growth trend (leak indicator)
$growthTrend = $this->calculateMemoryGrowthTrend($sequence);
// Combine CV and growth trend
$memoryScore = ($cv * 0.6) + ($growthTrend * 0.4);
return $this->normalize($memoryScore, 0.0, 1.5);
}
/**
* Feature 3: Retry Frequency (0.0-1.0)
*
* Measures how often jobs are retried.
* Higher values indicate reliability issues.
*/
private function extractRetryFrequency(JobExecutionSequence $sequence): float
{
return $sequence->getAverageRetryRate();
}
/**
* Feature 4: Failure Rate (0.0-1.0)
*
* Percentage of failed job executions.
* Direct indicator of job quality/reliability.
*/
private function extractFailureRate(JobExecutionSequence $sequence): float
{
return $sequence->getFailureRate();
}
/**
* Feature 5: Queue Depth Correlation (0.0-1.0)
*
* Measures correlation between queue depth and execution time.
* Higher values indicate performance degradation under load.
*
* Calculation:
* - Pearson correlation between queue depth and execution time
* - Normalized to 0-1 (abs correlation)
*/
private function extractQueueDepthCorrelation(JobExecutionSequence $sequence): float
{
if ($sequence->count() < 3) {
return 0.0;
}
$executions = $sequence->all();
// Extract queue depths and execution times
$queueDepths = [];
$executionTimes = [];
foreach ($executions as $exec) {
$queueDepths[] = (float) $exec->queueDepth;
$executionTimes[] = $exec->executionTimeMs;
}
// Calculate Pearson correlation
$correlation = $this->calculatePearsonCorrelation($queueDepths, $executionTimes);
// Return absolute correlation (0-1)
return abs($correlation);
}
/**
* Feature 6: Dependency Chain Complexity (0.0-1.0)
*
* Estimates complexity of job dependencies.
* Higher values indicate complex execution patterns.
*
* Heuristic based on:
* - Number of unique queues (more queues = more complex dependencies)
* - Execution frequency variation
*/
private function extractDependencyChainComplexity(JobExecutionSequence $sequence): float
{
if ($sequence->isEmpty()) {
return 0.0;
}
$uniqueQueues = count($sequence->getUniqueQueues());
// More queues = potentially more complex dependencies
$queueComplexity = $this->normalize((float) $uniqueQueues, 1.0, 10.0);
// Execution frequency variation as complexity indicator
$frequencyVariation = $this->calculateExecutionFrequencyVariation($sequence);
// Combine indicators
return ($queueComplexity * 0.5) + ($frequencyVariation * 0.5);
}
/**
* Feature 7: Payload Size Anomaly (0.0-1.0)
*
* Detects unusual payload sizes.
* Higher values indicate data anomalies.
*
* Calculation:
* - Coefficient of variation for payload sizes
* - Outlier detection using IQR method
*/
private function extractPayloadSizeAnomaly(JobExecutionSequence $sequence): float
{
if ($sequence->count() < 3) {
return 0.0;
}
$executions = $sequence->all();
// Extract payload sizes
$payloadSizes = array_map(
fn($exec) => (float) $exec->payloadSizeBytes,
$executions
);
// Filter out zeros
$payloadSizes = array_filter($payloadSizes, fn($size) => $size > 0);
if (empty($payloadSizes)) {
return 0.0;
}
// Calculate CV
$mean = array_sum($payloadSizes) / count($payloadSizes);
if ($mean === 0.0) {
return 0.0;
}
$variance = 0.0;
foreach ($payloadSizes as $size) {
$variance += ($size - $mean) ** 2;
}
$variance /= count($payloadSizes);
$stdDev = sqrt($variance);
$cv = $stdDev / $mean;
// Normalize CV
return $this->normalize($cv, 0.0, 2.0);
}
/**
* Feature 8: Execution Timing Regularity (0.0-1.0)
*
* Measures consistency of execution timing intervals.
* Higher values indicate very regular patterns (possible automated/bot execution).
*
* Calculation:
* - Calculate variance of inter-execution times
* - Lower variance = higher regularity
*/
private function extractExecutionTimingRegularity(JobExecutionSequence $sequence): float
{
if ($sequence->count() < 3) {
return 0.0;
}
$executions = $sequence->all();
// Calculate inter-execution times
$intervals = [];
for ($i = 1; $i < count($executions); $i++) {
$prev = $executions[$i - 1];
$curr = $executions[$i];
$interval = $curr->createdAt->toFloat() - $prev->createdAt->toFloat();
$intervals[] = $interval;
}
if (empty($intervals)) {
return 0.0;
}
// Calculate variance of intervals
$mean = array_sum($intervals) / count($intervals);
if ($mean === 0.0) {
return 0.0;
}
$variance = 0.0;
foreach ($intervals as $interval) {
$variance += ($interval - $mean) ** 2;
}
$variance /= count($intervals);
// Coefficient of variation
$stdDev = sqrt($variance);
$cv = $stdDev / $mean;
// Invert CV: low variance = high regularity
// Perfect regularity (CV = 0) → 1.0
// High variance (CV > 1) → 0.0
return max(0.0, 1.0 - $cv);
}
/**
* Calculate memory growth trend
*/
private function calculateMemoryGrowthTrend(JobExecutionSequence $sequence): float
{
$executions = $sequence->all();
if (count($executions) < 3) {
return 0.0;
}
// Simple linear regression slope for memory usage
$n = count($executions);
$sumX = 0;
$sumY = 0;
$sumXY = 0;
$sumX2 = 0;
foreach ($executions as $i => $exec) {
$x = (float) $i;
$y = (float) $exec->memoryUsageBytes;
$sumX += $x;
$sumY += $y;
$sumXY += $x * $y;
$sumX2 += $x * $x;
}
$denominator = ($n * $sumX2) - ($sumX * $sumX);
if ($denominator === 0.0) {
return 0.0;
}
$slope = (($n * $sumXY) - ($sumX * $sumY)) / $denominator;
// Normalize slope relative to average memory
$avgMemory = $sequence->getAverageMemoryUsage();
if ($avgMemory === 0.0) {
return 0.0;
}
$normalizedSlope = abs($slope) / $avgMemory;
return $this->normalize($normalizedSlope, 0.0, 0.5);
}
/**
* Calculate Pearson correlation coefficient
*/
private function calculatePearsonCorrelation(array $x, array $y): float
{
$n = count($x);
if ($n === 0 || count($y) !== $n) {
return 0.0;
}
$sumX = array_sum($x);
$sumY = array_sum($y);
$sumXY = 0;
$sumX2 = 0;
$sumY2 = 0;
for ($i = 0; $i < $n; $i++) {
$sumXY += $x[$i] * $y[$i];
$sumX2 += $x[$i] * $x[$i];
$sumY2 += $y[$i] * $y[$i];
}
$numerator = ($n * $sumXY) - ($sumX * $sumY);
$denominator = sqrt((($n * $sumX2) - ($sumX * $sumX)) * (($n * $sumY2) - ($sumY * $sumY)));
if ($denominator === 0.0) {
return 0.0;
}
return $numerator / $denominator;
}
/**
* Calculate execution frequency variation
*/
private function calculateExecutionFrequencyVariation(JobExecutionSequence $sequence): float
{
if ($sequence->count() < 3) {
return 0.0;
}
$executions = $sequence->all();
// Calculate inter-execution times
$intervals = [];
for ($i = 1; $i < count($executions); $i++) {
$prev = $executions[$i - 1];
$curr = $executions[$i];
$interval = $curr->createdAt->toFloat() - $prev->createdAt->toFloat();
$intervals[] = $interval;
}
if (empty($intervals)) {
return 0.0;
}
// Calculate CV
$mean = array_sum($intervals) / count($intervals);
if ($mean === 0.0) {
return 0.0;
}
$variance = 0.0;
foreach ($intervals as $interval) {
$variance += ($interval - $mean) ** 2;
}
$variance /= count($intervals);
$stdDev = sqrt($variance);
$cv = $stdDev / $mean;
return $this->normalize($cv, 0.0, 2.0);
}
/**
* Normalize value to 0.0-1.0 range
*/
private function normalize(float $value, float $min, float $max): float
{
if ($max === $min) {
return 0.0;
}
$normalized = ($value - $min) / ($max - $min);
return max(0.0, min(1.0, $normalized));
}
}

View File

@@ -0,0 +1,424 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\MachineLearning;
use App\Framework\Queue\MachineLearning\ValueObjects\JobExecutionContext;
use App\Framework\Queue\MachineLearning\ValueObjects\JobExecutionSequence;
use App\Framework\Queue\MachineLearning\ValueObjects\JobFeatures;
use App\Framework\Queue\MachineLearning\ValueObjects\JobAnomalyResult;
use App\Framework\Queue\Services\JobMetricsManagerInterface;
use App\Framework\Queue\ValueObjects\JobMetrics;
use App\Framework\Core\ValueObjects\Duration;
/**
* Job History Analyzer - Historical Job Pattern Analysis for Anomaly Detection
*
* Coordinates the ML-based job anomaly detection pipeline by:
* 1. Fetching job execution history from JobMetricsManager
* 2. Converting to JobExecutionSequence
* 3. Extracting behavioral features
* 4. Detecting anomalies using statistical and heuristic methods
*
* Analysis Capabilities:
* - Single job analysis (job-specific patterns)
* - Queue-wide analysis (queue health monitoring)
* - Time-window analysis (temporal pattern detection)
* - Batch analysis (multiple jobs/queues simultaneously)
*
* Integration:
* - Uses existing JobMetricsManager for data access
* - Leverages JobFeatureExtractor for feature engineering
* - Employs JobAnomalyDetector for anomaly classification
*/
final readonly class JobHistoryAnalyzer
{
public function __construct(
private JobMetricsManagerInterface $metricsManager,
private JobFeatureExtractor $featureExtractor,
private JobAnomalyDetector $anomalyDetector
) {}
/**
* Analyze job execution history for specific job
*
* @param string $jobId Job identifier
* @param Duration $timeWindow Analysis time window (default: last 1 hour)
*/
public function analyzeJob(string $jobId, ?Duration $timeWindow = null): JobAnomalyResult
{
$timeWindow ??= Duration::fromHours(1);
// Fetch job metrics
$metrics = $this->metricsManager->getJobMetrics($jobId);
if ($metrics === null) {
return JobAnomalyResult::normal('Job not found in metrics database');
}
// Create sequence from single job (repeated executions if exists)
$sequence = $this->buildSequenceFromJobMetrics([$metrics]);
if ($sequence->isEmpty()) {
return JobAnomalyResult::lowConfidence(
new \App\Framework\Core\ValueObjects\Score(0),
[],
'Insufficient job execution history'
);
}
// Extract features and detect anomalies
return $this->performAnalysis($sequence);
}
/**
* Analyze all jobs in a queue for anomalies
*
* @param string $queueName Queue name to analyze
* @param Duration $timeWindow Analysis time window (default: last 1 hour)
*/
public function analyzeQueue(string $queueName, ?Duration $timeWindow = null): JobAnomalyResult
{
$timeWindow ??= Duration::fromHours(1);
// Get queue performance stats (includes all jobs in time window)
$stats = $this->metricsManager->getPerformanceStats($queueName, $this->durationToTimeWindow($timeWindow));
if (empty($stats)) {
return JobAnomalyResult::normal('No queue metrics available');
}
// Build execution sequence from queue stats
$sequence = $this->buildSequenceFromQueueStats($queueName, $timeWindow);
if ($sequence->isEmpty()) {
return JobAnomalyResult::lowConfidence(
new \App\Framework\Core\ValueObjects\Score(0),
[],
'Insufficient queue execution history'
);
}
// Extract features and detect anomalies
return $this->performAnalysis($sequence);
}
/**
* Analyze failed jobs in a queue
*
* @param string|null $queueName Queue name (null = all queues)
* @param Duration $timeWindow Analysis time window (default: last 1 hour)
*/
public function analyzeFailedJobs(?string $queueName = null, ?Duration $timeWindow = null): JobAnomalyResult
{
$timeWindow ??= Duration::fromHours(1);
// Get failed jobs
$failedJobs = $this->metricsManager->getFailedJobs($queueName, $this->durationToTimeWindow($timeWindow));
if (empty($failedJobs)) {
return JobAnomalyResult::normal('No failed jobs in time window');
}
// Convert to execution sequence
$sequence = $this->buildSequenceFromJobMetrics($failedJobs);
if ($sequence->isEmpty()) {
return JobAnomalyResult::lowConfidence(
new \App\Framework\Core\ValueObjects\Score(0),
[],
'Insufficient failed job data'
);
}
// Extract features and detect anomalies
return $this->performAnalysis($sequence);
}
/**
* Batch analyze multiple queues
*
* @param array<string> $queueNames Queue names to analyze
* @param Duration $timeWindow Analysis time window
* @return array<string, JobAnomalyResult>
*/
public function analyzeMultipleQueues(array $queueNames, ?Duration $timeWindow = null): array
{
$timeWindow ??= Duration::fromHours(1);
$results = [];
foreach ($queueNames as $queueName) {
$results[$queueName] = $this->analyzeQueue($queueName, $timeWindow);
}
return $results;
}
/**
* Get queue health summary
*
* Analyzes queue and provides actionable health insights.
*
* @return array{
* queue_name: string,
* health_status: string,
* anomaly_result: JobAnomalyResult,
* metrics_summary: array,
* recommendations: array<string>
* }
*/
public function getQueueHealthSummary(string $queueName, ?Duration $timeWindow = null): array
{
$timeWindow ??= Duration::fromHours(1);
// Analyze queue for anomalies
$anomalyResult = $this->analyzeQueue($queueName, $timeWindow);
// Get queue metrics for summary
$queueMetrics = $this->metricsManager->getQueueMetrics($queueName, $this->durationToTimeWindow($timeWindow));
// Determine health status
$healthStatus = $this->determineHealthStatus($anomalyResult);
// Generate recommendations
$recommendations = $this->generateRecommendations($anomalyResult, $queueMetrics);
return [
'queue_name' => $queueName,
'health_status' => $healthStatus,
'anomaly_result' => $anomalyResult,
'metrics_summary' => [
'total_jobs' => $queueMetrics->getTotalJobs(),
'completion_rate' => $queueMetrics->getCompletionRate()->getValue(),
'average_execution_time' => $queueMetrics->getAverageExecutionTime(),
'peak_memory_usage' => $queueMetrics->getPeakMemoryUsage()
],
'recommendations' => $recommendations
];
}
/**
* Core analysis pipeline: Extract features → Detect anomalies
*/
private function performAnalysis(JobExecutionSequence $sequence): JobAnomalyResult
{
// Extract behavioral features
$features = $this->featureExtractor->extract($sequence);
// Detect anomalies
return $this->anomalyDetector->detect($features);
}
/**
* Build JobExecutionSequence from JobMetrics array
*
* @param array<JobMetrics> $jobMetricsList
*/
private function buildSequenceFromJobMetrics(array $jobMetricsList): JobExecutionSequence
{
if (empty($jobMetricsList)) {
return JobExecutionSequence::empty();
}
$executions = [];
foreach ($jobMetricsList as $metrics) {
// Convert JobMetrics to JobExecutionContext
$executions[] = JobExecutionContext::fromJobMetrics($metrics);
}
return JobExecutionSequence::fromExecutions($executions);
}
/**
* Build JobExecutionSequence from queue statistics
*
* Note: This is a heuristic approach when individual job metrics aren't available.
* For detailed analysis, use job-level metrics.
*/
private function buildSequenceFromQueueStats(string $queueName, Duration $timeWindow): JobExecutionSequence
{
// Get queue metrics
$queueMetrics = $this->metricsManager->getQueueMetrics($queueName, $this->durationToTimeWindow($timeWindow));
// For queue-wide analysis, we aggregate all jobs executed in the time window
// This is less granular than job-level analysis but useful for queue health monitoring
// Fetch performance stats which includes job-level details
$performanceStats = $this->metricsManager->getPerformanceStats($queueName, $this->durationToTimeWindow($timeWindow));
if (empty($performanceStats)) {
return JobExecutionSequence::empty();
}
// Extract job executions from performance stats
// Performance stats typically include: execution_times, memory_usage, failure_count, etc.
$executions = [];
// If performance stats contain individual job data, convert to JobExecutionContext
// Otherwise, create synthetic executions from aggregated data
if (isset($performanceStats['individual_jobs'])) {
foreach ($performanceStats['individual_jobs'] as $jobData) {
$executions[] = $this->createExecutionContextFromArray($jobData);
}
} else {
// Fallback: Create synthetic execution context from aggregated stats
$executions[] = $this->createSyntheticExecutionContext($queueName, $queueMetrics, $performanceStats);
}
return JobExecutionSequence::fromExecutions($executions);
}
/**
* Create JobExecutionContext from array data
*/
private function createExecutionContextFromArray(array $data): JobExecutionContext
{
return new JobExecutionContext(
jobId: $data['job_id'] ?? 'unknown',
queueName: $data['queue_name'] ?? 'unknown',
status: $data['status'] ?? 'completed',
attempts: $data['attempts'] ?? 1,
maxAttempts: $data['max_attempts'] ?? 3,
executionTimeMs: $data['execution_time_ms'] ?? 0.0,
memoryUsageBytes: $data['memory_usage_bytes'] ?? 0,
errorMessage: $data['error_message'] ?? null,
createdAt: \App\Framework\Core\ValueObjects\Timestamp::fromString($data['created_at'] ?? 'now'),
startedAt: isset($data['started_at']) ? \App\Framework\Core\ValueObjects\Timestamp::fromString($data['started_at']) : null,
completedAt: isset($data['completed_at']) ? \App\Framework\Core\ValueObjects\Timestamp::fromString($data['completed_at']) : null,
failedAt: isset($data['failed_at']) ? \App\Framework\Core\ValueObjects\Timestamp::fromString($data['failed_at']) : null,
queueDepth: $data['queue_depth'] ?? 0,
payloadSizeBytes: $data['payload_size_bytes'] ?? 0,
metadata: $data['metadata'] ?? []
);
}
/**
* Create synthetic JobExecutionContext from aggregated queue metrics
*
* Used when individual job data is not available.
*/
private function createSyntheticExecutionContext(
string $queueName,
\App\Framework\Queue\ValueObjects\QueueMetrics $queueMetrics,
array $performanceStats
): JobExecutionContext {
$completionRate = $queueMetrics->getCompletionRate()->getValue();
$status = $completionRate > 80 ? 'completed' : 'failed';
return new JobExecutionContext(
jobId: 'aggregated',
queueName: $queueName,
status: $status,
attempts: 1,
maxAttempts: 3,
executionTimeMs: $queueMetrics->getAverageExecutionTime(),
memoryUsageBytes: $queueMetrics->getPeakMemoryUsage(),
errorMessage: $status === 'failed' ? 'Aggregated failure data' : null,
createdAt: \App\Framework\Core\ValueObjects\Timestamp::now(),
startedAt: \App\Framework\Core\ValueObjects\Timestamp::now(),
completedAt: $status === 'completed' ? \App\Framework\Core\ValueObjects\Timestamp::now() : null,
failedAt: $status === 'failed' ? \App\Framework\Core\ValueObjects\Timestamp::now() : null,
queueDepth: $performanceStats['queue_depth'] ?? 0,
payloadSizeBytes: $performanceStats['avg_payload_size'] ?? 0,
metadata: [
'synthetic' => true,
'total_jobs' => $queueMetrics->getTotalJobs(),
'completion_rate' => $completionRate
]
);
}
/**
* Determine health status from anomaly result
*/
private function determineHealthStatus(JobAnomalyResult $result): string
{
if (!$result->isAnomalous) {
return 'healthy';
}
return match ($result->getSeverity()) {
'critical' => 'critical',
'high' => 'degraded',
'medium' => 'warning',
default => 'monitoring'
};
}
/**
* Generate actionable recommendations based on analysis
*
* @return array<string>
*/
private function generateRecommendations(
JobAnomalyResult $result,
\App\Framework\Queue\ValueObjects\QueueMetrics $queueMetrics
): array {
$recommendations = [];
if (!$result->isAnomalous) {
$recommendations[] = 'Queue is healthy - continue monitoring';
return $recommendations;
}
// Pattern-specific recommendations
if ($result->hasPattern('high_failure_risk')) {
$recommendations[] = 'Investigate job logic and error handling';
$recommendations[] = 'Review failed job error messages for patterns';
$recommendations[] = 'Consider implementing circuit breaker pattern';
}
if ($result->hasPattern('performance_degradation')) {
$recommendations[] = 'Analyze job execution bottlenecks';
$recommendations[] = 'Check for N+1 query problems or inefficient algorithms';
$recommendations[] = 'Consider job payload optimization';
}
if ($result->hasPattern('resource_exhaustion')) {
$recommendations[] = 'Scale infrastructure or optimize job resource usage';
$recommendations[] = 'Implement job batching to reduce queue pressure';
$recommendations[] = 'Monitor worker capacity and scaling policies';
}
if ($result->hasPattern('automated_execution')) {
$recommendations[] = 'Verify job submission source and authentication';
$recommendations[] = 'Implement rate limiting for job submission endpoints';
$recommendations[] = 'Review API access logs for suspicious patterns';
}
if ($result->hasPattern('data_processing_anomaly')) {
$recommendations[] = 'Validate job payload structure and size constraints';
$recommendations[] = 'Implement payload sanitization and validation';
$recommendations[] = 'Review data sources for integrity issues';
}
// Queue metrics-based recommendations
$completionRate = $queueMetrics->getCompletionRate()->getValue();
if ($completionRate < 50) {
$recommendations[] = 'Critical: Completion rate below 50% - immediate investigation required';
}
return $recommendations;
}
/**
* Convert Duration to time window string
*/
private function durationToTimeWindow(Duration $duration): string
{
$seconds = $duration->toSeconds();
if ($seconds >= 86400) {
return (int)($seconds / 86400) . 'days';
}
if ($seconds >= 3600) {
return (int)($seconds / 3600) . 'hours';
}
return (int)($seconds / 60) . 'minutes';
}
}

View File

@@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\MachineLearning;
use App\Framework\MachineLearning\ModelManagement\ModelRegistry;
use App\Framework\MachineLearning\ModelManagement\ModelPerformanceMonitor;
use App\Framework\MachineLearning\ModelManagement\ValueObjects\ModelMetadata;
use App\Framework\Core\ValueObjects\Version;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Queue\MachineLearning\ValueObjects\JobFeatures;
use App\Framework\Queue\MachineLearning\ValueObjects\JobAnomalyResult;
/**
* Queue Job Anomaly Detection Model Management Adapter
*
* Integrates JobAnomalyDetector with the ML Model Management System:
* - Automatic model registration
* - Real-time performance tracking
* - Prediction monitoring
* - Configuration management
*
* Usage:
* ```php
* $adapter = new QueueAnomalyModelAdapter($registry, $performanceMonitor, $detector);
*
* // Register current model version
* $adapter->registerCurrentModel();
*
* // Analyze with tracking
* $result = $adapter->analyzeWithTracking($features, $groundTruth);
* ```
*/
final readonly class QueueAnomalyModelAdapter
{
private const MODEL_NAME = 'queue-anomaly';
private const CURRENT_VERSION = '1.0.0';
public function __construct(
private ModelRegistry $registry,
private ModelPerformanceMonitor $performanceMonitor,
private JobAnomalyDetector $detector
) {}
/**
* Register current queue anomaly model in registry
*/
public function registerCurrentModel(?array $performanceMetrics = null): ModelMetadata
{
$version = Version::fromString(self::CURRENT_VERSION);
// Check if already registered
if ($this->registry->exists(self::MODEL_NAME, $version)) {
return $this->registry->get(self::MODEL_NAME, $version);
}
// Create metadata
$metadata = ModelMetadata::forQueueAnomaly(
version: $version,
configuration: $this->detector->getConfiguration()
);
// Add performance metrics if provided
if ($performanceMetrics !== null) {
$metadata = $metadata->withPerformanceMetrics($performanceMetrics);
}
// Register in registry
$this->registry->register($metadata);
return $metadata;
}
/**
* Analyze job features with automatic performance tracking
*
* @param JobFeatures $features Job execution features
* @param bool|null $groundTruth Ground truth (if known) - true if job is anomalous
*
* @return array Analysis result with tracking info
*/
public function analyzeWithTracking(
JobFeatures $features,
?bool $groundTruth = null
): array {
// Perform ML analysis
$analysisResult = $this->detector->detect($features);
// Determine prediction
$prediction = $analysisResult->isAnomalous;
$confidence = $analysisResult->anomalyScore->getValue() / 100.0; // Convert 0-100 to 0.0-1.0
// Track prediction in performance monitor
$this->performanceMonitor->trackPrediction(
modelName: self::MODEL_NAME,
version: Version::fromString(self::CURRENT_VERSION),
prediction: $prediction,
actual: $groundTruth,
confidence: $confidence,
features: $this->extractFeatureSummary($analysisResult)
);
// Convert result to array format
$resultArray = [
'is_anomalous' => $analysisResult->isAnomalous,
'anomaly_score' => $analysisResult->anomalyScore->getValue(),
'feature_scores' => array_map(
fn($score) => $score->getValue(),
$analysisResult->featureScores
),
'detected_patterns' => $analysisResult->detectedPatterns,
'primary_indicator' => $analysisResult->primaryIndicator,
'success' => true
];
// Add tracking info
$resultArray['tracking'] = [
'model_name' => self::MODEL_NAME,
'model_version' => self::CURRENT_VERSION,
'prediction' => $prediction ? 'anomalous' : 'normal',
'ground_truth' => $groundTruth,
'tracked' => true,
];
return $resultArray;
}
/**
* Get current model performance metrics
*/
public function getCurrentPerformanceMetrics(): array
{
return $this->performanceMonitor->getCurrentMetrics(
self::MODEL_NAME,
Version::fromString(self::CURRENT_VERSION)
);
}
/**
* Check if model performance has degraded
*/
public function checkPerformanceDegradation(float $thresholdPercent = 0.05): array
{
return $this->performanceMonitor->getPerformanceDegradationInfo(
self::MODEL_NAME,
Version::fromString(self::CURRENT_VERSION),
$thresholdPercent
);
}
/**
* Update model configuration in registry
*/
public function updateConfiguration(array $newConfiguration): void
{
$version = Version::fromString(self::CURRENT_VERSION);
$metadata = $this->registry->get(self::MODEL_NAME, $version);
if ($metadata === null) {
throw new \RuntimeException(
'Model not registered. Call registerCurrentModel() first.'
);
}
$updated = $metadata->withConfiguration($newConfiguration);
$this->registry->update($updated);
}
/**
* Deploy current model to production
*/
public function deployToProduction(): void
{
$version = Version::fromString(self::CURRENT_VERSION);
$metadata = $this->registry->get(self::MODEL_NAME, $version);
if ($metadata === null) {
throw new \RuntimeException(
'Model not registered. Call registerCurrentModel() first.'
);
}
$deployed = $metadata->withDeployment(
environment: 'production',
deployedAt: Timestamp::now()
);
$this->registry->update($deployed);
}
/**
* Get model metadata
*/
public function getModelMetadata(): ?ModelMetadata
{
return $this->registry->get(
self::MODEL_NAME,
Version::fromString(self::CURRENT_VERSION)
);
}
/**
* Extract feature summary for tracking
*/
private function extractFeatureSummary(JobAnomalyResult $result): array
{
return [
'feature_count' => count($result->featureScores),
'pattern_count' => count($result->detectedPatterns),
'primary_indicator' => $result->primaryIndicator,
'is_anomalous' => $result->isAnomalous,
];
}
}

View File

@@ -0,0 +1,360 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\MachineLearning\ValueObjects;
use App\Framework\Core\ValueObjects\Score;
/**
* Job Anomaly Detection Result - ML-Based Job Behavior Analysis Result
*
* Immutable value object containing job anomaly detection results
* using framework's Core Score for confidence scoring.
*
* Features:
* - Core Score integration for anomaly confidence (0.0-1.0)
* - Pattern-based anomaly classification
* - Feature contribution analysis
* - Severity assessment and recommended actions
* - Detailed metadata for investigation
*
* Used by: JobAnomalyDetector for unified anomaly detection results
*/
final readonly class JobAnomalyResult
{
/**
* @param Score $anomalyScore Framework Core Score (0.0-1.0) representing anomaly confidence
* @param bool $isAnomalous Whether the job execution is classified as anomalous
* @param array<string, Score> $featureScores Individual feature contribution scores
* @param array<array{type: string, confidence: Score, description: string}> $detectedPatterns Detected anomaly patterns
* @param string $primaryIndicator Primary feature indicating anomaly
* @param array<string, mixed> $metadata Additional context and debugging information
*/
public function __construct(
public Score $anomalyScore,
public bool $isAnomalous,
public array $featureScores,
public array $detectedPatterns,
public string $primaryIndicator,
public array $metadata = []
) {}
/**
* Create result for normal (non-anomalous) job execution
*/
public static function normal(string $reason): self
{
return new self(
anomalyScore: Score::zero(),
isAnomalous: false,
featureScores: [],
detectedPatterns: [],
primaryIndicator: 'none',
metadata: ['reason' => $reason]
);
}
/**
* Create result for low confidence detection (inconclusive)
*/
public static function lowConfidence(
Score $score,
array $featureScores,
string $reason = 'Insufficient data or low confidence'
): self {
return new self(
anomalyScore: $score,
isAnomalous: false,
featureScores: $featureScores,
detectedPatterns: [],
primaryIndicator: 'unknown',
metadata: [
'reason' => $reason,
'confidence_level' => 'low'
]
);
}
/**
* Create result for detected anomaly
*
* @param Score $score Overall anomaly confidence score
* @param array<string, Score> $featureScores Individual feature scores
* @param array<array{type: string, confidence: Score, description: string}> $detectedPatterns Detected patterns
* @param string $primaryIndicator Primary anomaly indicator feature
*/
public static function anomalous(
Score $score,
array $featureScores,
array $detectedPatterns,
string $primaryIndicator
): self {
return new self(
anomalyScore: $score,
isAnomalous: true,
featureScores: $featureScores,
detectedPatterns: $detectedPatterns,
primaryIndicator: $primaryIndicator,
metadata: [
'detection_method' => 'statistical_and_heuristic',
'pattern_count' => count($detectedPatterns)
]
);
}
/**
* Get severity level based on anomaly score
*
* - critical: Score >= 0.8 (immediate action required)
* - high: Score >= 0.6 (investigate soon)
* - medium: Score >= 0.4 (monitor closely)
* - low: Score < 0.4 (awareness)
*/
public function getSeverity(): string
{
if (!$this->isAnomalous) {
return 'none';
}
return match (true) {
$this->anomalyScore->getValue() >= 80 => 'critical',
$this->anomalyScore->getValue() >= 60 => 'high',
$this->anomalyScore->getValue() >= 40 => 'medium',
default => 'low'
};
}
/**
* Get recommended action based on severity and patterns
*/
public function getRecommendedAction(): string
{
if (!$this->isAnomalous) {
return 'No action required';
}
$severity = $this->getSeverity();
// Pattern-specific recommendations
foreach ($this->detectedPatterns as $pattern) {
$type = $pattern['type'];
if ($type === 'high_failure_risk') {
return match ($severity) {
'critical' => 'Immediate investigation required - High failure rate with excessive retries',
'high' => 'Investigate job logic and error handling',
default => 'Monitor failure patterns and retry frequency'
};
}
if ($type === 'performance_degradation') {
return match ($severity) {
'critical' => 'Critical performance issue - Check for resource bottlenecks',
'high' => 'Investigate execution time variance and memory patterns',
default => 'Monitor performance metrics'
};
}
if ($type === 'resource_exhaustion') {
return match ($severity) {
'critical' => 'Resource exhaustion detected - Scale infrastructure or optimize jobs',
'high' => 'Review queue depth impact and memory usage',
default => 'Monitor resource utilization trends'
};
}
if ($type === 'automated_execution') {
return match ($severity) {
'critical' => 'Potential bot activity - Verify job submission source',
'high' => 'Investigate automated job submission patterns',
default => 'Monitor timing regularity'
};
}
if ($type === 'data_processing_anomaly') {
return match ($severity) {
'critical' => 'Data anomaly detected - Verify payload integrity and processing logic',
'high' => 'Investigate payload size variations and memory patterns',
default => 'Monitor data processing patterns'
};
}
}
// Generic recommendations by severity
return match ($severity) {
'critical' => 'Immediate investigation required',
'high' => 'Investigate anomaly patterns',
'medium' => 'Monitor closely',
default => 'Review and monitor'
};
}
/**
* Get top contributing features to the anomaly
*
* @param int $limit Maximum number of contributors to return
* @return array<array{feature: string, score: Score, contribution_percentage: float}>
*/
public function getTopContributors(int $limit = 3): array
{
if (empty($this->featureScores)) {
return [];
}
// Calculate total score for percentage calculation
$totalScore = array_reduce(
$this->featureScores,
fn(float $carry, Score $score) => $carry + $score->getValue(),
0.0
);
if ($totalScore === 0.0) {
return [];
}
// Sort features by score descending
$sorted = $this->featureScores;
uasort($sorted, fn(Score $a, Score $b) => $b->getValue() <=> $a->getValue());
// Take top N and calculate contribution percentages
$contributors = [];
$count = 0;
foreach ($sorted as $feature => $score) {
if ($count >= $limit) {
break;
}
$contributors[] = [
'feature' => $feature,
'score' => $score,
'contribution_percentage' => ($score->getValue() / $totalScore) * 100.0
];
$count++;
}
return $contributors;
}
/**
* Check if result has specific pattern type
*/
public function hasPattern(string $patternType): bool
{
foreach ($this->detectedPatterns as $pattern) {
if ($pattern['type'] === $patternType) {
return true;
}
}
return false;
}
/**
* Get pattern by type
*
* @return array{type: string, confidence: Score, description: string}|null
*/
public function getPattern(string $patternType): ?array
{
foreach ($this->detectedPatterns as $pattern) {
if ($pattern['type'] === $patternType) {
return $pattern;
}
}
return null;
}
/**
* Get all pattern types
*
* @return array<string>
*/
public function getPatternTypes(): array
{
return array_map(
fn(array $pattern) => $pattern['type'],
$this->detectedPatterns
);
}
/**
* Check if anomaly requires immediate attention
*/
public function requiresImmediateAttention(): bool
{
return $this->isAnomalous && $this->getSeverity() === 'critical';
}
/**
* Get confidence level as string
*/
public function getConfidenceLevel(): string
{
$value = $this->anomalyScore->getValue();
return match (true) {
$value >= 80 => 'very_high',
$value >= 60 => 'high',
$value >= 40 => 'medium',
$value >= 20 => 'low',
default => 'very_low'
};
}
/**
* Convert to array for serialization
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'anomaly_score' => $this->anomalyScore->getValue(),
'is_anomalous' => $this->isAnomalous,
'severity' => $this->getSeverity(),
'confidence_level' => $this->getConfidenceLevel(),
'primary_indicator' => $this->primaryIndicator,
'detected_patterns' => array_map(
fn(array $pattern) => [
'type' => $pattern['type'],
'confidence' => $pattern['confidence']->getValue(),
'description' => $pattern['description']
],
$this->detectedPatterns
),
'feature_scores' => array_map(
fn(Score $score) => $score->getValue(),
$this->featureScores
),
'top_contributors' => $this->getTopContributors(3),
'recommended_action' => $this->getRecommendedAction(),
'requires_immediate_attention' => $this->requiresImmediateAttention(),
'metadata' => $this->metadata
];
}
/**
* Create debugging representation
*/
public function __toString(): string
{
if (!$this->isAnomalous) {
return 'JobAnomalyResult[Normal]';
}
$score = $this->anomalyScore->getValue();
$severity = $this->getSeverity();
$patterns = implode(', ', $this->getPatternTypes());
return sprintf(
'JobAnomalyResult[Anomalous, Score=%.2f, Severity=%s, Patterns=%s]',
$score,
$severity,
$patterns ?: 'none'
);
}
}

View File

@@ -0,0 +1,257 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\MachineLearning\ValueObjects;
use App\Framework\Queue\ValueObjects\JobMetrics;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Core\ValueObjects\Duration;
/**
* Job Execution Context - Metadata for Job Analysis
*
* Immutable value object containing execution metadata
* for job anomaly detection analysis.
*
* Features:
* - Execution timing and performance metrics
* - Retry and failure information
* - Queue depth and workload context
* - Memory and resource usage patterns
*
* Used by: JobFeatureExtractor for behavioral analysis
*/
final readonly class JobExecutionContext
{
/**
* @param string $jobId Job identifier
* @param string $queueName Queue name
* @param string $status Current job status (pending, running, completed, failed)
* @param int $attempts Number of execution attempts
* @param int $maxAttempts Maximum allowed attempts
* @param float $executionTimeMs Execution time in milliseconds
* @param int $memoryUsageBytes Memory usage in bytes
* @param ?string $errorMessage Error message if failed
* @param Timestamp $createdAt Job creation timestamp
* @param ?Timestamp $startedAt Job start timestamp
* @param ?Timestamp $completedAt Job completion timestamp
* @param ?Timestamp $failedAt Job failure timestamp
* @param int $queueDepth Current queue depth (jobs waiting)
* @param int $payloadSizeBytes Estimated job payload size
* @param array $metadata Additional metadata
*/
public function __construct(
public string $jobId,
public string $queueName,
public string $status,
public int $attempts,
public int $maxAttempts,
public float $executionTimeMs,
public int $memoryUsageBytes,
public ?string $errorMessage,
public Timestamp $createdAt,
public ?Timestamp $startedAt,
public ?Timestamp $completedAt,
public ?Timestamp $failedAt,
public int $queueDepth = 0,
public int $payloadSizeBytes = 0,
public array $metadata = []
) {}
/**
* Create from JobMetrics value object
*/
public static function fromJobMetrics(
JobMetrics $metrics,
int $queueDepth = 0,
int $payloadSizeBytes = 0
): self {
return new self(
jobId: $metrics->jobId,
queueName: $metrics->queueName,
status: $metrics->status,
attempts: $metrics->attempts,
maxAttempts: $metrics->maxAttempts,
executionTimeMs: $metrics->executionTimeMs,
memoryUsageBytes: $metrics->memoryUsageBytes,
errorMessage: $metrics->errorMessage,
createdAt: Timestamp::fromString($metrics->createdAt),
startedAt: $metrics->startedAt ? Timestamp::fromString($metrics->startedAt) : null,
completedAt: $metrics->completedAt ? Timestamp::fromString($metrics->completedAt) : null,
failedAt: $metrics->failedAt ? Timestamp::fromString($metrics->failedAt) : null,
queueDepth: $queueDepth,
payloadSizeBytes: $payloadSizeBytes,
metadata: $metrics->metadata
);
}
/**
* Check if job completed successfully
*/
public function isCompleted(): bool
{
return $this->status === 'completed';
}
/**
* Check if job failed
*/
public function isFailed(): bool
{
return $this->status === 'failed';
}
/**
* Check if job is currently running
*/
public function isRunning(): bool
{
return $this->status === 'running';
}
/**
* Check if job is pending
*/
public function isPending(): bool
{
return $this->status === 'pending';
}
/**
* Check if job exceeded retry attempts
*/
public function hasExceededRetries(): bool
{
return $this->attempts >= $this->maxAttempts;
}
/**
* Get retry rate (attempts / maxAttempts)
*/
public function getRetryRate(): float
{
if ($this->maxAttempts === 0) {
return 0.0;
}
return min(1.0, $this->attempts / $this->maxAttempts);
}
/**
* Get execution duration
*/
public function getExecutionDuration(): ?Duration
{
if ($this->startedAt === null) {
return null;
}
$endTimestamp = $this->completedAt ?? $this->failedAt ?? Timestamp::now();
$durationSeconds = $endTimestamp->toFloat() - $this->startedAt->toFloat();
return Duration::fromSeconds((int) $durationSeconds);
}
/**
* Get queue time (time from creation to start)
*/
public function getQueueTime(): ?Duration
{
if ($this->startedAt === null) {
return null;
}
$queueTimeSeconds = $this->startedAt->toFloat() - $this->createdAt->toFloat();
return Duration::fromSeconds((int) $queueTimeSeconds);
}
/**
* Get execution time in seconds
*/
public function getExecutionTimeSeconds(): float
{
return $this->executionTimeMs / 1000.0;
}
/**
* Get memory usage in megabytes
*/
public function getMemoryUsageMB(): float
{
return $this->memoryUsageBytes / (1024 * 1024);
}
/**
* Get payload size in kilobytes
*/
public function getPayloadSizeKB(): float
{
return $this->payloadSizeBytes / 1024;
}
/**
* Check if job has error message
*/
public function hasError(): bool
{
return $this->errorMessage !== null;
}
/**
* Get metadata value by key
*/
public function getMetadata(string $key, mixed $default = null): mixed
{
return $this->metadata[$key] ?? $default;
}
/**
* Convert to array for serialization
*/
public function toArray(): array
{
return [
'job_id' => $this->jobId,
'queue_name' => $this->queueName,
'status' => $this->status,
'attempts' => $this->attempts,
'max_attempts' => $this->maxAttempts,
'execution_time_ms' => $this->executionTimeMs,
'execution_time_seconds' => $this->getExecutionTimeSeconds(),
'memory_usage_bytes' => $this->memoryUsageBytes,
'memory_usage_mb' => $this->getMemoryUsageMB(),
'error_message' => $this->errorMessage,
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
'started_at' => $this->startedAt?->format('Y-m-d H:i:s'),
'completed_at' => $this->completedAt?->format('Y-m-d H:i:s'),
'failed_at' => $this->failedAt?->format('Y-m-d H:i:s'),
'queue_depth' => $this->queueDepth,
'payload_size_bytes' => $this->payloadSizeBytes,
'payload_size_kb' => $this->getPayloadSizeKB(),
'retry_rate' => $this->getRetryRate(),
'execution_duration_seconds' => $this->getExecutionDuration()?->toSeconds(),
'queue_time_seconds' => $this->getQueueTime()?->toSeconds(),
'metadata' => $this->metadata,
];
}
/**
* Create debugging representation
*/
public function __toString(): string
{
return sprintf(
'JobExecution[%s, queue=%s, status=%s, attempts=%d/%d, time=%.2fms, memory=%.2fMB]',
$this->jobId,
$this->queueName,
$this->status,
$this->attempts,
$this->maxAttempts,
$this->executionTimeMs,
$this->getMemoryUsageMB()
);
}
}

View File

@@ -0,0 +1,396 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\MachineLearning\ValueObjects;
use App\Framework\Core\ValueObjects\Duration;
/**
* Job Execution Sequence - Collection of Job Executions
*
* Immutable collection of job execution contexts for
* behavioral pattern analysis and anomaly detection.
*
* Features:
* - Time-ordered job execution history
* - Statistical aggregations
* - Performance trend analysis
* - Failure pattern detection
*
* Used by: JobFeatureExtractor for sequence-based analysis
*/
final readonly class JobExecutionSequence
{
/** @param array<JobExecutionContext> $executions */
public function __construct(
private array $executions
) {}
/**
* Create from array of JobExecutionContexts
*
* @param array<JobExecutionContext> $executions
*/
public static function fromExecutions(array $executions): self
{
// Sort by created timestamp (oldest first)
$sorted = $executions;
usort($sorted, function (JobExecutionContext $a, JobExecutionContext $b) {
return $a->createdAt->toFloat() <=> $b->createdAt->toFloat();
});
return new self($sorted);
}
/**
* Create empty sequence
*/
public static function empty(): self
{
return new self([]);
}
/**
* Get number of executions in sequence
*/
public function count(): int
{
return count($this->executions);
}
/**
* Check if sequence is empty
*/
public function isEmpty(): bool
{
return empty($this->executions);
}
/**
* Get all executions
*
* @return array<JobExecutionContext>
*/
public function all(): array
{
return $this->executions;
}
/**
* Get execution by index
*/
public function get(int $index): ?JobExecutionContext
{
return $this->executions[$index] ?? null;
}
/**
* Get first execution
*/
public function first(): ?JobExecutionContext
{
return $this->executions[0] ?? null;
}
/**
* Get last execution
*/
public function last(): ?JobExecutionContext
{
$count = count($this->executions);
return $count > 0 ? $this->executions[$count - 1] : null;
}
/**
* Filter executions by status
*/
public function filterByStatus(string $status): self
{
$filtered = array_filter(
$this->executions,
fn(JobExecutionContext $exec) => $exec->status === $status
);
return new self(array_values($filtered));
}
/**
* Filter failed executions
*/
public function filterFailed(): self
{
return $this->filterByStatus('failed');
}
/**
* Filter completed executions
*/
public function filterCompleted(): self
{
return $this->filterByStatus('completed');
}
/**
* Filter executions within time window
*/
public function filterByTimeWindow(Duration $window): self
{
$now = time();
$windowSeconds = $window->toSeconds();
$filtered = array_filter(
$this->executions,
fn(JobExecutionContext $exec) => ($now - $exec->createdAt->toFloat()) <= $windowSeconds
);
return new self(array_values($filtered));
}
/**
* Filter executions by queue name
*/
public function filterByQueue(string $queueName): self
{
$filtered = array_filter(
$this->executions,
fn(JobExecutionContext $exec) => $exec->queueName === $queueName
);
return new self(array_values($filtered));
}
/**
* Get average execution time (milliseconds)
*/
public function getAverageExecutionTime(): float
{
if ($this->isEmpty()) {
return 0.0;
}
$total = array_sum(array_map(
fn(JobExecutionContext $exec) => $exec->executionTimeMs,
$this->executions
));
return $total / $this->count();
}
/**
* Get average memory usage (bytes)
*/
public function getAverageMemoryUsage(): float
{
if ($this->isEmpty()) {
return 0.0;
}
$total = array_sum(array_map(
fn(JobExecutionContext $exec) => $exec->memoryUsageBytes,
$this->executions
));
return $total / $this->count();
}
/**
* Get failure rate (0.0 - 1.0)
*/
public function getFailureRate(): float
{
if ($this->isEmpty()) {
return 0.0;
}
$failedCount = count($this->filterFailed()->executions);
return $failedCount / $this->count();
}
/**
* Get completion rate (0.0 - 1.0)
*/
public function getCompletionRate(): float
{
if ($this->isEmpty()) {
return 1.0;
}
$completedCount = count($this->filterCompleted()->executions);
return $completedCount / $this->count();
}
/**
* Get average retry rate
*/
public function getAverageRetryRate(): float
{
if ($this->isEmpty()) {
return 0.0;
}
$total = array_sum(array_map(
fn(JobExecutionContext $exec) => $exec->getRetryRate(),
$this->executions
));
return $total / $this->count();
}
/**
* Get execution time variance (for regularity detection)
*/
public function getExecutionTimeVariance(): float
{
if ($this->count() < 2) {
return 0.0;
}
$mean = $this->getAverageExecutionTime();
$squaredDifferences = array_map(
fn(JobExecutionContext $exec) => ($exec->executionTimeMs - $mean) ** 2,
$this->executions
);
return array_sum($squaredDifferences) / $this->count();
}
/**
* Get memory usage variance
*/
public function getMemoryUsageVariance(): float
{
if ($this->count() < 2) {
return 0.0;
}
$mean = $this->getAverageMemoryUsage();
$squaredDifferences = array_map(
fn(JobExecutionContext $exec) => ($exec->memoryUsageBytes - $mean) ** 2,
$this->executions
);
return array_sum($squaredDifferences) / $this->count();
}
/**
* Get unique queue names
*
* @return array<string>
*/
public function getUniqueQueues(): array
{
$queues = array_map(
fn(JobExecutionContext $exec) => $exec->queueName,
$this->executions
);
return array_values(array_unique($queues));
}
/**
* Get time span (duration from first to last execution)
*/
public function getTimeSpan(): ?Duration
{
if ($this->count() < 2) {
return null;
}
$first = $this->first();
$last = $this->last();
if ($first === null || $last === null) {
return null;
}
$spanSeconds = $last->createdAt->toFloat() - $first->createdAt->toFloat();
return Duration::fromSeconds((int) $spanSeconds);
}
/**
* Get execution frequency (executions per hour)
*/
public function getExecutionFrequency(): float
{
if ($this->isEmpty()) {
return 0.0;
}
$timeSpan = $this->getTimeSpan();
if ($timeSpan === null || $timeSpan->toSeconds() === 0) {
return 0.0;
}
$hours = $timeSpan->toSeconds() / 3600.0;
return $this->count() / $hours;
}
/**
* Get statistics summary
*/
public function getStatistics(): array
{
return [
'total_executions' => $this->count(),
'completed' => count($this->filterCompleted()->executions),
'failed' => count($this->filterFailed()->executions),
'completion_rate' => $this->getCompletionRate(),
'failure_rate' => $this->getFailureRate(),
'average_execution_time_ms' => $this->getAverageExecutionTime(),
'average_memory_usage_bytes' => $this->getAverageMemoryUsage(),
'average_memory_usage_mb' => $this->getAverageMemoryUsage() / (1024 * 1024),
'average_retry_rate' => $this->getAverageRetryRate(),
'execution_time_variance' => $this->getExecutionTimeVariance(),
'memory_usage_variance' => $this->getMemoryUsageVariance(),
'unique_queues' => count($this->getUniqueQueues()),
'time_span_seconds' => $this->getTimeSpan()?->toSeconds(),
'execution_frequency_per_hour' => $this->getExecutionFrequency(),
];
}
/**
* Merge with another sequence
*/
public function merge(self $other): self
{
$merged = array_merge($this->executions, $other->executions);
return self::fromExecutions($merged);
}
/**
* Take first N executions
*/
public function take(int $limit): self
{
return new self(array_slice($this->executions, 0, $limit));
}
/**
* Take last N executions
*/
public function takeLast(int $limit): self
{
return new self(array_slice($this->executions, -$limit));
}
/**
* Convert to array
*
* @return array<array>
*/
public function toArray(): array
{
return array_map(
fn(JobExecutionContext $exec) => $exec->toArray(),
$this->executions
);
}
}

View File

@@ -0,0 +1,271 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\MachineLearning\ValueObjects;
/**
* Job Features - 8-Dimensional Feature Vector for Job Anomaly Detection
*
* Extracted behavioral features from job execution sequences
* for machine learning-based anomaly detection.
*
* Features (all normalized to 0.0-1.0):
* 1. execution_time_variance - Variance in execution times (stability)
* 2. memory_usage_pattern - Memory consumption pattern (resource efficiency)
* 3. retry_frequency - How often jobs retry (reliability indicator)
* 4. failure_rate - Percentage of failed executions (quality indicator)
* 5. queue_depth_correlation - Job volume impact on performance
* 6. dependency_chain_complexity - Estimated execution dependencies
* 7. payload_size_anomaly - Unusual payload sizes (data anomaly)
* 8. execution_timing_regularity - Execution time consistency (bot detection)
*
* Immutable value object following framework patterns.
*/
final readonly class JobFeatures
{
/**
* @param float $executionTimeVariance Execution time variance (0.0-1.0, higher = more unstable)
* @param float $memoryUsagePattern Memory usage pattern score (0.0-1.0, higher = more anomalous)
* @param float $retryFrequency Retry frequency (0.0-1.0, higher = more retries)
* @param float $failureRate Failure rate (0.0-1.0)
* @param float $queueDepthCorrelation Queue depth impact on performance (0.0-1.0)
* @param float $dependencyChainComplexity Dependency complexity estimate (0.0-1.0)
* @param float $payloadSizeAnomaly Payload size anomaly score (0.0-1.0)
* @param float $executionTimingRegularity Execution timing regularity (0.0-1.0, higher = more regular/bot-like)
*/
public function __construct(
public float $executionTimeVariance,
public float $memoryUsagePattern,
public float $retryFrequency,
public float $failureRate,
public float $queueDepthCorrelation,
public float $dependencyChainComplexity,
public float $payloadSizeAnomaly,
public float $executionTimingRegularity
) {
// Validation: All features must be normalized (0.0-1.0)
foreach ($this->toArray() as $name => $value) {
if ($value < 0.0 || $value > 1.0) {
throw new \InvalidArgumentException(
sprintf('Feature "%s" must be normalized (0.0-1.0), got: %.4f', $name, $value)
);
}
}
}
/**
* Create features with all zeros (baseline)
*/
public static function zero(): self
{
return new self(
executionTimeVariance: 0.0,
memoryUsagePattern: 0.0,
retryFrequency: 0.0,
failureRate: 0.0,
queueDepthCorrelation: 0.0,
dependencyChainComplexity: 0.0,
payloadSizeAnomaly: 0.0,
executionTimingRegularity: 0.0
);
}
/**
* Get feature names in order
*
* @return array<string>
*/
public static function getFeatureNames(): array
{
return [
'execution_time_variance',
'memory_usage_pattern',
'retry_frequency',
'failure_rate',
'queue_depth_correlation',
'dependency_chain_complexity',
'payload_size_anomaly',
'execution_timing_regularity',
];
}
/**
* Convert to feature vector array
*
* @return array<float>
*/
public function toVector(): array
{
return [
$this->executionTimeVariance,
$this->memoryUsagePattern,
$this->retryFrequency,
$this->failureRate,
$this->queueDepthCorrelation,
$this->dependencyChainComplexity,
$this->payloadSizeAnomaly,
$this->executionTimingRegularity,
];
}
/**
* Convert to associative array
*
* @return array<string, float>
*/
public function toArray(): array
{
return [
'execution_time_variance' => $this->executionTimeVariance,
'memory_usage_pattern' => $this->memoryUsagePattern,
'retry_frequency' => $this->retryFrequency,
'failure_rate' => $this->failureRate,
'queue_depth_correlation' => $this->queueDepthCorrelation,
'dependency_chain_complexity' => $this->dependencyChainComplexity,
'payload_size_anomaly' => $this->payloadSizeAnomaly,
'execution_timing_regularity' => $this->executionTimingRegularity,
];
}
/**
* Calculate Euclidean distance to other feature vector
*/
public function distanceTo(self $other): float
{
$sumSquaredDifferences = 0.0;
$thisVector = $this->toVector();
$otherVector = $other->toVector();
for ($i = 0; $i < 8; $i++) {
$diff = $thisVector[$i] - $otherVector[$i];
$sumSquaredDifferences += $diff * $diff;
}
return sqrt($sumSquaredDifferences);
}
/**
* Calculate Manhattan distance to other feature vector
*/
public function manhattanDistanceTo(self $other): float
{
$sumAbsDifferences = 0.0;
$thisVector = $this->toVector();
$otherVector = $other->toVector();
for ($i = 0; $i < 8; $i++) {
$sumAbsDifferences += abs($thisVector[$i] - $otherVector[$i]);
}
return $sumAbsDifferences;
}
/**
* Check if features indicate high failure risk
* (heuristic: high failure rate + high retry frequency)
*/
public function indicatesHighFailureRisk(): bool
{
return $this->failureRate > 0.3 && $this->retryFrequency > 0.5;
}
/**
* Check if features indicate performance degradation
* (heuristic: high execution variance + memory pattern anomaly)
*/
public function indicatesPerformanceDegradation(): bool
{
return $this->executionTimeVariance > 0.6 && $this->memoryUsagePattern > 0.6;
}
/**
* Check if features indicate resource exhaustion
* (heuristic: high queue depth correlation + memory anomaly)
*/
public function indicatesResourceExhaustion(): bool
{
return $this->queueDepthCorrelation > 0.7 && $this->memoryUsagePattern > 0.7;
}
/**
* Check if features indicate automated/bot-like execution
* (heuristic: very high timing regularity + low variance)
*/
public function indicatesAutomatedExecution(): bool
{
return $this->executionTimingRegularity > 0.9 && $this->executionTimeVariance < 0.1;
}
/**
* Check if features indicate data processing anomaly
* (heuristic: high payload size anomaly + high memory pattern)
*/
public function indicatesDataProcessingAnomaly(): bool
{
return $this->payloadSizeAnomaly > 0.7 && $this->memoryUsagePattern > 0.6;
}
/**
* Get anomaly indicators as array
*
* @return array<string, bool>
*/
public function getAnomalyIndicators(): array
{
return [
'high_failure_risk' => $this->indicatesHighFailureRisk(),
'performance_degradation' => $this->indicatesPerformanceDegradation(),
'resource_exhaustion' => $this->indicatesResourceExhaustion(),
'automated_execution' => $this->indicatesAutomatedExecution(),
'data_processing_anomaly' => $this->indicatesDataProcessingAnomaly(),
];
}
/**
* Get highest feature value and name
*
* @return array{name: string, value: float}
*/
public function getHighestFeature(): array
{
$features = $this->toArray();
$maxValue = max($features);
$maxName = array_search($maxValue, $features, true);
return [
'name' => (string) $maxName,
'value' => $maxValue,
];
}
/**
* Get top N features by value
*
* @return array<string, float>
*/
public function getTopFeatures(int $n = 3): array
{
$features = $this->toArray();
arsort($features);
return array_slice($features, 0, $n, true);
}
/**
* Create debugging representation
*/
public function __toString(): string
{
$top = $this->getTopFeatures(3);
$topStr = implode(', ', array_map(
fn($name, $value) => sprintf('%s=%.2f', $name, $value),
array_keys($top),
$top
));
return sprintf('JobFeatures[%s]', $topStr);
}
}