o11y-8.x-1.x-dev/modules/o11y_metrics/src/Bridge/PrometheusBridge.php
modules/o11y_metrics/src/Bridge/PrometheusBridge.php
<?php
namespace Drupal\o11y_metrics\Bridge;
use Drupal\o11y_metrics\Prometheus\CollectorRegistryForNonPlugins;
use Prometheus\CollectorRegistry;
use Prometheus\RenderTextFormat;
use Prometheus\Storage\InMemory;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Drupal\o11y_metrics\BaseMetricsSourceInterface;
use Drupal\o11y_metrics\Prometheus\Storage\DrupalCache;
/**
* {@inheritdoc}
*/
class PrometheusBridge implements PrometheusBridgeInterface {
use ContainerAwareTrait;
/**
* Stateful promphp collector registry.
*
* @var \Drupal\o11y_metrics\Prometheus\CollectorRegistryForNonPlugins
*/
protected $collectorRegistryForNonPlugins;
/**
* Stateless promphp collector registry.
*
* @var \Prometheus\CollectorRegistry
*/
protected CollectorRegistry $collectorRegistryForPlugins;
/**
* The site settings.
*
* @var \Drupal\Core\Site\Settings
*/
protected $settings;
/**
* The promphp renderer.
*
* @var \Prometheus\RenderTextFormat
*/
protected $renderer;
/**
* Default is drupal cache.
*/
public function __construct(DrupalCache $drupalCache) {
$this->collectorRegistryForNonPlugins = new CollectorRegistryForNonPlugins($drupalCache, FALSE);
$this->collectorRegistryForPlugins = new CollectorRegistry(new InMemory(), FALSE);
$this->renderer = new RenderTextFormat();
}
/**
* {@inheritdoc}
*/
public function render(): string {
$metrics_with_storage = $this->renderer->render($this->collectorRegistryForNonPlugins->getMetricFamilySamples());
// Now execute the plugin metrics and get their rendered result.
// Plugins should call getCounter/getGauge/getHistogram without passing a
// $metricsSource, i.e. using a collector registry that uses an InMemory
// storage, i.e. a storage whose persistence does not outlive the request
// lifetime.
// Plugins are executed only during scraping of prometheus route (/metrics).
// If plugins used a persistent storage then they could register say a
// gauge like `drupal_config_status_sync{has_changes="0"} 1` at scrape t1
// and `drupal_config_status_sync{has_changes="1"} 1` at scrape t2.
// Except that at scrape 2 both lines would be outputted.
/** @var \Drupal\o11y_metrics\MetricsCollectorManagerInterface $collector_manager */
$collector_manager = $this->container->get('o11y_metrics.metrics_collector_manager');
$collector_manager->executeMetricsPlugins();
$metrics_without_storage = $this->renderer->render($this->collectorRegistryForPlugins->getMetricFamilySamples());
return implode(PHP_EOL, array_filter(array_map('trim', [
$metrics_with_storage,
$metrics_without_storage,
])));
}
/**
* {@inheritdoc}
*/
public function getCounter(
string $namespace,
string $name,
string $help,
array $labels = [],
BaseMetricsSourceInterface $metricsSource = NULL
) {
$args = [$namespace, $name, $help, $labels];
if ($metricsSource) {
$metric = $this->collectorRegistryForNonPlugins->getOrRegisterCounter(...$args);
$this->collectorRegistryForNonPlugins->associateSourceToMetric($metricsSource, $metric);
}
else {
$metric = $this->collectorRegistryForPlugins->getOrRegisterCounter(...$args);
}
return $metric;
}
/**
* {@inheritdoc}
*/
public function getGauge(
string $namespace,
string $name,
string $help,
array $labels = [],
BaseMetricsSourceInterface $metricsSource = NULL
) {
$args = [$namespace, $name, $help, $labels];
if ($metricsSource) {
$metric = $this->collectorRegistryForNonPlugins->getOrRegisterGauge(...$args);
$this->collectorRegistryForNonPlugins->associateSourceToMetric($metricsSource, $metric);
}
else {
$metric = $this->collectorRegistryForPlugins->getOrRegisterGauge(...$args);
}
return $metric;
}
/**
* {@inheritdoc}
*/
public function getHistogram(
string $namespace,
string $name,
string $help,
array $labels = [],
array $buckets = NULL,
BaseMetricsSourceInterface $metricsSource = NULL
) {
$args = [$namespace, $name, $help, $labels, $buckets];
if ($metricsSource) {
$metric = $this->collectorRegistryForNonPlugins->getOrRegisterHistogram(...$args);
$this->collectorRegistryForNonPlugins->associateSourceToMetric($metricsSource, $metric);
}
else {
$metric = $this->collectorRegistryForPlugins->getOrRegisterHistogram(...$args);
}
return $metric;
}
/**
* {@inheritdoc}
*/
public function getSummary(
string $namespace,
string $name,
string $help,
array $labels = [],
int $maxAgeSeconds = 600,
array $quantiles = NULL,
BaseMetricsSourceInterface $metricsSource = NULL
) {
$args = [$namespace, $name, $help, $labels, $maxAgeSeconds, $quantiles];
if ($metricsSource) {
$metric = $this->collectorRegistryForNonPlugins->getOrRegisterSummary(...$args);
$this->collectorRegistryForNonPlugins->associateSourceToMetric($metricsSource, $metric);
}
else {
$metric = $this->collectorRegistryForPlugins->getOrRegisterSummary(...$args);
}
return $metric;
}
/**
* {@inheritdoc}
*/
public function removeMetricsOfSource(string $metricsSourceClassName) {
$sourceId = $this->getSourceIdFromMetricsSourceClassName($metricsSourceClassName);
$this->collectorRegistryForNonPlugins->removeMetricsOfSource($sourceId);
}
/**
* {@inheritdoc}
*/
public function hasMetricsOfSource(string $metricsSourceClassName) {
$sourceId = $this->getSourceIdFromMetricsSourceClassName($metricsSourceClassName);
return $this->collectorRegistryForNonPlugins->hasMetricsOfSource($sourceId);
}
/**
* {@inheritdoc}
*/
public function wipeMetrics() {
$this->collectorRegistryForNonPlugins->wipeStorage();
$this->collectorRegistryForPlugins->wipeStorage();
}
/**
* Utility function.
*/
protected function getSourceIdFromMetricsSourceClassName(string $metricsSourceClassName) {
$ifaces = class_implements($metricsSourceClassName);
if (empty($ifaces[BaseMetricsSourceInterface::class])) {
throw new \InvalidArgumentException('Parameter $metricsSourceClassName of ' . __METHOD__ . ' should implement interface ' . BaseMetricsSourceInterface::class);
}
return $metricsSourceClassName::getMetricsSourceId();
}
}
