cache_monitor-1.0.x-dev/src/Cache/TimingBackend.php
src/Cache/TimingBackend.php
<?php
namespace Drupal\cache_monitor\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\cache_monitor\Metrics\Aggregator;
/**
* Measures time per cache operation and reports to Aggregator.
*/
final class TimingBackend implements CacheBackendInterface {
/**
* Request-level cached decision.
*
* @var bool|null
*/
private static ?bool $cacheMonitorMetricsAllowed = null;
/**
* The decorated cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
private CacheBackendInterface $inner;
/**
* The name of the cache bin.
*
* @var string
*/
private string $bin;
private string $backend;
/**
* The metrics aggregator.
*
* @var \Drupal\cache_monitor\Metrics\Aggregator
*/
private Aggregator $agg;
/**
* Constructs a new TimingBackend instance.
*
* @param \Drupal\Core\Cache\CacheBackendInterface $inner
* The decorated cache backend.
* @param string $bin
* The name of the cache bin.
* @param \Drupal\cache_monitor\Metrics\Aggregator $agg
* The metrics aggregator.
*/
public function __construct(
CacheBackendInterface $inner,
string $bin,
Aggregator $agg
) {
$this->inner = $inner;
$this->bin = $bin;
$this->agg = $agg;
$this->backend = get_class($inner);
}
/**
* Determine if metrics collection is allowed.
*/
private function metricsAllowed(): bool {
if (self::$cacheMonitorMetricsAllowed !== null) {
return self::$cacheMonitorMetricsAllowed;
}
try {
$state = \Drupal::state();
if (!$state->get('cache_monitor.metrics_enabled', false)) {
return self::$cacheMonitorMetricsAllowed = false;
}
$paths = $state->get('cache_monitor.paths', []);
if (!is_array($paths) || $paths === []) {
return self::$cacheMonitorMetricsAllowed = true;
}
if (!\Drupal::hasService('path.current') || !\Drupal::hasService('path_alias.manager')) {
return self::$cacheMonitorMetricsAllowed = false;
}
$current = \Drupal::service('path.current')->getPath() ?: '/';
$currentNoSlash = ltrim($current, '/');
$internal = \Drupal::service('path_alias.manager')->getPathByAlias($current);
$internalNoSlash = ltrim($internal, '/');
return self::$cacheMonitorMetricsAllowed = (
in_array($currentNoSlash, $paths, true) ||
in_array($internalNoSlash, $paths, true)
);
}
catch (\Throwable $e) {
return self::$cacheMonitorMetricsAllowed = false;
}
}
/**
* Times a callable and records the time taken.
*
* @param callable $fn
* The callable to time.
* @param string $op
* The operation name.
* @param int $count
* The count of items processed (for batch operations).
*
* @return mixed
* The result of the callable.
*/
private function t(callable $fn, string $op, int $count = 1) {
if (!$this->metricsAllowed()) {
return $fn();
}
$t0 = hrtime(true);
$res = $fn();
$ms = (hrtime(true) - $t0) / 1e6;
$this->agg->add($this->bin, $this->backend, $op, $count, $ms);
return $res;
}
/**
* {@inheritdoc}
*/
public function get($cid, $allow_invalid = FALSE) {
return $this->t(fn() => $this->inner->get($cid, $allow_invalid), 'get');
}
/**
* {@inheritdoc}
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
$n = is_array($cids) ? count($cids) : 0;
return $this->t(fn() => $this->inner->getMultiple($cids, $allow_invalid), 'getMultiple', $n);
}
/**
* {@inheritdoc}
*/
public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = []) {
return $this->t(fn() => $this->inner->set($cid, $data, $expire, $tags), 'set');
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items) {
return $this->t(fn() => $this->inner->setMultiple($items), 'setMultiple', count($items));
}
/**
* {@inheritdoc}
*/
public function delete($cid) {
return $this->t(fn() => $this->inner->delete($cid), 'delete');
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $cids) {
return $this->t(fn() => $this->inner->deleteMultiple($cids), 'deleteMultiple', count($cids));
}
/**
* {@inheritdoc}
*/
public function invalidateAll() {
return $this->t(fn() => $this->inner->invalidateAll(), 'invalidateAll');
}
/**
* {@inheritdoc}
*/
public function deleteAll() {
return $this->t(fn() => $this->inner->deleteAll(), 'deleteAll');
}
/**
* {@inheritdoc}
*/
public function invalidate($cid) {
return $this->t(fn() => $this->inner->invalidate($cid), 'invalidate');
}
/**
* {@inheritdoc}
*/
public function invalidateMultiple(array $cids) {
return $this->t(fn() => $this->inner->invalidateMultiple($cids), 'invalidateMultiple', count($cids));
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
return $this->t(fn() => $this->inner->invalidateTags($tags), 'invalidateTags', count($tags));
}
/**
* {@inheritdoc}
*/
public function removeBin() {
return $this->t(fn() => $this->inner->removeBin(), 'removeBin');
}
/**
* {@inheritdoc}
*/
public function garbageCollection() {
return $this->t(fn() => $this->inner->garbageCollection(), 'garbageCollection');
}
}
