o11y-8.x-1.x-dev/modules/o11y_metrics/modules/o11y_metrics_database/src/EventSubscriber/DatabaseCollector.php
modules/o11y_metrics/modules/o11y_metrics_database/src/EventSubscriber/DatabaseCollector.php
<?php
namespace Drupal\o11y_metrics_database\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\o11y_metrics\BaseMetricsSourceInterface;
use Drupal\o11y_metrics\Bridge\PrometheusBridgeInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\TerminateEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Gather db metrics on request end.
*/
class DatabaseCollector implements EventSubscriberInterface, BaseMetricsSourceInterface {
const CONFIG_NAME = 'o11y_metrics_database.settings';
/**
* The promphp bridge.
*
* @var \Drupal\o11y_metrics\Bridge\PrometheusBridgeInterface
*/
protected $promBridge;
/**
* The current_route_match service.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* The prometheusio database config object.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $config;
/**
* {@inheritdoc}
*/
public function __construct(
PrometheusBridgeInterface $promBridge,
RouteMatchInterface $routeMatch,
ConfigFactoryInterface $config
) {
$this->promBridge = $promBridge;
$this->routeMatch = $routeMatch;
$this->config = $config->get(static::CONFIG_NAME);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
KernelEvents::TERMINATE => ['collectPrometheusMetricsDatabase', 0],
];
}
/**
* {@inheritDoc}
*/
public static function getMetricsSourceId(): string {
return 'database';
}
/**
* Event callback.
*
* Gather the db logs on request end.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function collectPrometheusMetricsDatabase(TerminateEvent $event) {
$routeName = str_replace('.', '_', $this->routeMatch->getRouteName());
$routeObject = $this->routeMatch->getRouteObject();
if (empty($routeName) || !$routeObject) {
return;
}
$isAdminRoute = $routeObject->getOptions()['_admin_route'] ?? FALSE;
if ($isAdminRoute && ($this->config->get('exclude_admin_paths'))) {
return;
}
$buckets = $this->config->get('buckets');
foreach (array_keys(Database::getAllConnectionInfo()) as $database_key) {
$database = Database::getConnection('default', $database_key);
if (!$database->getLogger()) {
continue;
}
$queries = $database->getLogger()->get('o11y_metrics');
$totalTimeInMs = $this->getTotalTimeInMs($queries);
$this->promBridge->getHistogram(
'drupal',
'database_time',
'Timing metrics for database.',
[
'database',
'route',
],
empty($buckets) ? NULL : $buckets,
$this
)
->observe($totalTimeInMs, [$database_key, $routeName]);
}
}
/**
* Get total time in ms of the select queries.
*
* @return int|float
* The total time of the select queries.
*/
protected function getTotalTimeInMs(array $queries) {
$totalTimeInMs = 0;
foreach ($queries as $query) {
// Consider only SELECTs.
if (strpos($query['query'], 'SELECT') === FALSE) {
continue;
}
// Queries have microsecond precision.
$totalTimeInMs = $query['time'] * 1000;
}
return $totalTimeInMs;
}
}
