This commit is contained in:
2025-07-20 04:08:08 -04:00
commit e0ca63ebdf
48 changed files with 7913 additions and 0 deletions

View 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;
}

View 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();
}
}
}

View 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]);
}
}