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:
255
src/Framework/Discovery/MemoryGuard.php
Normal file
255
src/Framework/Discovery/MemoryGuard.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Discovery;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Byte;
|
||||
use App\Framework\Core\ValueObjects\GrowthRate;
|
||||
use App\Framework\Core\ValueObjects\Percentage;
|
||||
use App\Framework\DateTime\Clock;
|
||||
use App\Framework\Discovery\ValueObjects\MemoryLeakInfo;
|
||||
use App\Framework\Logging\Logger;
|
||||
use App\Framework\Performance\MemoryMonitor;
|
||||
|
||||
/**
|
||||
* Advanced memory leak prevention and detection system
|
||||
*/
|
||||
final class MemoryGuard
|
||||
{
|
||||
/** @var array<string, int> Tracking memory usage per operation */
|
||||
private array $memoryCheckpoints = [];
|
||||
|
||||
/** @var array<string, MemoryLeakInfo> Detected memory leaks */
|
||||
private array $detectedLeaks = [];
|
||||
|
||||
/** @var int Maximum allowed memory growth per operation (5MB) */
|
||||
private const int MAX_MEMORY_GROWTH = 5 * 1024 * 1024;
|
||||
|
||||
/** @var float Memory leak detection threshold (50% growth) */
|
||||
private const float LEAK_THRESHOLD = 1.5;
|
||||
|
||||
public function __construct(
|
||||
private readonly MemoryMonitor $memoryMonitor,
|
||||
private readonly Clock $clock,
|
||||
private readonly ?Logger $logger = null
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a memory checkpoint before an operation
|
||||
*/
|
||||
public function checkpoint(string $operation): void
|
||||
{
|
||||
// Force garbage collection before checkpoint
|
||||
$this->forceCleanup();
|
||||
|
||||
$currentMemory = $this->memoryMonitor->getCurrentMemory()->toBytes();
|
||||
$this->memoryCheckpoints[$operation] = $currentMemory;
|
||||
|
||||
$this->logger?->debug("Memory checkpoint created for {$operation}: {$currentMemory} bytes");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate memory usage after an operation
|
||||
* @return bool True if memory usage is acceptable, false if potential leak detected
|
||||
*/
|
||||
public function validate(string $operation): bool
|
||||
{
|
||||
if (! isset($this->memoryCheckpoints[$operation])) {
|
||||
$this->logger?->warning("No checkpoint found for operation: {$operation}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Force cleanup before measuring
|
||||
$this->forceCleanup();
|
||||
|
||||
$startMemory = $this->memoryCheckpoints[$operation];
|
||||
$currentMemory = $this->memoryMonitor->getCurrentMemory()->toBytes();
|
||||
$memoryGrowth = $currentMemory - $startMemory;
|
||||
|
||||
// Remove checkpoint
|
||||
unset($this->memoryCheckpoints[$operation]);
|
||||
|
||||
// Check for excessive memory growth
|
||||
if ($memoryGrowth > self::MAX_MEMORY_GROWTH) {
|
||||
$leakInfo = new MemoryLeakInfo(
|
||||
operation: $operation,
|
||||
startMemory: Byte::fromBytes($startMemory),
|
||||
endMemory: Byte::fromBytes($currentMemory),
|
||||
growth: Byte::fromBytes($memoryGrowth),
|
||||
growthPercentage: GrowthRate::fromMemoryValues($currentMemory, $startMemory),
|
||||
timestamp: $this->clock->time()
|
||||
);
|
||||
|
||||
$this->detectedLeaks[$operation] = $leakInfo;
|
||||
|
||||
$this->logger?->error("Potential memory leak detected in {$operation}: {$leakInfo}");
|
||||
|
||||
// Attempt aggressive cleanup
|
||||
$this->aggressiveCleanup();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for percentage-based leak
|
||||
if ($startMemory > 0 && $currentMemory > $startMemory * self::LEAK_THRESHOLD) {
|
||||
$growthPercentage = ($currentMemory / $startMemory - 1) * 100;
|
||||
$this->logger?->warning(
|
||||
"High memory growth detected in {$operation}: {$growthPercentage}% increase"
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback with memory protection
|
||||
* @template T
|
||||
* @param callable(): T $callback
|
||||
* @return T
|
||||
*/
|
||||
public function protect(string $operation, callable $callback): mixed
|
||||
{
|
||||
$this->checkpoint($operation);
|
||||
|
||||
try {
|
||||
$result = $callback();
|
||||
|
||||
if (! $this->validate($operation)) {
|
||||
$this->logger?->warning("Memory leak detected after {$operation}");
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (\Throwable $e) {
|
||||
// Clean up checkpoint on error
|
||||
unset($this->memoryCheckpoints[$operation]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process data in chunks with memory protection
|
||||
* @template T
|
||||
* @param array<T> $items
|
||||
* @param callable(T): void $processor
|
||||
*/
|
||||
public function processWithMemoryGuard(array $items, callable $processor, int $chunkSize = 100): void
|
||||
{
|
||||
$chunks = array_chunk($items, $chunkSize);
|
||||
$chunkNumber = 0;
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$chunkNumber++;
|
||||
$operation = "chunk_{$chunkNumber}";
|
||||
|
||||
$this->protect($operation, function () use ($chunk, $processor) {
|
||||
foreach ($chunk as $item) {
|
||||
$processor($item);
|
||||
}
|
||||
});
|
||||
|
||||
// Check if we're approaching memory limits
|
||||
if ($this->isMemoryPressureHigh()) {
|
||||
$this->logger?->warning("High memory pressure detected, forcing cleanup");
|
||||
$this->aggressiveCleanup();
|
||||
|
||||
// Reduce chunk size for next iteration
|
||||
$chunkSize = max(10, (int)($chunkSize * 0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force garbage collection and cleanup
|
||||
*/
|
||||
public function forceCleanup(): void
|
||||
{
|
||||
// Multiple GC runs for thorough cleanup
|
||||
gc_collect_cycles();
|
||||
gc_collect_cycles();
|
||||
|
||||
// PHP 7.3+ memory cache cleanup
|
||||
if (function_exists('gc_mem_caches')) {
|
||||
gc_mem_caches();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggressive memory cleanup for critical situations
|
||||
*/
|
||||
public function aggressiveCleanup(): void
|
||||
{
|
||||
// Clear all internal caches
|
||||
$this->clearInternalCaches();
|
||||
|
||||
// Force multiple GC runs
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
gc_collect_cycles();
|
||||
usleep(10000); // 10ms pause between runs
|
||||
}
|
||||
|
||||
// Clear opcache if available
|
||||
if (function_exists('opcache_reset')) {
|
||||
opcache_reset();
|
||||
}
|
||||
|
||||
// Clear realpath cache
|
||||
clearstatcache(true);
|
||||
|
||||
$this->logger?->info("Aggressive memory cleanup completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if memory pressure is high
|
||||
*/
|
||||
public function isMemoryPressureHigh(): bool
|
||||
{
|
||||
$usage = $this->memoryMonitor->getMemoryUsagePercentage();
|
||||
|
||||
return $usage->greaterThan(Percentage::from(80.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detected memory leaks
|
||||
* @return array<string, MemoryLeakInfo>
|
||||
*/
|
||||
public function getDetectedLeaks(): array
|
||||
{
|
||||
return $this->detectedLeaks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear detected leaks history
|
||||
*/
|
||||
public function clearLeakHistory(): void
|
||||
{
|
||||
$this->detectedLeaks = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory guard statistics
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
return [
|
||||
'active_checkpoints' => count($this->memoryCheckpoints),
|
||||
'detected_leaks' => count($this->detectedLeaks),
|
||||
'current_memory' => $this->memoryMonitor->getCurrentMemory()->toHumanReadable(),
|
||||
'peak_memory' => $this->memoryMonitor->getPeakMemory()->toHumanReadable(),
|
||||
'memory_usage' => $this->memoryMonitor->getMemoryUsagePercentage()->getValue() . '%',
|
||||
'is_high_pressure' => $this->isMemoryPressureHigh(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear internal caches (to be extended by specific implementations)
|
||||
*/
|
||||
private function clearInternalCaches(): void
|
||||
{
|
||||
// Clear any static caches
|
||||
// This is a hook for clearing application-specific caches
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user