Files
michaelschiemer/src/Framework/Discovery/Quality/DiscoveryQualityValidator.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

495 lines
17 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Quality;
use App\Framework\Core\ValueObjects\Score;
use App\Framework\DateTime\Clock;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\UnifiedDiscoveryService;
use App\Framework\Discovery\ValueObjects\DiscoveryContext;
use App\Framework\Logging\Logger;
/**
* Quality assurance validator for Discovery system
*
* Provides comprehensive quality checks including performance
* validation, memory efficiency analysis, cache effectiveness,
* and overall system health assessment.
*/
final class DiscoveryQualityValidator
{
// Quality thresholds
private const array PERFORMANCE_THRESHOLDS = [
'max_discovery_time_seconds' => 30,
'max_memory_usage_mb' => 256,
'min_cache_hit_rate' => 0.7,
'max_memory_pressure' => 0.8,
'min_items_per_second' => 10,
];
private const array QUALITY_WEIGHTS = [
'performance' => 0.3,
'memory_efficiency' => 0.25,
'cache_effectiveness' => 0.2,
'reliability' => 0.15,
'maintainability' => 0.1,
];
public function __construct(
private readonly Clock $clock,
private readonly ?Logger $logger = null
) {
}
/**
* Perform comprehensive quality validation
*/
public function validateQuality(
UnifiedDiscoveryService $service,
DiscoveryContext $context,
?DiscoveryRegistry $registry = null
): DiscoveryQualityReport {
$startTime = microtime(true);
$startMemory = memory_get_usage(true);
$this->logger?->info('Starting discovery quality validation', [
'context_key' => $context->getCacheKey(),
]);
// Performance validation
$performanceScore = $this->validatePerformance($service, $context, $registry);
// Memory efficiency validation
$memoryScore = $this->validateMemoryEfficiency($service, $startMemory);
// Cache effectiveness validation
$cacheScore = $this->validateCacheEffectiveness($service);
// Reliability validation
$reliabilityScore = $this->validateReliability($service);
// Maintainability validation
$maintainabilityScore = $this->validateMaintainability($service, $registry);
// Calculate overall quality score
$overallScore = $this->calculateOverallScore([
'performance' => $performanceScore,
'memory_efficiency' => $memoryScore,
'cache_effectiveness' => $cacheScore,
'reliability' => $reliabilityScore,
'maintainability' => $maintainabilityScore,
]);
$validationTime = microtime(true) - $startTime;
$report = new DiscoveryQualityReport(
overallScore: $overallScore,
performanceScore: $performanceScore,
memoryScore: $memoryScore,
cacheScore: $cacheScore,
reliabilityScore: $reliabilityScore,
maintainabilityScore: $maintainabilityScore,
validationTime: $validationTime,
recommendations: $this->generateRecommendations($overallScore, [
'performance' => $performanceScore,
'memory_efficiency' => $memoryScore,
'cache_effectiveness' => $cacheScore,
'reliability' => $reliabilityScore,
'maintainability' => $maintainabilityScore,
]),
timestamp: $this->clock->now()
);
$this->logger?->info('Discovery quality validation completed', [
'overall_score' => $overallScore->toString(),
'validation_time' => $validationTime,
'quality_rating' => $report->getQualityRating(),
]);
return $report;
}
/**
* Validate performance characteristics
*/
private function validatePerformance(
UnifiedDiscoveryService $service,
DiscoveryContext $context,
?DiscoveryRegistry $registry
): Score {
$score = 100;
$issues = [];
try {
// Test discovery timing if registry not provided
if ($registry === null) {
$startTime = microtime(true);
$registry = $service->discoverWithOptions($context->options);
$discoveryTime = microtime(true) - $startTime;
} else {
$discoveryTime = 5.0; // Assume reasonable time if registry provided
}
// Check discovery time
if ($discoveryTime > self::PERFORMANCE_THRESHOLDS['max_discovery_time_seconds']) {
$penalty = min(30, ($discoveryTime - self::PERFORMANCE_THRESHOLDS['max_discovery_time_seconds']) * 2);
$score -= $penalty;
$issues[] = "Discovery took {$discoveryTime}s (threshold: " . self::PERFORMANCE_THRESHOLDS['max_discovery_time_seconds'] . "s)";
}
// Check throughput
$itemsCount = count($registry);
$itemsPerSecond = $itemsCount / max($discoveryTime, 0.1);
if ($itemsPerSecond < self::PERFORMANCE_THRESHOLDS['min_items_per_second']) {
$score -= 15;
$issues[] = "Low throughput: {$itemsPerSecond} items/s (threshold: " . self::PERFORMANCE_THRESHOLDS['min_items_per_second'] . ")";
}
// Check for performance bottlenecks
$healthStatus = $service->getHealthStatus();
if (isset($healthStatus['memory_management']['memory_pressure'])) {
$memoryPressure = (float) $healthStatus['memory_management']['memory_pressure'];
if ($memoryPressure > self::PERFORMANCE_THRESHOLDS['max_memory_pressure']) {
$score -= 20;
$issues[] = "High memory pressure: {$memoryPressure} (threshold: " . self::PERFORMANCE_THRESHOLDS['max_memory_pressure'] . ")";
}
}
} catch (\Throwable $e) {
$score -= 50;
$issues[] = "Performance test failed: " . $e->getMessage();
}
if (! empty($issues)) {
$this->logger?->warning('Performance validation issues detected', [
'issues' => $issues,
'score' => $score,
]);
}
return Score::fromRatio(max(0, $score), 100);
}
/**
* Validate memory efficiency
*/
private function validateMemoryEfficiency(UnifiedDiscoveryService $service, int $startMemory): Score
{
$score = 100;
$issues = [];
try {
$currentMemory = memory_get_usage(true);
$memoryUsed = $currentMemory - $startMemory;
$memoryUsedMB = $memoryUsed / 1024 / 1024;
// Check memory usage
if ($memoryUsedMB > self::PERFORMANCE_THRESHOLDS['max_memory_usage_mb']) {
$penalty = min(40, ($memoryUsedMB - self::PERFORMANCE_THRESHOLDS['max_memory_usage_mb']) / 10);
$score -= $penalty;
$issues[] = "High memory usage: {$memoryUsedMB}MB (threshold: " . self::PERFORMANCE_THRESHOLDS['max_memory_usage_mb'] . "MB)";
}
// Check memory management statistics
$memoryStats = $service->getMemoryStatistics();
if (isset($memoryStats['memory_status']['memory_pressure'])) {
$pressure = $memoryStats['memory_status']['memory_pressure'];
if ($pressure > 0.9) {
$score -= 25;
$issues[] = "Critical memory pressure: {$pressure}";
} elseif ($pressure > 0.8) {
$score -= 15;
$issues[] = "High memory pressure: {$pressure}";
}
}
// Check for memory leaks
if (isset($memoryStats['guard_statistics']['emergency_mode']) && $memoryStats['guard_statistics']['emergency_mode']) {
$score -= 30;
$issues[] = "Memory guard in emergency mode";
}
} catch (\Throwable $e) {
$score -= 25;
$issues[] = "Memory efficiency test failed: " . $e->getMessage();
}
if (! empty($issues)) {
$this->logger?->warning('Memory efficiency validation issues detected', [
'issues' => $issues,
'score' => $score,
]);
}
return Score::fromRatio(max(0, $score), 100);
}
/**
* Validate cache effectiveness
*/
private function validateCacheEffectiveness(UnifiedDiscoveryService $service): Score
{
$score = 100;
$issues = [];
try {
$healthStatus = $service->getHealthStatus();
// Check if cache is enabled
if (! isset($healthStatus['cache']['memory_aware']) || ! $healthStatus['cache']['memory_aware']) {
$score -= 20;
$issues[] = "Memory-aware caching not enabled";
}
// For now, assume good cache performance if no issues detected
// In a real implementation, you would track cache hit rates over time
} catch (\Throwable $e) {
$score -= 30;
$issues[] = "Cache effectiveness test failed: " . $e->getMessage();
}
if (! empty($issues)) {
$this->logger?->warning('Cache effectiveness validation issues detected', [
'issues' => $issues,
'score' => $score,
]);
}
return Score::fromRatio(max(0, $score), 100);
}
/**
* Validate system reliability
*/
private function validateReliability(UnifiedDiscoveryService $service): Score
{
$score = 100;
$issues = [];
try {
// Test basic functionality
$testResults = $service->test();
if ($testResults['overall_status'] !== 'healthy') {
$score -= 40;
$issues[] = "Service health check failed: " . $testResults['overall_status'];
}
// Check component statuses
foreach ($testResults['components'] as $component => $status) {
if (is_array($status) && isset($status['status']) && $status['status'] === 'error') {
$score -= 15;
$issues[] = "Component {$component} has error: " . ($status['error'] ?? 'unknown');
}
}
} catch (\Throwable $e) {
$score -= 50;
$issues[] = "Reliability test failed: " . $e->getMessage();
}
if (! empty($issues)) {
$this->logger?->warning('Reliability validation issues detected', [
'issues' => $issues,
'score' => $score,
]);
}
return Score::fromRatio(max(0, $score), 100);
}
/**
* Validate maintainability aspects
*/
private function validateMaintainability(UnifiedDiscoveryService $service, ?DiscoveryRegistry $registry): Score
{
$score = 100;
$issues = [];
try {
// Check if registry has reasonable structure
if ($registry !== null && count($registry) === 0) {
$score -= 10;
$issues[] = "Empty discovery registry - may indicate configuration issues";
}
// Check health status structure
$healthStatus = $service->getHealthStatus();
$expectedKeys = ['service', 'cache', 'memory_management', 'components'];
foreach ($expectedKeys as $key) {
if (! isset($healthStatus[$key])) {
$score -= 5;
$issues[] = "Missing health status key: {$key}";
}
}
} catch (\Throwable $e) {
$score -= 20;
$issues[] = "Maintainability test failed: " . $e->getMessage();
}
if (! empty($issues)) {
$this->logger?->debug('Maintainability validation issues detected', [
'issues' => $issues,
'score' => $score,
]);
}
return Score::fromRatio(max(0, $score), 100);
}
/**
* Calculate overall quality score
*/
private function calculateOverallScore(array $scores): Score
{
$weightedSum = 0;
$totalWeight = 0;
foreach (self::QUALITY_WEIGHTS as $category => $weight) {
if (isset($scores[$category])) {
$weightedSum += $scores[$category]->toDecimal() * $weight;
$totalWeight += $weight;
}
}
$overallScore = $totalWeight > 0 ? $weightedSum / $totalWeight : 0;
return Score::fromRatio((int) ($overallScore * 100), 100);
}
/**
* Generate quality improvement recommendations
*/
private function generateRecommendations(Score $overallScore, array $categoryScores): array
{
$recommendations = [];
// Overall recommendations
if ($overallScore->toDecimal() < 0.6) {
$recommendations[] = 'CRITICAL: Overall quality score is below acceptable threshold - immediate action required';
} elseif ($overallScore->toDecimal() < 0.8) {
$recommendations[] = 'Quality score indicates room for improvement - consider optimization';
}
// Category-specific recommendations
foreach ($categoryScores as $category => $score) {
if ($score->toDecimal() < 0.7) {
$recommendations[] = match ($category) {
'performance' => 'Performance optimization needed - consider enabling parallel processing and reducing batch sizes',
'memory_efficiency' => 'Memory usage is high - enable memory monitoring and optimize memory management strategies',
'cache_effectiveness' => 'Cache performance is poor - review cache configuration and implement cache warming',
'reliability' => 'System reliability issues detected - investigate component health and error handling',
'maintainability' => 'Maintainability concerns found - review configuration and system structure',
default => "Improve {$category} score through system optimization"
};
}
}
if (empty($recommendations)) {
$recommendations[] = 'Quality metrics are within acceptable ranges - continue monitoring';
}
return $recommendations;
}
/**
* Validate specific quality criteria
*/
public function validateCriteria(UnifiedDiscoveryService $service, array $criteria): array
{
$results = [];
foreach ($criteria as $criterion => $expectedValue) {
try {
$result = match ($criterion) {
'max_discovery_time' => $this->checkDiscoveryTime($service, $expectedValue),
'max_memory_usage' => $this->checkMemoryUsage($service, $expectedValue),
'min_cache_hit_rate' => $this->checkCacheHitRate($service, $expectedValue),
'service_health' => $this->checkServiceHealth($service, $expectedValue),
default => ['valid' => false, 'error' => "Unknown criterion: {$criterion}"]
};
$results[$criterion] = $result;
} catch (\Throwable $e) {
$results[$criterion] = [
'valid' => false,
'error' => "Validation failed: " . $e->getMessage(),
];
}
}
return $results;
}
/**
* Check discovery time criterion
*/
private function checkDiscoveryTime(UnifiedDiscoveryService $service, float $maxSeconds): array
{
// This would require actual timing - simplified for example
return [
'valid' => true,
'message' => 'Discovery time validation not implemented in test environment',
];
}
/**
* Check memory usage criterion
*/
private function checkMemoryUsage(UnifiedDiscoveryService $service, int $maxMB): array
{
$currentMemoryMB = memory_get_usage(true) / 1024 / 1024;
return [
'valid' => $currentMemoryMB <= $maxMB,
'actual_value' => $currentMemoryMB,
'expected_max' => $maxMB,
'message' => $currentMemoryMB <= $maxMB ? 'Memory usage within limits' : 'Memory usage exceeds limits',
];
}
/**
* Check cache hit rate criterion
*/
private function checkCacheHitRate(UnifiedDiscoveryService $service, float $minRate): array
{
// This would require cache statistics - simplified for example
return [
'valid' => true,
'message' => 'Cache hit rate validation not implemented in test environment',
];
}
/**
* Check service health criterion
*/
private function checkServiceHealth(UnifiedDiscoveryService $service, string $expectedStatus): array
{
try {
$testResults = $service->test();
$actualStatus = $testResults['overall_status'];
return [
'valid' => $actualStatus === $expectedStatus,
'actual_value' => $actualStatus,
'expected_value' => $expectedStatus,
'message' => $actualStatus === $expectedStatus ? 'Service health as expected' : 'Service health differs from expected',
];
} catch (\Throwable $e) {
return [
'valid' => false,
'error' => 'Health check failed: ' . $e->getMessage(),
];
}
}
}