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:
73
src/Framework/Discovery/Events/CacheCompressionEvent.php
Normal file
73
src/Framework/Discovery/Events/CacheCompressionEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
64
src/Framework/Discovery/Events/CacheEvictionEvent.php
Normal file
64
src/Framework/Discovery/Events/CacheEvictionEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
85
src/Framework/Discovery/Events/CacheHitEvent.php
Normal file
85
src/Framework/Discovery/Events/CacheHitEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
62
src/Framework/Discovery/Events/CacheMissEvent.php
Normal file
62
src/Framework/Discovery/Events/CacheMissEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
17
src/Framework/Discovery/Events/ChunkEventType.php
Normal file
17
src/Framework/Discovery/Events/ChunkEventType.php
Normal 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';
|
||||
}
|
||||
84
src/Framework/Discovery/Events/ChunkProcessingEvent.php
Normal file
84
src/Framework/Discovery/Events/ChunkProcessingEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
37
src/Framework/Discovery/Events/DiscoveryCompletedEvent.php
Normal file
37
src/Framework/Discovery/Events/DiscoveryCompletedEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
41
src/Framework/Discovery/Events/DiscoveryFailedEvent.php
Normal file
41
src/Framework/Discovery/Events/DiscoveryFailedEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
33
src/Framework/Discovery/Events/DiscoveryStartedEvent.php
Normal file
33
src/Framework/Discovery/Events/DiscoveryStartedEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
418
src/Framework/Discovery/Events/EventAggregator.php
Normal file
418
src/Framework/Discovery/Events/EventAggregator.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/Framework/Discovery/Events/FileProcessedEvent.php
Normal file
38
src/Framework/Discovery/Events/FileProcessedEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
65
src/Framework/Discovery/Events/MemoryCleanupEvent.php
Normal file
65
src/Framework/Discovery/Events/MemoryCleanupEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
87
src/Framework/Discovery/Events/MemoryLeakDetectedEvent.php
Normal file
87
src/Framework/Discovery/Events/MemoryLeakDetectedEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
66
src/Framework/Discovery/Events/MemoryPressureEvent.php
Normal file
66
src/Framework/Discovery/Events/MemoryPressureEvent.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
95
src/Framework/Discovery/Events/PerformanceAnalysisEvent.php
Normal file
95
src/Framework/Discovery/Events/PerformanceAnalysisEvent.php
Normal 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';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user