cache_monitor-1.0.x-dev/src/EventSubscriber/FlushSubscriber.php
src/EventSubscriber/FlushSubscriber.php
<?php
namespace Drupal\cache_monitor\EventSubscriber;
use Drupal\Core\Database\Database;
use Drupal\cache_monitor\Metrics\Aggregator;
use Drupal\cache_monitor\Storage\Storage;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\State\StateInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\TerminateEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* On kernel terminate, flush aggregated metrics to storage.
*/
final class FlushSubscriber implements EventSubscriberInterface {
/** @var \Drupal\cache_monitor\Metrics\Aggregator */
private Aggregator $agg;
/** @var \Drupal\cache_monitor\Storage\Storage */
private Storage $storage;
/** @var \Drupal\Core\Session\AccountInterface */
private AccountInterface $currentUser;
/** @var \Drupal\Core\State\StateInterface */
private StateInterface $state;
/**
* Constructs a new FlushSubscriber instance.
*
* @param \Drupal\cache_monitor\Metrics\Aggregator $agg
* The metrics aggregator.
* @param \Drupal\cache_monitor\Storage\Storage $storage
* The storage service.
* @param \Drupal\Core\Session\AccountInterface $currentUser
* The current user.
* @param \Drupal\Core\State\StateInterface $state
* The state service.
*/
public function __construct(
Aggregator $agg,
Storage $storage,
AccountInterface $currentUser,
StateInterface $state
) {
$this->agg = $agg;
$this->storage = $storage;
$this->currentUser = $currentUser;
$this->state = $state;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [KernelEvents::TERMINATE => 'onTerminate'];
}
/**
* Checks if the storage tables are ready.
*
* If not, we cannot save metrics (e.g. during install or update).
*/
private function storageReady(): bool {
try {
$schema = Database::getConnection()->schema();
return $schema->tableExists('cache_monitor_request') && $schema->tableExists('cache_monitor_metric');
}
catch (\Exception $e) {
// DB not ready (installing, updating, broken connection).
return false;
}
}
/**
* On kernel terminate, flush aggregated metrics to storage.
*
* @param \Symfony\Component\HttpKernel\Event\TerminateEvent $event
* The terminate event.
*/
public function onTerminate(TerminateEvent $event): void {
if (!$this->storageReady()) {
$this->agg->drain(); // Cleanup, but skip saving.
return;
}
$rows = $this->agg->drain();
if (empty($rows)) {
return; // Nothing to save.
}
$knownBins = array_keys(Cache::getBins());
$observed = array_unique(array_map(fn($r) => $r['bin'], $rows));
$allBins = array_values(array_unique(array_merge($knownBins, $observed)));
sort($allBins);
$req = $event->getRequest();
try {
$this->storage->save([
'method' => $req->getMethod(),
'uri' => $req->getRequestUri(),
'uid' => (int) $this->currentUser->id(),
'ip' => $req->getClientIp() ?: '',
'context' => $req->attributes->get('_route') ?: NULL,
], $rows, $allBins);
}
catch (\Throwable $e) {
// Never break the request; optionally log to watchdog.
\Drupal::logger('cache_monitor_timing')->warning('Saving metrics failed: @m', ['@m' => $e->getMessage()]);
}
}
}
