check_prometheus(); // If plugins_loaded hasn't fully completed, hook init at priority 20. // Otherwise, run init directly. if ( ! did_action( 'plugins_loaded' ) || doing_action( 'plugins_loaded' ) ) { add_action( 'plugins_loaded', array( $this, 'init' ), 20 ); } else { $this->init(); } } /** * Check if WP Prometheus is active. * * @return void */ public function check_prometheus(): void { $this->prometheus_active = defined( 'WP_PROMETHEUS_VERSION' ) || class_exists( 'WP_Prometheus\Plugin' ); } /** * Initialize Prometheus integration. * * @return void */ public function init(): void { if ( ! $this->prometheus_active ) { return; } // Register metrics collector. add_action( 'wp_prometheus_collect_metrics', array( $this, 'collect_metrics' ) ); } /** * Check if Prometheus is active. * * @return bool */ public function is_active(): bool { return $this->prometheus_active; } /** * Collect FediStream metrics. * * @param object $collector The Prometheus collector. * @return void */ public function collect_metrics( $collector ): void { // Register and collect all metric categories. $this->collect_content_metrics( $collector ); $this->collect_engagement_metrics( $collector ); $this->collect_user_metrics( $collector ); if ( $this->is_woocommerce_enabled() ) { $this->collect_woocommerce_metrics( $collector ); } if ( $this->is_activitypub_enabled() ) { $this->collect_activitypub_metrics( $collector ); } } /** * Collect content metrics. * * @param object $collector The Prometheus collector. * @return void */ private function collect_content_metrics( $collector ): void { // Content count by type and status. $content_gauge = $collector->register_gauge( 'fedistream_content_total', 'Total FediStream content count', array( 'type', 'status' ) ); $post_types = array( 'fedistream_artist', 'fedistream_album', 'fedistream_track', 'fedistream_playlist', ); foreach ( $post_types as $post_type ) { $type_label = str_replace( 'fedistream_', '', $post_type ); $counts = wp_count_posts( $post_type ); foreach ( array( 'publish', 'draft', 'pending' ) as $status ) { $count = isset( $counts->$status ) ? (int) $counts->$status : 0; $content_gauge->set( $count, array( $type_label, $status ) ); } } // Genre count. $genres_gauge = $collector->register_gauge( 'fedistream_genres_total', 'Total number of genres', array() ); $genre_count = wp_count_terms( array( 'taxonomy' => 'fedistream_genre' ) ); $genres_gauge->set( is_wp_error( $genre_count ) ? 0 : (int) $genre_count, array() ); // Mood count. $moods_gauge = $collector->register_gauge( 'fedistream_moods_total', 'Total number of moods', array() ); $mood_count = wp_count_terms( array( 'taxonomy' => 'fedistream_mood' ) ); $moods_gauge->set( is_wp_error( $mood_count ) ? 0 : (int) $mood_count, array() ); } /** * Collect engagement metrics. * * @param object $collector The Prometheus collector. * @return void */ private function collect_engagement_metrics( $collector ): void { global $wpdb; $plays_table = $wpdb->prefix . 'fedistream_plays'; $favorites_table = $wpdb->prefix . 'fedistream_favorites'; $follows_table = $wpdb->prefix . 'fedistream_user_follows'; $history_table = $wpdb->prefix . 'fedistream_listening_history'; // Total plays. $plays_gauge = $collector->register_gauge( 'fedistream_plays_total', 'Total track plays', array() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $total_plays = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$plays_table}" ); $plays_gauge->set( $total_plays, array() ); // Plays today. $plays_today_gauge = $collector->register_gauge( 'fedistream_plays_today', 'Track plays today', array() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $plays_today = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$plays_table} WHERE DATE(played_at) = CURDATE()" ); $plays_today_gauge->set( $plays_today, array() ); // Favorites by type. $favorites_gauge = $collector->register_gauge( 'fedistream_favorites_total', 'Total favorites by content type', array( 'type' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $favorites_by_type = $wpdb->get_results( "SELECT content_type, COUNT(*) as count FROM {$favorites_table} GROUP BY content_type" ); foreach ( $favorites_by_type as $row ) { $favorites_gauge->set( (int) $row->count, array( $row->content_type ) ); } // Local follows. $follows_gauge = $collector->register_gauge( 'fedistream_local_follows_total', 'Total local artist follows', array() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $total_follows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$follows_table}" ); $follows_gauge->set( $total_follows, array() ); // Listening history entries. $history_gauge = $collector->register_gauge( 'fedistream_listening_history_entries', 'Total listening history entries', array() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $history_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$history_table}" ); $history_gauge->set( $history_count, array() ); } /** * Collect user metrics. * * @param object $collector The Prometheus collector. * @return void */ private function collect_user_metrics( $collector ): void { global $wpdb; $favorites_table = $wpdb->prefix . 'fedistream_favorites'; $follows_table = $wpdb->prefix . 'fedistream_user_follows'; $notifications_table = $wpdb->prefix . 'fedistream_notifications'; // Users with library (favorites). $users_library_gauge = $collector->register_gauge( 'fedistream_users_with_library', 'Users who have favorites', array() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $users_with_library = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT user_id) FROM {$favorites_table}" ); $users_library_gauge->set( $users_with_library, array() ); // Users following artists. $users_following_gauge = $collector->register_gauge( 'fedistream_users_following_artists', 'Users following at least one artist', array() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $users_following = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT user_id) FROM {$follows_table}" ); $users_following_gauge->set( $users_following, array() ); // Notifications by type and status. $notifications_gauge = $collector->register_gauge( 'fedistream_notifications_total', 'Notifications by type and status', array( 'type', 'status' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $notifications = $wpdb->get_results( "SELECT type, is_read, COUNT(*) as count FROM {$notifications_table} GROUP BY type, is_read" ); foreach ( $notifications as $row ) { $status = $row->is_read ? 'read' : 'unread'; $notifications_gauge->set( (int) $row->count, array( $row->type, $status ) ); } // Pending notifications (simple gauge). $pending_gauge = $collector->register_gauge( 'fedistream_notifications_pending', 'Total unread notifications', array() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $pending = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$notifications_table} WHERE is_read = 0" ); $pending_gauge->set( $pending, array() ); } /** * Collect WooCommerce metrics. * * @param object $collector The Prometheus collector. * @return void */ private function collect_woocommerce_metrics( $collector ): void { global $wpdb; $purchases_table = $wpdb->prefix . 'fedistream_purchases'; // Purchases by type. $purchases_gauge = $collector->register_gauge( 'fedistream_purchases_total', 'Total purchases by content type', array( 'type' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $purchases = $wpdb->get_results( "SELECT content_type, COUNT(*) as count FROM {$purchases_table} GROUP BY content_type" ); foreach ( $purchases as $row ) { $purchases_gauge->set( (int) $row->count, array( $row->content_type ) ); } // Unique customers. $customers_gauge = $collector->register_gauge( 'fedistream_customers_total', 'Unique customers with purchases', array() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $customers = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT user_id) FROM {$purchases_table}" ); $customers_gauge->set( $customers, array() ); // WooCommerce products count (FediStream types). if ( function_exists( 'wc_get_products' ) ) { $products_gauge = $collector->register_gauge( 'fedistream_products_total', 'FediStream products in WooCommerce', array( 'type' ) ); foreach ( array( 'fedistream_album', 'fedistream_track' ) as $type ) { $products = wc_get_products( array( 'type' => $type, 'limit' => -1, 'return' => 'ids', ) ); $products_gauge->set( count( $products ), array( $type ) ); } } } /** * Collect ActivityPub metrics. * * @param object $collector The Prometheus collector. * @return void */ private function collect_activitypub_metrics( $collector ): void { global $wpdb; $followers_table = $wpdb->prefix . 'fedistream_followers'; $reactions_table = $wpdb->prefix . 'fedistream_reactions'; // Total ActivityPub followers. $followers_gauge = $collector->register_gauge( 'fedistream_activitypub_followers_total', 'Total ActivityPub followers across all artists', array() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $total_followers = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$followers_table}" ); $followers_gauge->set( $total_followers, array() ); // Followers by artist (top 10 to avoid cardinality explosion). $followers_by_artist_gauge = $collector->register_gauge( 'fedistream_activitypub_followers_by_artist', 'Followers per artist', array( 'artist_id', 'artist_name' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $followers_by_artist = $wpdb->get_results( "SELECT artist_id, COUNT(*) as count FROM {$followers_table} GROUP BY artist_id ORDER BY count DESC LIMIT 10" ); foreach ( $followers_by_artist as $row ) { $artist = get_post( $row->artist_id ); $artist_name = $artist ? $artist->post_title : 'Unknown'; $followers_by_artist_gauge->set( (int) $row->count, array( $row->artist_id, $artist_name ) ); } // Reactions by type (if table exists). // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $reactions_table ) ); if ( $table_exists ) { $reactions_gauge = $collector->register_gauge( 'fedistream_activitypub_reactions_total', 'Fediverse reactions by type', array( 'type' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $reactions = $wpdb->get_results( "SELECT reaction_type, COUNT(*) as count FROM {$reactions_table} GROUP BY reaction_type" ); foreach ( $reactions as $row ) { $reactions_gauge->set( (int) $row->count, array( $row->reaction_type ) ); } } } /** * Check if WooCommerce integration is enabled. * * @return bool */ private function is_woocommerce_enabled(): bool { return get_option( 'wp_fedistream_enable_woocommerce', 0 ) && class_exists( 'WooCommerce' ); } /** * Check if ActivityPub integration is enabled. * * @return bool */ private function is_activitypub_enabled(): bool { return (bool) get_option( 'wp_fedistream_enable_activitypub', 1 ); } }