Initial
This commit is contained in:
85
src/Memory/Handlers/CacheEvictionHandler.php
Normal file
85
src/Memory/Handlers/CacheEvictionHandler.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SqrtSpace\SpaceTime\Memory\Handlers;
|
||||
|
||||
use SqrtSpace\SpaceTime\Memory\MemoryPressureHandler;
|
||||
use SqrtSpace\SpaceTime\Memory\MemoryPressureLevel;
|
||||
|
||||
/**
|
||||
* Evict cache entries under memory pressure
|
||||
*/
|
||||
class CacheEvictionHandler implements MemoryPressureHandler
|
||||
{
|
||||
private array $caches = [];
|
||||
private array $evictionRates = [
|
||||
MemoryPressureLevel::LOW->value => 0.1, // Evict 10%
|
||||
MemoryPressureLevel::MEDIUM->value => 0.25, // Evict 25%
|
||||
MemoryPressureLevel::HIGH->value => 0.5, // Evict 50%
|
||||
MemoryPressureLevel::CRITICAL->value => 0.9, // Evict 90%
|
||||
];
|
||||
|
||||
/**
|
||||
* Register a cache that can be evicted
|
||||
*/
|
||||
public function registerCache(EvictableCache $cache, int $priority = 0): void
|
||||
{
|
||||
$this->caches[] = ['cache' => $cache, 'priority' => $priority];
|
||||
|
||||
// Sort by priority (lower number = higher priority to keep)
|
||||
usort($this->caches, fn($a, $b) => $b['priority'] <=> $a['priority']);
|
||||
}
|
||||
|
||||
public function shouldHandle(MemoryPressureLevel $level): bool
|
||||
{
|
||||
return $level !== MemoryPressureLevel::NONE;
|
||||
}
|
||||
|
||||
public function handle(MemoryPressureLevel $level, array $memoryInfo): void
|
||||
{
|
||||
$evictionRate = $this->evictionRates[$level->value] ?? 0;
|
||||
|
||||
if ($evictionRate === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Evict from lowest priority caches first
|
||||
foreach ($this->caches as $cacheInfo) {
|
||||
$cache = $cacheInfo['cache'];
|
||||
$size = $cache->size();
|
||||
|
||||
if ($size > 0) {
|
||||
$toEvict = (int) ceil($size * $evictionRate);
|
||||
$cache->evict($toEvict);
|
||||
|
||||
// Check if pressure is relieved
|
||||
$currentUsage = memory_get_usage(true);
|
||||
if ($currentUsage < $memoryInfo['limit'] * 0.7) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for caches that support eviction
|
||||
*/
|
||||
interface EvictableCache
|
||||
{
|
||||
/**
|
||||
* Get current cache size
|
||||
*/
|
||||
public function size(): int;
|
||||
|
||||
/**
|
||||
* Evict n entries from cache
|
||||
*/
|
||||
public function evict(int $count): void;
|
||||
|
||||
/**
|
||||
* Clear entire cache
|
||||
*/
|
||||
public function clear(): void;
|
||||
}
|
||||
55
src/Memory/Handlers/GarbageCollectionHandler.php
Normal file
55
src/Memory/Handlers/GarbageCollectionHandler.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SqrtSpace\SpaceTime\Memory\Handlers;
|
||||
|
||||
use SqrtSpace\SpaceTime\Memory\MemoryPressureHandler;
|
||||
use SqrtSpace\SpaceTime\Memory\MemoryPressureLevel;
|
||||
|
||||
/**
|
||||
* Trigger garbage collection under memory pressure
|
||||
*/
|
||||
class GarbageCollectionHandler implements MemoryPressureHandler
|
||||
{
|
||||
private float $lastCollection = 0;
|
||||
private float $minInterval = 1.0; // Minimum seconds between collections
|
||||
|
||||
public function shouldHandle(MemoryPressureLevel $level): bool
|
||||
{
|
||||
return $level->isHigherThan(MemoryPressureLevel::LOW);
|
||||
}
|
||||
|
||||
public function handle(MemoryPressureLevel $level, array $memoryInfo): void
|
||||
{
|
||||
$now = microtime(true);
|
||||
|
||||
// Don't collect too frequently
|
||||
if ($now - $this->lastCollection < $this->minInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force collection for high/critical pressure
|
||||
if ($level->isHigherThan(MemoryPressureLevel::MEDIUM)) {
|
||||
$this->forceCollection();
|
||||
$this->lastCollection = $now;
|
||||
}
|
||||
}
|
||||
|
||||
private function forceCollection(): void
|
||||
{
|
||||
// Enable GC if disabled
|
||||
$wasEnabled = gc_enabled();
|
||||
if (!$wasEnabled) {
|
||||
gc_enable();
|
||||
}
|
||||
|
||||
// Collect cycles
|
||||
$collected = gc_collect_cycles();
|
||||
|
||||
// Restore previous state
|
||||
if (!$wasEnabled) {
|
||||
gc_disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/Memory/Handlers/LoggingHandler.php
Normal file
59
src/Memory/Handlers/LoggingHandler.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SqrtSpace\SpaceTime\Memory\Handlers;
|
||||
|
||||
use SqrtSpace\SpaceTime\Memory\MemoryPressureHandler;
|
||||
use SqrtSpace\SpaceTime\Memory\MemoryPressureLevel;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* Log memory pressure events
|
||||
*/
|
||||
class LoggingHandler implements MemoryPressureHandler
|
||||
{
|
||||
private LoggerInterface $logger;
|
||||
private MemoryPressureLevel $minLevel;
|
||||
|
||||
public function __construct(
|
||||
?LoggerInterface $logger = null,
|
||||
MemoryPressureLevel $minLevel = MemoryPressureLevel::MEDIUM
|
||||
) {
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
$this->minLevel = $minLevel;
|
||||
}
|
||||
|
||||
public function shouldHandle(MemoryPressureLevel $level): bool
|
||||
{
|
||||
return $level->isHigherThan($this->minLevel) || $level === $this->minLevel;
|
||||
}
|
||||
|
||||
public function handle(MemoryPressureLevel $level, array $memoryInfo): void
|
||||
{
|
||||
$context = [
|
||||
'level' => $level->value,
|
||||
'usage' => $this->formatBytes($memoryInfo['usage']),
|
||||
'limit' => $this->formatBytes($memoryInfo['limit']),
|
||||
'percentage' => round($memoryInfo['percentage'], 2),
|
||||
'available' => $this->formatBytes($memoryInfo['available']),
|
||||
];
|
||||
|
||||
match ($level) {
|
||||
MemoryPressureLevel::CRITICAL => $this->logger->critical('Critical memory pressure detected', $context),
|
||||
MemoryPressureLevel::HIGH => $this->logger->error('High memory pressure detected', $context),
|
||||
MemoryPressureLevel::MEDIUM => $this->logger->warning('Medium memory pressure detected', $context),
|
||||
MemoryPressureLevel::LOW => $this->logger->info('Low memory pressure detected', $context),
|
||||
default => $this->logger->debug('Memory pressure check', $context),
|
||||
};
|
||||
}
|
||||
|
||||
private function formatBytes(float $bytes): string
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB'];
|
||||
$factor = floor((strlen((string)(int)$bytes) - 1) / 3);
|
||||
|
||||
return sprintf("%.2f %s", $bytes / pow(1024, $factor), $units[$factor]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user