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
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\ValueObjects\CompressionLevel;
/**
* Cache compression event
*
* Emitted when cache data is compressed to reduce memory usage.
*/
final readonly class CacheCompressionEvent
{
public function __construct(
public Byte $originalSize,
public Byte $compressedSize,
public CompressionLevel $compressionLevel,
public float $compressionRatio,
public Timestamp $timestamp
) {
}
/**
* Get memory savings from compression
*/
public function getMemorySavings(): Byte
{
return $this->originalSize->subtract($this->compressedSize);
}
/**
* Get compression effectiveness
*/
public function getEffectiveness(): string
{
return match (true) {
$this->compressionRatio <= 0.3 => 'excellent', // 70%+ compression
$this->compressionRatio <= 0.5 => 'good', // 50-70% compression
$this->compressionRatio <= 0.7 => 'moderate', // 30-50% compression
$this->compressionRatio <= 0.9 => 'low', // 10-30% compression
default => 'poor' // < 10% compression
};
}
/**
* Check if compression was beneficial
*/
public function wasBeneficial(): bool
{
return $this->compressionRatio < 0.9; // At least 10% reduction
}
/**
* Convert to array for serialization
*/
public function toArray(): array
{
return [
'original_size' => $this->originalSize->toHumanReadable(),
'compressed_size' => $this->compressedSize->toHumanReadable(),
'compression_level' => $this->compressionLevel->value,
'compression_ratio' => round($this->compressionRatio * 100, 2) . '%',
'memory_savings' => $this->getMemorySavings()->toHumanReadable(),
'effectiveness' => $this->getEffectiveness(),
'was_beneficial' => $this->wasBeneficial(),
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\ValueObjects\CacheLevel;
/**
* Cache eviction event
*
* Emitted when cache items are evicted due to memory pressure
* or other cache management policies.
*/
final readonly class CacheEvictionEvent
{
public function __construct(
public string $reason,
public int $itemsEvicted,
public Byte $memoryFreed,
public CacheLevel $cacheLevel,
public Timestamp $timestamp
) {
}
/**
* Check if this was an emergency eviction
*/
public function isEmergencyEviction(): bool
{
return in_array($this->reason, ['critical_cleanup', 'emergency_memory', 'out_of_memory']);
}
/**
* Get eviction effectiveness
*/
public function getEffectiveness(): string
{
return match (true) {
$this->memoryFreed->greaterThan(Byte::fromMegabytes(50)) => 'high',
$this->memoryFreed->greaterThan(Byte::fromMegabytes(10)) => 'medium',
$this->memoryFreed->greaterThan(Byte::zero()) => 'low',
default => 'none'
};
}
/**
* Convert to array for serialization
*/
public function toArray(): array
{
return [
'reason' => $this->reason,
'items_evicted' => $this->itemsEvicted,
'memory_freed' => $this->memoryFreed->toHumanReadable(),
'cache_level' => $this->cacheLevel->value,
'is_emergency' => $this->isEmergencyEviction(),
'effectiveness' => $this->getEffectiveness(),
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\ValueObjects\CacheLevel;
/**
* Event fired when discovery results are loaded from cache
*
* Extended for memory-aware caching with data size and cache level tracking.
*/
final readonly class CacheHitEvent
{
public function __construct(
public CacheKey $cacheKey,
public int $itemCount,
public Duration $cacheAge,
public Timestamp $timestamp,
public ?Byte $dataSize = null,
public ?CacheLevel $cacheLevel = null
) {
}
public function isFresh(): bool
{
// Consider cache fresh if less than 30 minutes old
return $this->cacheAge->lessThan(Duration::fromMinutes(30));
}
/**
* Check if this is a large cache hit
*/
public function isLargeCacheHit(): bool
{
return $this->dataSize?->greaterThan(Byte::fromMegabytes(1)) ?? false;
}
/**
* Get cache efficiency rating
*/
public function getEfficiencyRating(): string
{
if ($this->dataSize === null) {
return 'unknown';
}
return match (true) {
$this->isFresh() && ! $this->isLargeCacheHit() => 'optimal',
$this->isFresh() => 'good',
$this->dataSize->greaterThan(Byte::fromMegabytes(5)) => 'poor',
default => 'fair'
};
}
public function toArray(): array
{
$baseArray = [
'cache_key' => (string) $this->cacheKey,
'item_count' => $this->itemCount,
'cache_age_seconds' => $this->cacheAge->toSeconds(),
'cache_age_human' => $this->cacheAge->humanReadable(),
'is_fresh' => $this->isFresh(),
'timestamp' => $this->timestamp->toFloat(),
];
// Add memory-aware fields if available
if ($this->dataSize !== null) {
$baseArray['data_size'] = $this->dataSize->toHumanReadable();
$baseArray['is_large_hit'] = $this->isLargeCacheHit();
$baseArray['efficiency_rating'] = $this->getEfficiencyRating();
}
if ($this->cacheLevel !== null) {
$baseArray['cache_level'] = $this->cacheLevel->value;
}
return $baseArray;
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\ValueObjects\CacheLevel;
/**
* Cache miss event
*
* Emitted when a cache lookup fails to find the requested data.
*/
final readonly class CacheMissEvent
{
public function __construct(
public CacheKey $cacheKey,
public string $reason,
public CacheLevel $cacheLevel,
public Timestamp $timestamp
) {
}
/**
* Get miss reason category
*/
public function getReasonCategory(): string
{
return match ($this->reason) {
'not_found' => 'miss',
'expired' => 'expiration',
'evicted' => 'eviction',
'corrupted' => 'corruption',
default => 'unknown'
};
}
/**
* Check if this miss could have been prevented
*/
public function isPreventable(): bool
{
return in_array($this->reason, ['expired', 'evicted']);
}
/**
* Convert to array for serialization
*/
public function toArray(): array
{
return [
'cache_key' => $this->cacheKey->toString(),
'reason' => $this->reason,
'reason_category' => $this->getReasonCategory(),
'cache_level' => $this->cacheLevel->value,
'is_preventable' => $this->isPreventable(),
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
/**
* Types of chunk processing events
*/
enum ChunkEventType: string
{
case STARTED = 'started';
case PROGRESS = 'progress';
case COMPLETED = 'completed';
case FAILED = 'failed';
case MEMORY_ADJUSTED = 'memory_adjusted';
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\ValueObjects\MemoryStrategy;
/**
* Event fired when chunk processing starts, progresses, or completes
*/
final readonly class ChunkProcessingEvent
{
public function __construct(
public ChunkEventType $eventType,
public int $chunkIndex,
public int $totalChunks,
public int $chunkSize,
public int $processedFiles,
public int $totalFiles,
public Byte $chunkMemoryUsage,
public float $chunkComplexity,
public ?Duration $processingTime,
public MemoryStrategy $strategy,
public ?string $context,
public Timestamp $timestamp
) {
}
public function getProgressPercentage(): float
{
return $this->totalFiles > 0 ? ($this->processedFiles / $this->totalFiles) * 100 : 0.0;
}
public function getChunkProgressPercentage(): float
{
return $this->totalChunks > 0 ? (($this->chunkIndex + 1) / $this->totalChunks) * 100 : 0.0;
}
public function getProcessingRate(): ?float
{
if ($this->processingTime === null || $this->processingTime->toSeconds() <= 0) {
return null;
}
return $this->chunkSize / $this->processingTime->toSeconds(); // Files per second
}
public function getMemoryEfficiency(): float
{
return $this->chunkSize > 0
? $this->chunkMemoryUsage->toBytes() / $this->chunkSize
: 0.0; // Bytes per file
}
public function toArray(): array
{
return [
'event_type' => $this->eventType->value,
'chunk_index' => $this->chunkIndex,
'total_chunks' => $this->totalChunks,
'chunk_size' => $this->chunkSize,
'processed_files' => $this->processedFiles,
'total_files' => $this->totalFiles,
'chunk_memory_usage' => $this->chunkMemoryUsage->toHumanReadable(),
'chunk_memory_usage_bytes' => $this->chunkMemoryUsage->toBytes(),
'chunk_complexity' => round($this->chunkComplexity, 2),
'processing_time_seconds' => $this->processingTime?->toSeconds(),
'processing_time_human' => $this->processingTime?->humanReadable(),
'strategy' => $this->strategy->value,
'context' => $this->context,
'progress_percentage' => round($this->getProgressPercentage(), 2),
'chunk_progress_percentage' => round($this->getChunkProgressPercentage(), 2),
'processing_rate_files_per_sec' => $this->getProcessingRate() !== null
? round($this->getProcessingRate(), 2)
: null,
'memory_efficiency_bytes_per_file' => round($this->getMemoryEfficiency(), 0),
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\ValueObjects\ScanType;
use App\Framework\Filesystem\ValueObjects\ScannerMetrics;
/**
* Event fired when discovery process completes successfully
*/
final readonly class DiscoveryCompletedEvent
{
public function __construct(
public int $filesScanned,
public Duration $duration,
public ?ScannerMetrics $metrics,
public ScanType $scanType,
public Timestamp $timestamp
) {
}
public function toArray(): array
{
return [
'files_scanned' => $this->filesScanned,
'duration_seconds' => $this->duration->toSeconds(),
'duration_human' => $this->duration->humanReadable(),
'metrics' => $this->metrics?->toArray(),
'scan_type' => $this->scanType->value,
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\Results\DiscoveryResults;
use App\Framework\Discovery\ValueObjects\ScanType;
use Throwable;
/**
* Event fired when discovery process fails
*/
final readonly class DiscoveryFailedEvent
{
public function __construct(
public Throwable $exception,
public ?DiscoveryResults $partialResults,
public ScanType $scanType,
public Timestamp $timestamp
) {
}
public function hasPartialResults(): bool
{
return $this->partialResults !== null;
}
public function toArray(): array
{
return [
'error' => $this->exception->getMessage(),
'error_type' => get_class($this->exception),
'has_partial_results' => $this->hasPartialResults(),
'partial_files_count' => $this->partialResults ? count($this->partialResults->toArray()) : 0,
'scan_type' => $this->scanType->value,
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\ValueObjects\ScanType;
/**
* Event fired when discovery process starts
*/
final readonly class DiscoveryStartedEvent
{
public function __construct(
public int $estimatedFiles,
public array $directories,
public ScanType $scanType,
public Timestamp $timestamp
) {
}
public function toArray(): array
{
return [
'estimated_files' => $this->estimatedFiles,
'directories' => $this->directories,
'scan_type' => $this->scanType->value,
'scan_type_description' => $this->scanType->getDescription(),
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,418 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\Events\EventDispatcher;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\Discovery\Memory\LeakSeverity;
use App\Framework\Discovery\Memory\MemoryStatus;
use App\Framework\Logging\Logger;
/**
* Event aggregator for Discovery memory and performance events
*
* Collects, aggregates, and analyzes events to provide insights
* into Discovery operation patterns and performance characteristics.
*/
final class EventAggregator
{
private array $memoryPressureEvents = [];
private array $memoryCleanupEvents = [];
private array $memoryLeakEvents = [];
private array $chunkProcessingEvents = [];
private array $strategyChangeEvents = [];
private array $statistics = [
'total_events' => 0,
'memory_pressure_events' => 0,
'cleanup_events' => 0,
'leak_events' => 0,
'chunk_events' => 0,
'strategy_changes' => 0,
];
public function __construct(
private readonly EventDispatcher $eventDispatcher,
private readonly Clock $clock,
private readonly ?Logger $logger = null,
private readonly int $maxEventsPerType = 100
) {
$this->registerEventHandlers();
}
/**
* Register event handlers for all Discovery events
*/
private function registerEventHandlers(): void
{
$this->eventDispatcher->listen(MemoryPressureEvent::class, [$this, 'handleMemoryPressureEvent']);
$this->eventDispatcher->listen(MemoryCleanupEvent::class, [$this, 'handleMemoryCleanupEvent']);
$this->eventDispatcher->listen(MemoryLeakDetectedEvent::class, [$this, 'handleMemoryLeakEvent']);
$this->eventDispatcher->listen(ChunkProcessingEvent::class, [$this, 'handleChunkProcessingEvent']);
$this->eventDispatcher->listen(MemoryStrategyChangedEvent::class, [$this, 'handleStrategyChangeEvent']);
}
/**
* Handle memory pressure events
*/
public function handleMemoryPressureEvent(MemoryPressureEvent $event): void
{
$this->memoryPressureEvents[] = $event;
$this->statistics['memory_pressure_events']++;
$this->statistics['total_events']++;
$this->trimEventHistory($this->memoryPressureEvents);
$this->logger?->debug('Memory pressure event aggregated', [
'status' => $event->status->value,
'memory_pressure' => $event->memoryPressure->toString(),
'context' => $event->context,
]);
}
/**
* Handle memory cleanup events
*/
public function handleMemoryCleanupEvent(MemoryCleanupEvent $event): void
{
$this->memoryCleanupEvents[] = $event;
$this->statistics['cleanup_events']++;
$this->statistics['total_events']++;
$this->trimEventHistory($this->memoryCleanupEvents);
$this->logger?->debug('Memory cleanup event aggregated', [
'memory_freed' => $event->memoryFreed->toHumanReadable(),
'was_emergency' => $event->wasEmergency,
'was_effective' => $event->wasEffective(),
]);
}
/**
* Handle memory leak detection events
*/
public function handleMemoryLeakEvent(MemoryLeakDetectedEvent $event): void
{
$this->memoryLeakEvents[] = $event;
$this->statistics['leak_events']++;
$this->statistics['total_events']++;
$this->trimEventHistory($this->memoryLeakEvents);
$this->logger?->warning('Memory leak event aggregated', [
'severity' => $event->severity->value,
'growth_rate' => $event->growthRate->toHumanReadable(),
'requires_immediate_action' => $event->requiresImmediateAction(),
]);
}
/**
* Handle chunk processing events
*/
public function handleChunkProcessingEvent(ChunkProcessingEvent $event): void
{
$this->chunkProcessingEvents[] = $event;
$this->statistics['chunk_events']++;
$this->statistics['total_events']++;
$this->trimEventHistory($this->chunkProcessingEvents);
$this->logger?->debug('Chunk processing event aggregated', [
'event_type' => $event->eventType->value,
'chunk_index' => $event->chunkIndex,
'progress' => $event->getProgressPercentage() . '%',
]);
}
/**
* Handle strategy change events
*/
public function handleStrategyChangeEvent(MemoryStrategyChangedEvent $event): void
{
$this->strategyChangeEvents[] = $event;
$this->statistics['strategy_changes']++;
$this->statistics['total_events']++;
$this->trimEventHistory($this->strategyChangeEvents);
$this->logger?->info('Memory strategy change event aggregated', [
'previous_strategy' => $event->previousStrategy->value,
'new_strategy' => $event->newStrategy->value,
'reason' => $event->changeReason,
'is_emergency' => $event->isEmergencyChange(),
]);
}
/**
* Get aggregated memory pressure analysis
*/
public function getMemoryPressureAnalysis(): array
{
if (empty($this->memoryPressureEvents)) {
return [];
}
$criticalCount = 0;
$warningCount = 0;
$totalPressureSum = 0.0;
$pressureSpikes = [];
foreach ($this->memoryPressureEvents as $event) {
match ($event->status) {
MemoryStatus::CRITICAL => $criticalCount++,
MemoryStatus::WARNING => $warningCount++,
default => null
};
$totalPressureSum += $event->memoryPressure->toDecimal();
if ($event->memoryPressure->toDecimal() > 0.9) {
$pressureSpikes[] = [
'timestamp' => $event->timestamp->toFloat(),
'pressure' => $event->memoryPressure->toString(),
'context' => $event->context,
];
}
}
return [
'total_events' => count($this->memoryPressureEvents),
'critical_events' => $criticalCount,
'warning_events' => $warningCount,
'average_pressure' => round($totalPressureSum / count($this->memoryPressureEvents), 3),
'pressure_spikes' => $pressureSpikes,
'critical_ratio' => round($criticalCount / count($this->memoryPressureEvents), 3),
'warning_ratio' => round($warningCount / count($this->memoryPressureEvents), 3),
];
}
/**
* Get cleanup effectiveness analysis
*/
public function getCleanupEffectivenessAnalysis(): array
{
if (empty($this->memoryCleanupEvents)) {
return [];
}
$effectiveCleanups = 0;
$emergencyCleanups = 0;
$totalMemoryFreed = Byte::zero();
$totalCycles = 0;
foreach ($this->memoryCleanupEvents as $event) {
if ($event->wasEffective()) {
$effectiveCleanups++;
}
if ($event->wasEmergency) {
$emergencyCleanups++;
}
$totalMemoryFreed = $totalMemoryFreed->add($event->memoryFreed);
$totalCycles += $event->collectedCycles;
}
return [
'total_cleanups' => count($this->memoryCleanupEvents),
'effective_cleanups' => $effectiveCleanups,
'emergency_cleanups' => $emergencyCleanups,
'effectiveness_ratio' => round($effectiveCleanups / count($this->memoryCleanupEvents), 3),
'emergency_ratio' => round($emergencyCleanups / count($this->memoryCleanupEvents), 3),
'total_memory_freed' => $totalMemoryFreed->toHumanReadable(),
'total_cycles_collected' => $totalCycles,
'average_memory_freed' => $totalMemoryFreed->divide(count($this->memoryCleanupEvents))->toHumanReadable(),
];
}
/**
* Get memory leak trend analysis
*/
public function getMemoryLeakAnalysis(): array
{
if (empty($this->memoryLeakEvents)) {
return [];
}
$severityCounts = [
LeakSeverity::LOW->value => 0,
LeakSeverity::MEDIUM->value => 0,
LeakSeverity::HIGH->value => 0,
LeakSeverity::CRITICAL->value => 0,
];
$criticalLeaks = [];
$totalGrowthRate = Byte::zero();
foreach ($this->memoryLeakEvents as $event) {
$severityCounts[$event->severity->value]++;
$totalGrowthRate = $totalGrowthRate->add($event->growthRate);
if ($event->severity === LeakSeverity::CRITICAL) {
$criticalLeaks[] = [
'timestamp' => $event->timestamp->toFloat(),
'growth_rate' => $event->growthRate->toHumanReadable(),
'context' => $event->context,
];
}
}
return [
'total_leaks_detected' => count($this->memoryLeakEvents),
'severity_distribution' => $severityCounts,
'critical_leaks' => $criticalLeaks,
'average_growth_rate' => $totalGrowthRate->divide(count($this->memoryLeakEvents))->toHumanReadable(),
];
}
/**
* Get chunk processing performance analysis
*/
public function getChunkPerformanceAnalysis(): array
{
if (empty($this->chunkProcessingEvents)) {
return [];
}
$eventTypeCounts = [];
$completedChunks = [];
$failedChunks = [];
$memoryAdjustments = 0;
foreach ($this->chunkProcessingEvents as $event) {
$eventTypeCounts[$event->eventType->value] = ($eventTypeCounts[$event->eventType->value] ?? 0) + 1;
match ($event->eventType) {
ChunkEventType::COMPLETED => $completedChunks[] = $event,
ChunkEventType::FAILED => $failedChunks[] = $event,
ChunkEventType::MEMORY_ADJUSTED => $memoryAdjustments++,
default => null
};
}
$averageChunkSize = 0;
$averageProcessingTime = 0.0;
$totalMemoryUsage = Byte::zero();
if (! empty($completedChunks)) {
$totalChunkSize = array_sum(array_map(fn ($event) => $event->chunkSize, $completedChunks));
$averageChunkSize = $totalChunkSize / count($completedChunks);
$totalProcessingTime = array_sum(array_map(fn ($event) => $event->processingTime?->toSeconds() ?? 0, $completedChunks));
$averageProcessingTime = $totalProcessingTime / count($completedChunks);
foreach ($completedChunks as $event) {
$totalMemoryUsage = $totalMemoryUsage->add($event->chunkMemoryUsage);
}
}
return [
'total_chunk_events' => count($this->chunkProcessingEvents),
'event_type_distribution' => $eventTypeCounts,
'completed_chunks' => count($completedChunks),
'failed_chunks' => count($failedChunks),
'memory_adjustments' => $memoryAdjustments,
'average_chunk_size' => round($averageChunkSize, 1),
'average_processing_time' => round($averageProcessingTime, 3),
'total_memory_usage' => $totalMemoryUsage->toHumanReadable(),
'success_rate' => ! empty($completedChunks) && ! empty($failedChunks)
? round(count($completedChunks) / (count($completedChunks) + count($failedChunks)), 3)
: 1.0,
];
}
/**
* Get strategy change analysis
*/
public function getStrategyChangeAnalysis(): array
{
if (empty($this->strategyChangeEvents)) {
return [];
}
$strategyUsage = [];
$emergencyChanges = 0;
$downgrades = 0;
foreach ($this->strategyChangeEvents as $event) {
$strategyUsage[$event->newStrategy->value] = ($strategyUsage[$event->newStrategy->value] ?? 0) + 1;
if ($event->isEmergencyChange()) {
$emergencyChanges++;
}
if ($event->isStrategyDowngrade()) {
$downgrades++;
}
}
return [
'total_strategy_changes' => count($this->strategyChangeEvents),
'strategy_usage' => $strategyUsage,
'emergency_changes' => $emergencyChanges,
'strategy_downgrades' => $downgrades,
'emergency_ratio' => round($emergencyChanges / count($this->strategyChangeEvents), 3),
'downgrade_ratio' => round($downgrades / count($this->strategyChangeEvents), 3),
];
}
/**
* Get comprehensive Discovery analytics
*/
public function getDiscoveryAnalytics(): array
{
return [
'statistics' => $this->statistics,
'memory_pressure' => $this->getMemoryPressureAnalysis(),
'cleanup_effectiveness' => $this->getCleanupEffectivenessAnalysis(),
'memory_leaks' => $this->getMemoryLeakAnalysis(),
'chunk_performance' => $this->getChunkPerformanceAnalysis(),
'strategy_changes' => $this->getStrategyChangeAnalysis(),
'generated_at' => Timestamp::fromFloat($this->clock->time())->toFloat(),
];
}
/**
* Clear all aggregated events (for testing or memory management)
*/
public function clearEvents(): void
{
$this->memoryPressureEvents = [];
$this->memoryCleanupEvents = [];
$this->memoryLeakEvents = [];
$this->chunkProcessingEvents = [];
$this->strategyChangeEvents = [];
$this->statistics = [
'total_events' => 0,
'memory_pressure_events' => 0,
'cleanup_events' => 0,
'leak_events' => 0,
'chunk_events' => 0,
'strategy_changes' => 0,
];
}
/**
* Get current statistics
*/
public function getStatistics(): array
{
return $this->statistics;
}
/**
* Trim event history to prevent memory growth
*/
private function trimEventHistory(array &$events): void
{
if (count($events) > $this->maxEventsPerType) {
$events = array_slice($events, -$this->maxEventsPerType);
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use SplFileInfo;
/**
* Event fired when a file is successfully processed
*/
final readonly class FileProcessedEvent
{
public function __construct(
public SplFileInfo $file,
public Duration $processingTime,
public array $discoveredItems, // What was found in this file
public Timestamp $timestamp
) {
}
public function getFilePath(): string
{
return $this->file->getPathname();
}
public function toArray(): array
{
return [
'file' => $this->getFilePath(),
'processing_time_ms' => $this->processingTime->toMilliseconds(),
'discovered_items' => $this->discoveredItems,
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Event fired when memory cleanup is performed
*/
final readonly class MemoryCleanupEvent
{
public function __construct(
public Byte $beforeUsage,
public Byte $afterUsage,
public Byte $memoryFreed,
public int $collectedCycles,
public bool $wasEmergency,
public ?string $triggerReason,
public ?string $context,
public Timestamp $timestamp
) {
}
public function wasEffective(): bool
{
return $this->memoryFreed->greaterThan(Byte::zero()) || $this->collectedCycles > 0;
}
public function getEffectivenessRatio(): float
{
if ($this->beforeUsage->isEmpty()) {
return 0.0;
}
return $this->memoryFreed->toBytes() / $this->beforeUsage->toBytes();
}
public function getCleanupType(): string
{
return $this->wasEmergency ? 'emergency' : 'routine';
}
public function toArray(): array
{
return [
'before_usage' => $this->beforeUsage->toHumanReadable(),
'before_usage_bytes' => $this->beforeUsage->toBytes(),
'after_usage' => $this->afterUsage->toHumanReadable(),
'after_usage_bytes' => $this->afterUsage->toBytes(),
'memory_freed' => $this->memoryFreed->toHumanReadable(),
'memory_freed_bytes' => $this->memoryFreed->toBytes(),
'collected_cycles' => $this->collectedCycles,
'was_emergency' => $this->wasEmergency,
'trigger_reason' => $this->triggerReason,
'context' => $this->context,
'was_effective' => $this->wasEffective(),
'effectiveness_ratio' => round($this->getEffectivenessRatio() * 100, 2),
'cleanup_type' => $this->getCleanupType(),
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\Memory\LeakSeverity;
/**
* Event fired when a memory leak is detected during discovery
*/
final readonly class MemoryLeakDetectedEvent
{
public function __construct(
public Byte $detectedAt,
public Byte $growthRate,
public LeakSeverity $severity,
public int $windowSize,
public ?string $context,
public Timestamp $timestamp
) {
}
public function requiresImmediateAction(): bool
{
return $this->severity === LeakSeverity::CRITICAL || $this->severity === LeakSeverity::HIGH;
}
public function getEstimatedTimeToExhaustion(Byte $availableMemory): ?float
{
if ($this->growthRate->isEmpty()) {
return null;
}
// Calculate time in seconds until memory exhaustion
$timeToExhaustion = $availableMemory->toBytes() / $this->growthRate->toBytes();
// Return null if time would be negative or unreasonably long
return $timeToExhaustion > 0 && $timeToExhaustion < 86400 ? $timeToExhaustion : null;
}
public function getRecommendedActions(): array
{
return match ($this->severity) {
LeakSeverity::CRITICAL => [
'Stop processing immediately',
'Force garbage collection',
'Review object retention patterns',
'Switch to STREAMING memory strategy',
'Restart discovery with smaller batches',
],
LeakSeverity::HIGH => [
'Enable frequent cleanup cycles',
'Reduce batch sizes',
'Monitor object references',
'Consider switching to CONSERVATIVE strategy',
],
LeakSeverity::MEDIUM => [
'Monitor memory usage closely',
'Enable more frequent cleanup',
'Review large object usage',
],
LeakSeverity::LOW => [
'Continue monitoring',
'Consider periodic cleanup optimization',
]
};
}
public function toArray(): array
{
return [
'detected_at' => $this->detectedAt->toHumanReadable(),
'detected_at_bytes' => $this->detectedAt->toBytes(),
'growth_rate' => $this->growthRate->toHumanReadable(),
'growth_rate_bytes' => $this->growthRate->toBytes(),
'severity' => $this->severity->value,
'window_size' => $this->windowSize,
'context' => $this->context,
'requires_immediate_action' => $this->requiresImmediateAction(),
'recommended_actions' => $this->getRecommendedActions(),
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\Memory\MemoryStatus;
use App\Framework\Discovery\ValueObjects\MemoryStrategy;
/**
* Event fired when memory pressure reaches warning or critical levels
*/
final readonly class MemoryPressureEvent
{
public function __construct(
public MemoryStatus $status,
public Byte $currentUsage,
public Byte $memoryLimit,
public Percentage $memoryPressure,
public MemoryStrategy $strategy,
public ?string $context,
public Timestamp $timestamp
) {
}
public function isCritical(): bool
{
return $this->status === MemoryStatus::CRITICAL;
}
public function isWarning(): bool
{
return $this->status === MemoryStatus::WARNING;
}
public function getRecommendedAction(): string
{
return match ($this->status) {
MemoryStatus::CRITICAL => 'Immediate cleanup required - reduce batch size or stop processing',
MemoryStatus::WARNING => 'Consider enabling more frequent cleanup and reducing chunk sizes',
MemoryStatus::NORMAL => 'No action required'
};
}
public function toArray(): array
{
return [
'status' => $this->status->value,
'current_usage' => $this->currentUsage->toHumanReadable(),
'current_usage_bytes' => $this->currentUsage->toBytes(),
'memory_limit' => $this->memoryLimit->toHumanReadable(),
'memory_limit_bytes' => $this->memoryLimit->toBytes(),
'memory_pressure' => $this->memoryPressure->toString(),
'memory_pressure_decimal' => $this->memoryPressure->toDecimal(),
'strategy' => $this->strategy->value,
'context' => $this->context,
'is_critical' => $this->isCritical(),
'is_warning' => $this->isWarning(),
'recommended_action' => $this->getRecommendedAction(),
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\ValueObjects\MemoryStrategy;
/**
* Event fired when memory strategy is changed during discovery
*/
final readonly class MemoryStrategyChangedEvent
{
public function __construct(
public MemoryStrategy $previousStrategy,
public MemoryStrategy $newStrategy,
public string $changeReason,
public ?array $triggerMetrics,
public ?string $context,
public Timestamp $timestamp
) {
}
public function isEmergencyChange(): bool
{
return str_contains(strtolower($this->changeReason), 'emergency') ||
str_contains(strtolower($this->changeReason), 'critical');
}
public function isStrategyDowngrade(): bool
{
$strategyLevels = [
MemoryStrategy::STREAMING->value => 1,
MemoryStrategy::CONSERVATIVE->value => 2,
MemoryStrategy::BATCH->value => 3,
MemoryStrategy::ADAPTIVE->value => 4,
MemoryStrategy::AGGRESSIVE->value => 5,
];
$previousLevel = $strategyLevels[$this->previousStrategy->value] ?? 0;
$newLevel = $strategyLevels[$this->newStrategy->value] ?? 0;
return $newLevel < $previousLevel;
}
public function getImpactDescription(): string
{
if ($this->isStrategyDowngrade()) {
return 'Performance may decrease but memory usage will be more conservative';
} else {
return 'Performance may improve but memory usage will be higher';
}
}
public function toArray(): array
{
return [
'previous_strategy' => $this->previousStrategy->value,
'previous_strategy_description' => $this->previousStrategy->getDescription(),
'new_strategy' => $this->newStrategy->value,
'new_strategy_description' => $this->newStrategy->getDescription(),
'change_reason' => $this->changeReason,
'trigger_metrics' => $this->triggerMetrics,
'context' => $this->context,
'is_emergency_change' => $this->isEmergencyChange(),
'is_strategy_downgrade' => $this->isStrategyDowngrade(),
'impact_description' => $this->getImpactDescription(),
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Events;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Discovery\ValueObjects\DiscoveryContext;
use App\Framework\Discovery\ValueObjects\PerformanceMetrics;
/**
* Event emitted when discovery performance analysis is completed
*
* Provides comprehensive performance metrics for telemetry and monitoring systems.
*/
final readonly class PerformanceAnalysisEvent
{
public function __construct(
public string $operationId,
public DiscoveryContext $context,
public PerformanceMetrics $metrics,
public array $bottlenecks,
public array $recommendations,
public Timestamp $timestamp
) {
}
/**
* Get telemetry data for monitoring systems
*/
public function toTelemetryData(): array
{
return [
'event_type' => 'discovery_performance_analysis',
'operation_id' => $this->operationId,
'timestamp' => $this->timestamp->toFloat(),
'context' => [
'paths' => $this->context->paths,
'scan_type' => $this->context->scanType->value,
'cache_enabled' => $this->context->options->useCache,
],
'performance' => [
'duration_ms' => $this->metrics->snapshot->duration?->toMilliseconds() ?? 0,
'files_processed' => $this->metrics->snapshot->filesProcessed,
'memory_peak_mb' => $this->metrics->snapshot->peakMemory->toMegabytes(),
'memory_delta_mb' => $this->metrics->snapshot->memoryDelta?->toMegabytes() ?? 0,
'cache_hits' => $this->metrics->snapshot->cacheHits,
'cache_misses' => $this->metrics->snapshot->cacheMisses,
'io_operations' => $this->metrics->snapshot->ioOperations,
'errors' => $this->metrics->snapshot->errorsEncountered,
],
'metrics' => [
'throughput' => $this->metrics->throughput,
'operation_size_score' => $this->metrics->operationSize->toDecimal(),
'memory_efficiency_score' => $this->metrics->memoryEfficiency->toDecimal(),
'cache_efficiency_score' => $this->metrics->cacheEfficiency->toDecimal(),
'performance_score' => $this->metrics->performanceScore->toDecimal(),
'efficiency_rating' => $this->metrics->getEfficiencyRating(),
],
'issues' => [
'bottlenecks' => $this->bottlenecks,
'recommendations' => $this->recommendations,
'bottleneck_count' => count($this->bottlenecks),
],
];
}
/**
* Check if this represents a performance issue
*/
public function hasPerformanceIssues(): bool
{
return ! empty($this->bottlenecks) ||
$this->metrics->performanceScore->toDecimal() < 0.7;
}
/**
* Get severity level for alerting
*/
public function getSeverityLevel(): string
{
$score = $this->metrics->performanceScore->toDecimal();
$bottleneckCount = count($this->bottlenecks);
if ($score < 0.3 || $bottleneckCount >= 3) {
return 'critical';
} elseif ($score < 0.5 || $bottleneckCount >= 2) {
return 'warning';
} elseif ($score < 0.7 || $bottleneckCount >= 1) {
return 'info';
}
return 'success';
}
}