2026-02-01 15:31:21 +01:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Metrics collector class.
|
|
|
|
|
*
|
|
|
|
|
* @package WP_Prometheus
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
namespace Magdev\WpPrometheus\Metrics;
|
|
|
|
|
|
|
|
|
|
use Prometheus\CollectorRegistry;
|
|
|
|
|
use Prometheus\Storage\InMemory;
|
|
|
|
|
use Prometheus\RenderTextFormat;
|
|
|
|
|
|
|
|
|
|
// Prevent direct file access.
|
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collector class.
|
|
|
|
|
*
|
|
|
|
|
* Collects and manages Prometheus metrics.
|
|
|
|
|
*/
|
|
|
|
|
class Collector {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Prometheus collector registry.
|
|
|
|
|
*
|
|
|
|
|
* @var CollectorRegistry
|
|
|
|
|
*/
|
|
|
|
|
private CollectorRegistry $registry;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Metric namespace.
|
|
|
|
|
*
|
|
|
|
|
* @var string
|
|
|
|
|
*/
|
|
|
|
|
private string $namespace = 'wordpress';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructor.
|
|
|
|
|
*/
|
|
|
|
|
public function __construct() {
|
|
|
|
|
$this->registry = new CollectorRegistry( new InMemory() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the collector registry.
|
|
|
|
|
*
|
|
|
|
|
* @return CollectorRegistry
|
|
|
|
|
*/
|
|
|
|
|
public function get_registry(): CollectorRegistry {
|
|
|
|
|
return $this->registry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the metric namespace.
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function get_namespace(): string {
|
|
|
|
|
return $this->namespace;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collect all enabled metrics.
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function collect(): void {
|
|
|
|
|
$enabled_metrics = get_option( 'wp_prometheus_enabled_metrics', array() );
|
|
|
|
|
|
|
|
|
|
// Always collect WordPress info.
|
|
|
|
|
if ( in_array( 'wordpress_info', $enabled_metrics, true ) ) {
|
|
|
|
|
$this->collect_wordpress_info();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect user metrics.
|
|
|
|
|
if ( in_array( 'wordpress_users_total', $enabled_metrics, true ) ) {
|
|
|
|
|
$this->collect_users_total();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect posts metrics.
|
|
|
|
|
if ( in_array( 'wordpress_posts_total', $enabled_metrics, true ) ) {
|
|
|
|
|
$this->collect_posts_total();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect comments metrics.
|
|
|
|
|
if ( in_array( 'wordpress_comments_total', $enabled_metrics, true ) ) {
|
|
|
|
|
$this->collect_comments_total();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect plugins metrics.
|
|
|
|
|
if ( in_array( 'wordpress_plugins_total', $enabled_metrics, true ) ) {
|
|
|
|
|
$this->collect_plugins_total();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 14:24:05 +01:00
|
|
|
// Collect runtime metrics (HTTP requests, DB queries).
|
|
|
|
|
$this->collect_runtime_metrics( $enabled_metrics );
|
|
|
|
|
|
2026-02-01 15:31:21 +01:00
|
|
|
/**
|
|
|
|
|
* Fires after default metrics are collected.
|
|
|
|
|
*
|
|
|
|
|
* @param Collector $collector The metrics collector instance.
|
|
|
|
|
*/
|
|
|
|
|
do_action( 'wp_prometheus_collect_metrics', $this );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render metrics in Prometheus text format.
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function render(): string {
|
|
|
|
|
$this->collect();
|
|
|
|
|
|
|
|
|
|
$renderer = new RenderTextFormat();
|
|
|
|
|
return $renderer->render( $this->registry->getMetricFamilySamples() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collect WordPress info metric.
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function collect_wordpress_info(): void {
|
|
|
|
|
$gauge = $this->registry->getOrRegisterGauge(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
'info',
|
|
|
|
|
'WordPress installation information',
|
|
|
|
|
array( 'version', 'php_version', 'multisite' )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$gauge->set(
|
|
|
|
|
1,
|
|
|
|
|
array(
|
|
|
|
|
get_bloginfo( 'version' ),
|
|
|
|
|
PHP_VERSION,
|
|
|
|
|
is_multisite() ? 'yes' : 'no',
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collect total users metric.
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function collect_users_total(): void {
|
|
|
|
|
$gauge = $this->registry->getOrRegisterGauge(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
'users_total',
|
|
|
|
|
'Total number of WordPress users',
|
|
|
|
|
array( 'role' )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$user_count = count_users();
|
|
|
|
|
foreach ( $user_count['avail_roles'] as $role => $count ) {
|
|
|
|
|
$gauge->set( $count, array( $role ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collect total posts metric.
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function collect_posts_total(): void {
|
|
|
|
|
$gauge = $this->registry->getOrRegisterGauge(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
'posts_total',
|
|
|
|
|
'Total number of posts by type and status',
|
|
|
|
|
array( 'post_type', 'status' )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$post_types = get_post_types( array( 'public' => true ) );
|
|
|
|
|
foreach ( $post_types as $post_type ) {
|
|
|
|
|
$counts = wp_count_posts( $post_type );
|
|
|
|
|
foreach ( get_object_vars( $counts ) as $status => $count ) {
|
|
|
|
|
if ( $count > 0 ) {
|
|
|
|
|
$gauge->set( (int) $count, array( $post_type, $status ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collect total comments metric.
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function collect_comments_total(): void {
|
|
|
|
|
$gauge = $this->registry->getOrRegisterGauge(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
'comments_total',
|
|
|
|
|
'Total number of comments by status',
|
|
|
|
|
array( 'status' )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$comments = wp_count_comments();
|
|
|
|
|
$statuses = array(
|
|
|
|
|
'approved' => $comments->approved,
|
|
|
|
|
'moderated' => $comments->moderated,
|
|
|
|
|
'spam' => $comments->spam,
|
|
|
|
|
'trash' => $comments->trash,
|
|
|
|
|
'total_comments' => $comments->total_comments,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
foreach ( $statuses as $status => $count ) {
|
|
|
|
|
$gauge->set( (int) $count, array( $status ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collect total plugins metric.
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function collect_plugins_total(): void {
|
|
|
|
|
if ( ! function_exists( 'get_plugins' ) ) {
|
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$gauge = $this->registry->getOrRegisterGauge(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
'plugins_total',
|
|
|
|
|
'Total number of plugins by status',
|
|
|
|
|
array( 'status' )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$all_plugins = get_plugins();
|
|
|
|
|
$active_plugins = get_option( 'active_plugins', array() );
|
|
|
|
|
|
|
|
|
|
$gauge->set( count( $all_plugins ), array( 'installed' ) );
|
|
|
|
|
$gauge->set( count( $active_plugins ), array( 'active' ) );
|
|
|
|
|
$gauge->set( count( $all_plugins ) - count( $active_plugins ), array( 'inactive' ) );
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 14:24:05 +01:00
|
|
|
/**
|
|
|
|
|
* Collect runtime metrics from stored data.
|
|
|
|
|
*
|
|
|
|
|
* @param array $enabled_metrics List of enabled metrics.
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function collect_runtime_metrics( array $enabled_metrics ): void {
|
|
|
|
|
$runtime_collector = RuntimeCollector::get_instance();
|
|
|
|
|
$stored_metrics = $runtime_collector->get_stored_metrics();
|
|
|
|
|
|
|
|
|
|
// HTTP requests total counter.
|
|
|
|
|
if ( in_array( 'wordpress_http_requests_total', $enabled_metrics, true ) && ! empty( $stored_metrics['counters'] ) ) {
|
|
|
|
|
foreach ( $stored_metrics['counters'] as $counter_data ) {
|
|
|
|
|
if ( 'http_requests_total' !== $counter_data['name'] ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$counter = $this->registry->getOrRegisterCounter(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
'http_requests_total',
|
|
|
|
|
'Total number of HTTP requests',
|
|
|
|
|
array( 'method', 'status', 'endpoint' )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$counter->incBy(
|
|
|
|
|
(int) $counter_data['value'],
|
|
|
|
|
array(
|
|
|
|
|
$counter_data['labels']['method'] ?? 'GET',
|
|
|
|
|
$counter_data['labels']['status'] ?? '200',
|
|
|
|
|
$counter_data['labels']['endpoint'] ?? 'unknown',
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HTTP request duration histogram.
|
|
|
|
|
if ( in_array( 'wordpress_http_request_duration_seconds', $enabled_metrics, true ) && ! empty( $stored_metrics['histograms'] ) ) {
|
|
|
|
|
foreach ( $stored_metrics['histograms'] as $histogram_data ) {
|
|
|
|
|
if ( 'http_request_duration_seconds' !== $histogram_data['name'] ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For histograms, we expose as a gauge with pre-aggregated bucket counts.
|
|
|
|
|
// This is a workaround since we can't directly populate histogram buckets.
|
|
|
|
|
$this->expose_histogram_as_gauges(
|
|
|
|
|
'http_request_duration_seconds',
|
|
|
|
|
'HTTP request duration in seconds',
|
|
|
|
|
$histogram_data,
|
|
|
|
|
array( 'method', 'endpoint' )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Database queries total counter.
|
|
|
|
|
if ( in_array( 'wordpress_db_queries_total', $enabled_metrics, true ) && ! empty( $stored_metrics['counters'] ) ) {
|
|
|
|
|
foreach ( $stored_metrics['counters'] as $counter_data ) {
|
|
|
|
|
if ( 'db_queries_total' !== $counter_data['name'] ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$counter = $this->registry->getOrRegisterCounter(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
'db_queries_total',
|
|
|
|
|
'Total number of database queries',
|
|
|
|
|
array( 'endpoint' )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$counter->incBy(
|
|
|
|
|
(int) $counter_data['value'],
|
|
|
|
|
array(
|
|
|
|
|
$counter_data['labels']['endpoint'] ?? 'unknown',
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Database query duration histogram (if SAVEQUERIES is enabled).
|
|
|
|
|
if ( in_array( 'wordpress_db_queries_total', $enabled_metrics, true ) && ! empty( $stored_metrics['histograms'] ) ) {
|
|
|
|
|
foreach ( $stored_metrics['histograms'] as $histogram_data ) {
|
|
|
|
|
if ( 'db_query_duration_seconds' !== $histogram_data['name'] ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->expose_histogram_as_gauges(
|
|
|
|
|
'db_query_duration_seconds',
|
|
|
|
|
'Database query duration in seconds',
|
|
|
|
|
$histogram_data,
|
|
|
|
|
array( 'endpoint' )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Expose pre-aggregated histogram data as gauge metrics.
|
|
|
|
|
*
|
|
|
|
|
* Since we store histogram data externally, we expose it using gauges
|
|
|
|
|
* that follow Prometheus histogram naming conventions.
|
|
|
|
|
*
|
|
|
|
|
* @param string $name Metric name.
|
|
|
|
|
* @param string $help Metric description.
|
|
|
|
|
* @param array $histogram_data Stored histogram data.
|
|
|
|
|
* @param array $label_names Label names.
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function expose_histogram_as_gauges( string $name, string $help, array $histogram_data, array $label_names ): void {
|
|
|
|
|
$label_values = array();
|
|
|
|
|
foreach ( $label_names as $label_name ) {
|
|
|
|
|
$label_values[] = $histogram_data['labels'][ $label_name ] ?? 'unknown';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Expose bucket counts.
|
|
|
|
|
$bucket_gauge = $this->registry->getOrRegisterGauge(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
$name . '_bucket',
|
|
|
|
|
$help . ' (bucket)',
|
|
|
|
|
array_merge( $label_names, array( 'le' ) )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$cumulative_count = 0;
|
|
|
|
|
foreach ( $histogram_data['buckets'] as $le => $count ) {
|
|
|
|
|
$cumulative_count += $count;
|
|
|
|
|
$bucket_gauge->set(
|
|
|
|
|
$cumulative_count,
|
|
|
|
|
array_merge( $label_values, array( $le ) )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Expose sum.
|
|
|
|
|
$sum_gauge = $this->registry->getOrRegisterGauge(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
$name . '_sum',
|
|
|
|
|
$help . ' (sum)',
|
|
|
|
|
$label_names
|
|
|
|
|
);
|
|
|
|
|
$sum_gauge->set( $histogram_data['sum'], $label_values );
|
|
|
|
|
|
|
|
|
|
// Expose count.
|
|
|
|
|
$count_gauge = $this->registry->getOrRegisterGauge(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
$name . '_count',
|
|
|
|
|
$help . ' (count)',
|
|
|
|
|
$label_names
|
|
|
|
|
);
|
|
|
|
|
$count_gauge->set( $histogram_data['count'], $label_values );
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 15:31:21 +01:00
|
|
|
/**
|
|
|
|
|
* Register a custom gauge metric.
|
|
|
|
|
*
|
|
|
|
|
* @param string $name Metric name.
|
|
|
|
|
* @param string $help Metric description.
|
|
|
|
|
* @param array $labels Label names.
|
|
|
|
|
* @return \Prometheus\Gauge
|
|
|
|
|
*/
|
|
|
|
|
public function register_gauge( string $name, string $help, array $labels = array() ): \Prometheus\Gauge {
|
|
|
|
|
return $this->registry->getOrRegisterGauge(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
$name,
|
|
|
|
|
$help,
|
|
|
|
|
$labels
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register a custom counter metric.
|
|
|
|
|
*
|
|
|
|
|
* @param string $name Metric name.
|
|
|
|
|
* @param string $help Metric description.
|
|
|
|
|
* @param array $labels Label names.
|
|
|
|
|
* @return \Prometheus\Counter
|
|
|
|
|
*/
|
|
|
|
|
public function register_counter( string $name, string $help, array $labels = array() ): \Prometheus\Counter {
|
|
|
|
|
return $this->registry->getOrRegisterCounter(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
$name,
|
|
|
|
|
$help,
|
|
|
|
|
$labels
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register a custom histogram metric.
|
|
|
|
|
*
|
|
|
|
|
* @param string $name Metric name.
|
|
|
|
|
* @param string $help Metric description.
|
|
|
|
|
* @param array $labels Label names.
|
|
|
|
|
* @param array|null $buckets Histogram buckets.
|
|
|
|
|
* @return \Prometheus\Histogram
|
|
|
|
|
*/
|
|
|
|
|
public function register_histogram( string $name, string $help, array $labels = array(), ?array $buckets = null ): \Prometheus\Histogram {
|
|
|
|
|
return $this->registry->getOrRegisterHistogram(
|
|
|
|
|
$this->namespace,
|
|
|
|
|
$name,
|
|
|
|
|
$help,
|
|
|
|
|
$labels,
|
|
|
|
|
$buckets
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|