Custom Implementations
Custom Implementations
Create custom metric sources for caching, alternative data sources, or testing.
Overview
All metric sources implement interfaces, making them easy to swap out with custom implementations. This is useful for:
- Caching metrics in Redis/Memcached
- Using alternative data sources (PHP extensions, eBPF)
- Testing with stub implementations
- Adding custom processing or filtering
Available Interfaces
use Cbox\SystemMetrics\Contracts\{
EnvironmentDetector,
CpuMetricsSource,
MemoryMetricsSource,
LoadAverageSource,
UptimeSource,
StorageMetricsSource,
NetworkMetricsSource
};
Creating a Custom Source
Example: Redis-Cached CPU Metrics
use Cbox\SystemMetrics\Contracts\CpuMetricsSource;
use Cbox\SystemMetrics\DTO\Result;
use Cbox\SystemMetrics\Sources\Cpu\LinuxProcCpuMetricsSource;
class RedisCachedCpuSource implements CpuMetricsSource
{
public function __construct(
private Redis $redis,
private CpuMetricsSource $fallback = new LinuxProcCpuMetricsSource(),
private int $ttl = 1 // Cache for 1 second
) {}
public function read(): Result
{
$cacheKey = 'system_metrics:cpu';
// Try cache first
if ($cached = $this->redis->get($cacheKey)) {
$snapshot = unserialize($cached);
return Result::success($snapshot);
}
// Cache miss - read from system
$result = $this->fallback->read();
if ($result->isSuccess()) {
$this->redis->setex(
$cacheKey,
$this->ttl,
serialize($result->getValue())
);
}
return $result;
}
}
Configuring Custom Source Globally
use Cbox\SystemMetrics\Config\SystemMetricsConfig;
// Set custom CPU source
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
SystemMetricsConfig::setCpuMetricsSource(new RedisCachedCpuSource($redis));
// All subsequent calls use Redis cache
$cpu = SystemMetrics::cpu();
Testing with Stubs
Simple Stub
use Cbox\SystemMetrics\Contracts\CpuMetricsSource;
use Cbox\SystemMetrics\DTO\Result;
use Cbox\SystemMetrics\DTO\Metrics\Cpu\{CpuSnapshot, CpuTimes};
$stub = new class implements CpuMetricsSource {
public function read(): Result {
return Result::success(new CpuSnapshot(
total: new CpuTimes(100, 50, 500, 0, 0, 0, 0, 0),
perCore: [],
timestamp: new DateTimeImmutable()
));
}
};
SystemMetricsConfig::setCpuMetricsSource($stub);
PHPUnit Mock
use PHPUnit\Framework\TestCase;
use Cbox\SystemMetrics\Contracts\MemoryMetricsSource;
use Cbox\SystemMetrics\Config\SystemMetricsConfig;
class MyTest extends TestCase
{
public function testMemoryUsage()
{
// Create mock
$mock = $this->createMock(MemoryMetricsSource::class);
$mock->method('read')
->willReturn(Result::success($this->createFakeMemorySnapshot()));
// Configure globally
SystemMetricsConfig::setMemoryMetricsSource($mock);
// Test your code
$result = SystemMetrics::memory();
$this->assertTrue($result->isSuccess());
}
private function createFakeMemorySnapshot()
{
return new MemorySnapshot(
totalBytes: 16 * 1024 ** 3, // 16 GB
freeBytes: 8 * 1024 ** 3, // 8 GB
availableBytes: 10 * 1024 ** 3,
usedBytes: 6 * 1024 ** 3,
buffersBytes: 0,
cachedBytes: 0,
swapTotalBytes: 0,
swapFreeBytes: 0,
swapUsedBytes: 0,
timestamp: new DateTimeImmutable()
);
}
}
Using Dependency Injection
Actions can be instantiated with custom sources:
use Cbox\SystemMetrics\Actions\ReadCpuMetricsAction;
use Cbox\SystemMetrics\Sources\Cpu\LinuxProcCpuMetricsSource;
// Direct instantiation with custom source
$action = new ReadCpuMetricsAction(
new RedisCachedCpuSource($redis)
);
$result = $action->execute();
Example: Logging Source Wrapper
use Cbox\SystemMetrics\Contracts\MemoryMetricsSource;
use Cbox\SystemMetrics\DTO\Result;
use Psr\Log\LoggerInterface;
class LoggingMemorySource implements MemoryMetricsSource
{
public function __construct(
private MemoryMetricsSource $inner,
private LoggerInterface $logger
) {}
public function read(): Result
{
$start = microtime(true);
$result = $this->inner->read();
$duration = (microtime(true) - $start) * 1000; // ms
if ($result->isSuccess()) {
$mem = $result->getValue();
$this->logger->debug('Memory read succeeded', [
'duration_ms' => round($duration, 2),
'used_gb' => round($mem->usedBytes / 1024**3, 2),
]);
} else {
$this->logger->error('Memory read failed', [
'duration_ms' => round($duration, 2),
'error' => $result->getError()->getMessage(),
]);
}
return $result;
}
}
Example: Rate-Limited Source
use Cbox\SystemMetrics\Contracts\CpuMetricsSource;
use Cbox\SystemMetrics\DTO\Result;
class RateLimitedCpuSource implements CpuMetricsSource
{
private ?float $lastRead = null;
private ?Result $lastResult = null;
public function __construct(
private CpuMetricsSource $inner,
private float $minInterval = 0.1 // Min 100ms between reads
) {}
public function read(): Result
{
$now = microtime(true);
// Return cached result if within rate limit
if ($this->lastRead !== null &&
($now - $this->lastRead) < $this->minInterval) {
return $this->lastResult;
}
// Read fresh value
$this->lastResult = $this->inner->read();
$this->lastRead = $now;
return $this->lastResult;
}
}
Configuration Methods
use Cbox\SystemMetrics\Config\SystemMetricsConfig;
// CPU metrics source
SystemMetricsConfig::setCpuMetricsSource($customCpuSource);
// Memory metrics source
SystemMetricsConfig::setMemoryMetricsSource($customMemorySource);
// Environment detector
SystemMetricsConfig::setEnvironmentDetector($customDetector);
// Other sources...
SystemMetricsConfig::setLoadAverageSource($customLoadSource);
SystemMetricsConfig::setUptimeSource($customUptimeSource);
SystemMetricsConfig::setStorageMetricsSource($customStorageSource);
SystemMetricsConfig::setNetworkMetricsSource($customNetworkSource);
Best Practices
- Implement the interface completely - All methods must return
Result<T> - Handle errors gracefully - Return
Result::failure()instead of throwing - Preserve immutability - Return readonly DTOs
- Add proper timestamps - Include accurate
timestampfields - Test thoroughly - Verify both success and failure cases
- Document behavior - Explain caching, rate limiting, etc.
Related Documentation
- Error Handling - Result<T> pattern
- Architecture: Design Principles - Interface-driven design
- API Reference - All interfaces and DTOs