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(); } // Collect runtime metrics (HTTP requests, DB queries). $this->collect_runtime_metrics( $enabled_metrics ); /** * 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' ) ); } /** * 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 ); } /** * 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 ); } }