diff --git a/includes/class-bigquery-client.php b/includes/class-bigquery-client.php index 756b600..18ef034 100644 --- a/includes/class-bigquery-client.php +++ b/includes/class-bigquery-client.php @@ -131,10 +131,24 @@ public function push_metrics( $metrics ) { rewind( $stream ); $schema = array( 'fields' => array( - array( 'name' => 'timestamp_utc', 'type' => 'TIMESTAMP' ), + array( 'name' => 'timestamp_utc', 'type' => 'TIMESTAMP' ), + array( 'name' => 'site_url', 'type' => 'STRING' ), + // Autoload. array( 'name' => 'autoloaded_option_count', 'type' => 'INTEGER' ), - array( 'name' => 'autoloaded_option_size', 'type' => 'INTEGER' ), - array( 'name' => 'site_url', 'type' => 'STRING' ), + array( 'name' => 'autoloaded_option_size', 'type' => 'INTEGER' ), + // Plugins. + array( 'name' => 'active_plugin_count', 'type' => 'INTEGER' ), + array( 'name' => 'inactive_plugin_count', 'type' => 'INTEGER' ), + array( 'name' => 'total_plugin_count', 'type' => 'INTEGER' ), + // Hooks. + array( 'name' => 'hook_count', 'type' => 'INTEGER' ), + // Database. + array( 'name' => 'db_total_size_bytes', 'type' => 'INTEGER' ), + // WooCommerce orders. + array( 'name' => 'woo_order_items_size_bytes', 'type' => 'INTEGER' ), + array( 'name' => 'woo_order_itemmeta_size_bytes', 'type' => 'INTEGER' ), + array( 'name' => 'woo_oldest_order_age_days', 'type' => 'INTEGER' ), + array( 'name' => 'woo_archival_trigger', 'type' => 'INTEGER' ), ), ); diff --git a/includes/class-data-collector.php b/includes/class-data-collector.php index 0bc2b99..325ea61 100644 --- a/includes/class-data-collector.php +++ b/includes/class-data-collector.php @@ -20,7 +20,13 @@ class ProPerf_Data_Collector { * @return array Collected metrics. */ public function get_data() { - return $this->collect_autoloaded_options(); + return array_merge( + $this->collect_autoloaded_options(), + $this->collect_plugin_metrics(), + $this->collect_hook_metrics(), + $this->collect_db_table_metrics(), + $this->collect_woo_order_metrics() + ); } /** @@ -67,6 +73,155 @@ public function collect_autoloaded_options() { ); } + /** + * Collect plugin metrics. + * + * Counts active and inactive plugins installed on the site. + * + * @return array Plugin metrics. + */ + public function collect_plugin_metrics() { + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $all_plugins = get_plugins(); + $active_plugins = get_option( 'active_plugins', array() ); + $active_count = count( $active_plugins ); + $total_count = count( $all_plugins ); + $inactive_count = $total_count - $active_count; + + return array( + 'plugins' => array( + 'active_count' => $active_count, + 'inactive_count' => $inactive_count, + 'total_count' => $total_count, + ), + ); + } + + /** + * Collect hook metrics. + * + * Counts registered hooks via $wp_filter. + * + * @return array Hook metrics. + */ + public function collect_hook_metrics() { + global $wp_filter; + $hook_count = is_array( $wp_filter ) ? count( $wp_filter ) : 0; + + return array( + 'hooks' => array( + 'registered_count' => $hook_count, + ), + ); + } + + /** + * Collect database table size metrics. + * + * Reports total DB size and top 10 tables by size. + * + * @return array Database size metrics. + */ + public function collect_db_table_metrics() { + global $wpdb; + + $total_size = $wpdb->get_var( + 'SELECT SUM(data_length + index_length) + FROM information_schema.tables + WHERE table_schema = DATABASE()' + ); + + $top_tables = $wpdb->get_results( + 'SELECT table_name, ROUND(data_length + index_length) AS size_bytes + FROM information_schema.tables + WHERE table_schema = DATABASE() + ORDER BY size_bytes DESC LIMIT 10', + ARRAY_A + ); + + $top_tables_map = array(); + if ( $top_tables ) { + foreach ( $top_tables as $table ) { + $name = $table['table_name'] ?? $table['TABLE_NAME'] ?? ''; + $top_tables_map[ $name ] = intval( $table['size_bytes'] ?? $table['SIZE_BYTES'] ?? 0 ); + } + } + + return array( + 'database' => array( + 'total_size_bytes' => $total_size ? intval( $total_size ) : 0, + 'top_tables' => $top_tables_map, + ), + ); + } + + /** + * Collect WooCommerce order table metrics. + * + * Measures order_items and order_itemmeta table sizes, oldest order age, + * and sets an archival trigger flag when orders older than 365 days exist. + * Returns zeros with woo_active=false when WooCommerce is not installed. + * + * @return array WooCommerce order metrics. + */ + public function collect_woo_order_metrics() { + if ( ! class_exists( 'WooCommerce' ) ) { + return array( + 'woo_orders' => array( + 'woo_active' => false, + 'order_items_size_bytes' => 0, + 'order_itemmeta_size_bytes' => 0, + 'oldest_order_age_days' => 0, + 'archival_trigger' => false, + ), + ); + } + + global $wpdb; + + $order_items_table = $wpdb->prefix . 'woocommerce_order_items'; + $order_itemmeta_table = $wpdb->prefix . 'woocommerce_order_itemmeta'; + + $order_items_size = $wpdb->get_var( + $wpdb->prepare( + 'SELECT ROUND(data_length + index_length) + FROM information_schema.tables + WHERE table_schema = DATABASE() AND table_name = %s', + $order_items_table + ) + ); + + $order_itemmeta_size = $wpdb->get_var( + $wpdb->prepare( + 'SELECT ROUND(data_length + index_length) + FROM information_schema.tables + WHERE table_schema = DATABASE() AND table_name = %s', + $order_itemmeta_table + ) + ); + + $oldest_order_age_days = $wpdb->get_var( + "SELECT DATEDIFF(NOW(), MIN(post_date)) + FROM {$wpdb->posts} + WHERE post_type IN ('shop_order', 'wc_order') + AND post_status != 'trash'" + ); + $oldest_order_age_days = $oldest_order_age_days ? intval( $oldest_order_age_days ) : 0; + + return array( + 'woo_orders' => array( + 'woo_active' => true, + 'order_items_size_bytes' => $order_items_size ? intval( $order_items_size ) : 0, + 'order_itemmeta_size_bytes' => $order_itemmeta_size ? intval( $order_itemmeta_size ) : 0, + 'oldest_order_age_days' => $oldest_order_age_days, + 'archival_trigger' => $oldest_order_age_days > 365, + ), + ); + } + /** * Format metrics for BigQuery. * @@ -74,14 +229,28 @@ public function collect_autoloaded_options() { * @return array Formatted data for BigQuery. */ public function format_for_bigquery( $metrics ) { - $site_url = get_site_url(); + $site_url = get_site_url(); $timestamp = gmdate( 'Y-m-d H:i:s' ); return array( - 'timestamp_utc' => $timestamp, + 'timestamp_utc' => $timestamp, + 'site_url' => $site_url, + // Autoload. 'autoloaded_option_count' => $metrics['autoloaded_option']['count'], 'autoloaded_option_size' => $metrics['autoloaded_option']['size_bytes'], - 'site_url' => $site_url, + // Plugins. + 'active_plugin_count' => $metrics['plugins']['active_count'], + 'inactive_plugin_count' => $metrics['plugins']['inactive_count'], + 'total_plugin_count' => $metrics['plugins']['total_count'], + // Hooks. + 'hook_count' => $metrics['hooks']['registered_count'], + // Database. + 'db_total_size_bytes' => $metrics['database']['total_size_bytes'], + // WooCommerce orders. + 'woo_order_items_size_bytes' => $metrics['woo_orders']['order_items_size_bytes'], + 'woo_order_itemmeta_size_bytes' => $metrics['woo_orders']['order_itemmeta_size_bytes'], + 'woo_oldest_order_age_days' => $metrics['woo_orders']['oldest_order_age_days'], + 'woo_archival_trigger' => $metrics['woo_orders']['archival_trigger'] ? 1 : 0, ); } } diff --git a/properf-wordpress-adapter.php b/properf-wordpress-adapter.php index da9bf60..94ade3c 100644 --- a/properf-wordpress-adapter.php +++ b/properf-wordpress-adapter.php @@ -215,8 +215,12 @@ function properf_render_dashboard() { wp_die( 'Unauthorized' ); } - $metrics = properf_get_live_data(); + $metrics = properf_get_live_data(); $autoloaded_data_metrics = $metrics['autoloaded_option']; + $plugin_metrics = $metrics['plugins']; + $hook_metrics = $metrics['hooks']; + $db_metrics = $metrics['database']; + $woo_metrics = $metrics['woo_orders']; $count = $autoloaded_data_metrics['count']; $size_bytes = $autoloaded_data_metrics['size_bytes']; @@ -267,16 +271,39 @@ function properf_render_dashboard() { + + — + < 500 KB + + + + + < 15 + + + + + 0 + + + + + < 50,000 + + + + + < 10 GB @@ -302,6 +329,71 @@ function properf_render_dashboard() {

+ +

+ + + + + + + + + + $table_size ) : ?> + + + + + + +
+ +

+ + +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
< 365 days
+ + + + + +
+ array( + 'count' => 0, + 'size_bytes' => 0, + 'top_size_keys' => array(), + ), + 'plugins' => array( + 'active_count' => 0, + 'inactive_count' => 0, + 'total_count' => 0, + ), + 'hooks' => array( + 'registered_count' => 0, + ), + 'database' => array( + 'total_size_bytes' => 0, + 'top_tables' => array(), + ), + ); + if ( ! class_exists( 'ProPerf_Data_Collector' ) ) { - return array( - 'autoloaded_option' => array( - 'count' => 'Error: Collector Class Missing', - 'size_bytes' => 0, - 'top_size_keys' => array(), - ), - ); + return $empty_response; } try { @@ -590,12 +696,6 @@ function properf_get_live_data() { return $collector->get_data(); } catch ( Exception $e ) { error_log( 'ProPerf Error: ' . $e->getMessage() ); - return array( - 'autoloaded_option' => array( - 'count' => 'Error: ' . $e->getMessage(), - 'size_bytes' => 0, - 'top_size_keys' => array(), - ), - ); + return $empty_response; } }