chore: initial snapshot of 2meet-data-optimizer v0.1.0

Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
2026-07-31 05:06:36 +08:00
commit d36bb954d1
206 changed files with 66538 additions and 0 deletions
@@ -0,0 +1,288 @@
<?php
/**
* TMDO_Health_Cron — Daily health probe (v2.3.0 M6).
*
* Runs daily at 03:30 UTC. Aggregates results from:
* - TMDO_Site_Health 7 tests (schema_drift, error_budget, hook_conflicts,
* autoload_bloat, postmeta_explosion, orphan_zone_rows, missing_snapshot)
* - TMDO_Conflict_Monitor::get_summary()
* - shadow_diffs ratio per module in `verify` state
* - autoload size measurement
*
* Outputs:
* 1. Single audit log entry: op='health_check_daily' with full payload
* (so admin can read history via Logs tab + `wp wpdo audit` future CLI).
* 2. wpdo_health_alert option set when any critical found (existing
* TMDO_Core::render_health_alert_notice consumes this).
* 3. wpdo_health_last_run option for SOP runbook "is health green?" question.
* 4. action 'wpdo/health_alert_critical' fired on critical (v2.4.0 email
* notifier subscribes here; consumers get the full result array).
*
* @package WP_Data_Optimizer
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Daily probe — stateless static API.
*/
class TMDO_Health_Cron {
/** Option key for the most recent run summary. */
public const OPTION_LAST_RUN = 'wpdo_health_last_run';
/** Option key for active critical alert (consumed by Core notice). */
public const OPTION_ALERT = 'wpdo_health_alert';
/**
* Run the daily probe. Idempotent — safe to invoke ad-hoc.
*
* @return array {ok:bool, summary:array, critical_count:int, recommended_count:int, ts:string}
*/
public static function run(): array {
$started_at = microtime( true );
$results = self::run_site_health_tests();
$conflict = self::summarize_conflicts();
$shadow = self::summarize_shadow_diffs();
$autoload = self::measure_autoload_size();
$critical_count = 0;
$recommended_count = 0;
foreach ( $results as $r ) {
$status = (string) ( $r['status'] ?? 'good' );
if ( 'critical' === $status ) {
++$critical_count;
} elseif ( 'recommended' === $status ) {
++$recommended_count;
}
}
$summary = array(
'tests' => $results,
'critical_count' => $critical_count,
'recommended_count' => $recommended_count,
'conflicts' => $conflict,
'shadow_diffs' => $shadow,
'autoload_bytes' => $autoload,
'ran_at' => gmdate( 'Y-m-d H:i:s' ),
'duration_ms' => (int) round( ( microtime( true ) - $started_at ) * 1000 ),
);
// 1. Persist last-run snapshot (autoload=no, lightweight).
update_option( self::OPTION_LAST_RUN, $summary, false );
// 2. Set / clear alert flag.
if ( $critical_count > 0 ) {
$first_critical = self::first_critical_test( $results );
update_option(
self::OPTION_ALERT,
array(
'level' => 'critical',
'count' => $critical_count,
'first' => $first_critical,
'ran_at' => $summary['ran_at'],
),
false
);
} else {
delete_option( self::OPTION_ALERT );
}
// v2.5.0 M16: refresh module suggestions cache (autoload=no).
$module_suggestions_count = 0;
if ( class_exists( 'TMDO_Module_Detector' ) ) {
try {
$detected = TMDO_Module_Detector::detect_all( true );
foreach ( $detected as $r ) {
if ( ! empty( $r['available'] ) && 'enable' === ( $r['recommendation'] ?? '' ) ) {
++$module_suggestions_count;
}
}
} catch ( Throwable $e ) {
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- detector failure must not break health check.
error_log( '[WPDO] Module detector exception in health cron: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
}
}
// 3. Audit log entry.
if ( class_exists( 'TMDO_Logger' ) ) {
TMDO_Logger::info(
'health_check_daily',
array(
'critical' => $critical_count,
'recommended' => $recommended_count,
'duration_ms' => $summary['duration_ms'],
'autoload_kb' => (int) round( $autoload / 1024 ),
'conflicts' => (int) ( $conflict['total'] ?? 0 ),
'module_suggestions_count' => $module_suggestions_count,
)
);
}
// 4. Fire action for downstream subscribers (v2.4.0 email notifier).
if ( $critical_count > 0 ) {
do_action( 'wpdo/health_alert_critical', $summary );
} else {
do_action( 'wpdo/health_check_passed', $summary );
}
return array(
'ok' => true,
'summary' => $summary,
'critical_count' => $critical_count,
'recommended_count' => $recommended_count,
'ts' => $summary['ran_at'],
);
}
/**
* Read most recent run (for SOP runbook + Doctor tab streak counter).
*
* @return array|null
*/
public static function get_last_run(): ?array {
$v = get_option( self::OPTION_LAST_RUN );
return is_array( $v ) ? $v : null;
}
/**
* Compute consecutive green days from audit log (best-effort for SOP UI).
*
* @return int
*/
public static function consecutive_green_days(): int {
$last = self::get_last_run();
if ( null === $last ) {
return 0;
}
// If today's run is critical, streak = 0.
if ( ( $last['critical_count'] ?? 0 ) > 0 ) {
return 0;
}
// Otherwise approximate via TMDO_Logger — count distinct days with health_check_daily and 0 critical.
// Conservative best-effort: just check today's run is green = 1 day.
return 1;
}
// ─── private helpers ────────────────────────────────────────────────
/**
* Run all 7 Site Health tests directly (without the WP Site Health UI loop).
*
* @return array<string,array> Test slug → result array.
*/
private static function run_site_health_tests(): array {
$out = array();
if ( ! class_exists( 'TMDO_Site_Health' ) ) {
return $out;
}
$tests = array(
'wpdo_schema_drift' => 'check_schema_drift',
'wpdo_error_budget' => 'check_error_budget',
'wpdo_hook_conflicts' => 'check_hook_conflicts',
'wpdo_autoload_bloat' => 'check_autoload_bloat',
'wpdo_postmeta_explosion' => 'check_postmeta_explosion',
'wpdo_orphan_zone_rows' => 'check_orphan_zone_rows',
'wpdo_missing_snapshot' => 'check_missing_snapshot',
);
foreach ( $tests as $slug => $cb ) {
try {
$result = call_user_func( array( 'TMDO_Site_Health', $cb ) );
if ( is_array( $result ) ) {
$out[ $slug ] = array(
'status' => (string) ( $result['status'] ?? 'good' ),
'severity' => (string) ( $result['severity'] ?? 'good' ),
'label' => (string) ( $result['label'] ?? $slug ),
'description' => wp_strip_all_tags( (string) ( $result['description'] ?? '' ) ),
);
}
} catch ( Throwable $e ) {
$out[ $slug ] = array(
'status' => 'critical',
'severity' => 'critical',
'label' => $slug,
'description' => 'test threw: ' . $e->getMessage(),
);
}
}
return $out;
}
/**
* Return a conflict summary from TMDO_Conflict_Monitor.
*
* @return array {total:int, hook_overlap:int, uaepg_overlap:int}
*/
private static function summarize_conflicts(): array {
if ( ! class_exists( 'TMDO_Conflict_Monitor' ) ) {
return array(
'total' => 0,
'hook_overlap' => 0,
'uaepg_overlap' => 0,
);
}
$summary = TMDO_Conflict_Monitor::get_summary();
return array(
'total' => (int) ( $summary['total'] ?? 0 ),
'hook_overlap' => (int) ( $summary['hook_overlap'] ?? 0 ),
'uaepg_overlap' => (int) ( $summary['uaepg_overlap'] ?? 0 ),
);
}
/**
* Per-module shadow_diffs ratio (only for modules currently in `verify`).
* Reads wp_wpdo_shadow_diffs and bucket-counts by entity_type.
*
* @return array<string,array>
*/
private static function summarize_shadow_diffs(): array {
global $wpdb;
$out = array();
$table = $wpdb->prefix . 'wpdo_shadow_diffs';
$exists = (int) $wpdb->get_var(
$wpdb->prepare( // phpcs:ignore WordPress.DB
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
$table
)
);
if ( 0 === $exists ) {
return $out;
}
$rows = $wpdb->get_results( "SELECT entity_type, COUNT(*) AS cnt FROM `{$table}` WHERE ts >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 24 HOUR) GROUP BY entity_type", ARRAY_A ); // phpcs:ignore WordPress.DB
if ( is_array( $rows ) ) {
foreach ( $rows as $r ) {
$out[ (string) $r['entity_type'] ] = array(
'diffs_24h' => (int) $r['cnt'],
);
}
}
return $out;
}
/**
* Return the total byte size of autoloaded options.
*
* @return int Bytes in autoloaded options.
*/
private static function measure_autoload_size(): int {
global $wpdb;
return (int) $wpdb->get_var( "SELECT COALESCE(SUM(LENGTH(option_value)),0) FROM `{$wpdb->options}` WHERE autoload = 'yes'" ); // phpcs:ignore WordPress.DB
}
/**
* Return the slug of the first critical test result, or null if none.
*
* @param array $results Site Health test results map.
* @return string|null Slug of first critical test, or null.
*/
private static function first_critical_test( array $results ): ?string {
foreach ( $results as $slug => $r ) {
if ( 'critical' === ( $r['status'] ?? '' ) ) {
return $slug;
}
}
return null;
}
}
@@ -0,0 +1,283 @@
<?php
/**
* TMDO_Monthly_Summary — 30-day rollup report (v2.4.0 M11).
*
* Generates a monthly executive summary covering
* - Health success / fail / rate-limit ratios over last 30 days
* - Zone size growth (deltas from 30 days ago snapshot if available)
* - Per-module FSM trajectory (who promoted, who rolled back)
* - Snapshot retention overview (count, oldest, newest)
*
* Hooks the existing `wpdo_health_snapshot_monthly` action (Core line ~489)
* so it runs once per month at 03:00 UTC on the 1st. Output
* 1. Persisted to wp_wpdo_audit (op='monthly_summary').
* 2. Stored as wp_options.wpdo_monthly_summary_latest (autoload=no).
* 3. Rendered on the admin Doctor tab "📅 Monthly Summary" sub-section.
*
* @package WP_Data_Optimizer
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Monthly summary aggregator.
*/
class TMDO_Monthly_Summary {
public const OPTION_LATEST = 'wpdo_monthly_summary_latest';
/**
* Hook into Core's existing monthly cron.
*
* @return void
*/
public static function register(): void {
add_action( 'wpdo_health_snapshot_monthly', array( __CLASS__, 'generate' ), 20 );
}
/**
* Build + persist the monthly summary.
*
* @return array Summary structure.
*/
public static function generate(): array {
$started_at = microtime( true );
$summary = array(
'period_start' => gmdate( 'Y-m-d H:i:s', strtotime( '-30 days' ) ),
'period_end' => gmdate( 'Y-m-d H:i:s' ),
'health' => self::aggregate_health(),
'fsm_trajectory' => self::aggregate_fsm(),
'snapshots' => self::aggregate_snapshots(),
'zone_growth' => self::aggregate_zone_growth(),
'autoload_size' => self::measure_autoload(),
'duration_ms' => 0,
'generated_at' => gmdate( 'Y-m-d H:i:s' ),
);
$summary['duration_ms'] = (int) round( ( microtime( true ) - $started_at ) * 1000 );
update_option( self::OPTION_LATEST, $summary, false );
// v2.5.0 M14: archive into history (max 12 entries).
$history = (array) get_option( 'wpdo_monthly_summary_history', array() );
array_unshift( $history, $summary );
$history = array_slice( $history, 0, 12 );
update_option( 'wpdo_monthly_summary_history', $history, false );
if ( class_exists( 'TMDO_Logger' ) ) {
TMDO_Logger::info(
'monthly_summary',
array(
'health_success' => (int) ( $summary['health']['success'] ?? 0 ),
'health_critical' => (int) ( $summary['health']['critical'] ?? 0 ),
'fsm_promotions' => count( $summary['fsm_trajectory']['promotions'] ?? array() ),
'fsm_rollbacks' => count( $summary['fsm_trajectory']['rollbacks'] ?? array() ),
'snapshots_total' => (int) ( $summary['snapshots']['total'] ?? 0 ),
'autoload_kb' => (int) round( ( $summary['autoload_size'] ?? 0 ) / 1024 ),
)
);
}
do_action( 'wpdo/monthly_summary_generated', $summary );
return $summary;
}
/**
* Read the latest summary (from wp_options).
*
* @return array|null
*/
public static function get_latest(): ?array {
$v = get_option( self::OPTION_LATEST );
return is_array( $v ) ? $v : null;
}
// ─── private ────────────────────────────────────────────────────
/**
* Aggregate health-check audit log entries from last 30 days.
*
* @return array {success:int, recommended:int, critical:int, total:int}
*/
private static function aggregate_health(): array {
global $wpdb;
$out = array(
'success' => 0,
'recommended' => 0,
'critical' => 0,
'total' => 0,
);
// wp_wpdo_audit may not exist on early v1.x installs; check first.
$audit = $wpdb->prefix . 'wpdo_audit';
$exists = (int) $wpdb->get_var(
$wpdb->prepare( // phpcs:ignore WordPress.DB
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
$audit
)
);
if ( 0 === $exists ) {
return $out;
}
// Health check audit rows have op='health_check_daily'.
$rows = (array) $wpdb->get_results( "SELECT * FROM `{$audit}` WHERE op = 'health_check_daily' AND ts >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 30 DAY)", ARRAY_A ); // phpcs:ignore WordPress.DB
foreach ( $rows as $r ) {
$ctx = isset( $r['value_after'] ) ? json_decode( (string) $r['value_after'], true ) : null;
if ( ! is_array( $ctx ) ) {
continue;
}
$crit = (int) ( $ctx['critical'] ?? 0 );
$rec = (int) ( $ctx['recommended'] ?? 0 );
++$out['total'];
if ( $crit > 0 ) {
++$out['critical'];
} elseif ( $rec > 0 ) {
++$out['recommended'];
} else {
++$out['success'];
}
}
return $out;
}
/**
* Best-effort FSM trajectory: which modules changed state in the last 30
* days. Reads `wpdo_fsm_state_entered` + current state.
*
* @return array {promotions:array, rollbacks:array, stationary:array}
*/
private static function aggregate_fsm(): array {
$out = array(
'promotions' => array(),
'rollbacks' => array(),
'stationary' => array(),
);
if ( ! class_exists( 'TMDO_Feature_Flags' ) ) {
return $out;
}
$entered = (array) get_option( 'wpdo_fsm_state_entered', array() );
$now = time();
foreach ( TMDO_Feature_Flags::all() as $module => $state ) {
$ts = isset( $entered[ $module ]['entered_at'] ) ? strtotime( (string) $entered[ $module ]['entered_at'] . ' UTC' ) : 0;
$age_days = $ts > 0 ? (int) floor( ( $now - $ts ) / DAY_IN_SECONDS ) : null;
if ( $ts > 0 && ( $now - $ts ) <= ( 30 * DAY_IN_SECONDS ) ) {
// Recently changed — bucket by direction.
if ( 'idle' === $state ) {
$out['rollbacks'][ $module ] = array(
'state' => $state,
'days_in_state' => $age_days,
);
} else {
$out['promotions'][ $module ] = array(
'state' => $state,
'days_in_state' => $age_days,
);
}
} else {
$out['stationary'][ $module ] = array(
'state' => $state,
'days_in_state' => $age_days,
);
}
}
return $out;
}
/**
* Aggregate snapshot retention stats from the snapshots table.
*
* @return array {total:int, oldest:string|null, newest:string|null, total_bytes:int}
*/
private static function aggregate_snapshots(): array {
global $wpdb;
$table = $wpdb->prefix . 'wpdo_snapshots';
$exists = (int) $wpdb->get_var(
$wpdb->prepare( // phpcs:ignore WordPress.DB
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
$table
)
);
if ( 0 === $exists ) {
return array(
'total' => 0,
'oldest' => null,
'newest' => null,
'total_bytes' => 0,
);
}
$row = $wpdb->get_row( "SELECT COUNT(*) AS c, MIN(created_at) AS oldest, MAX(created_at) AS newest, COALESCE(SUM(size_bytes),0) AS bytes FROM `{$table}`", ARRAY_A ); // phpcs:ignore WordPress.DB
return array(
'total' => (int) ( $row['c'] ?? 0 ),
'oldest' => isset( $row['oldest'] ) ? (string) $row['oldest'] : null,
'newest' => isset( $row['newest'] ) ? (string) $row['newest'] : null,
'total_bytes' => (int) ( $row['bytes'] ?? 0 ),
);
}
/**
* Per-zone row count snapshot. Compares to previous month's value when
* available (delta_rows positive = growth).
*
* @return array<string,array>
*/
private static function aggregate_zone_growth(): array {
// Use historical site_metrics when available — avoids live COUNT(*) on large tables
// and provides a 30-day delta (current oldest snapshot).
if ( class_exists( 'TMDO_Site_Metrics_Collector' ) ) {
$latest = TMDO_Site_Metrics_Collector::get_latest_snapshot();
if ( ! empty( $latest ) ) {
// Get oldest snapshot within 30 days for delta calculation.
$keys_of_interest = array( 'eav.postmeta_rows', 'flat.hot_rows', 'flat.cold_rows', 'flat.warm_rows', 'custom_tables.total_rows' );
$out = array();
foreach ( $keys_of_interest as $mk ) {
if ( ! isset( $latest[ $mk ] ) ) {
continue;
}
$history = TMDO_Site_Metrics_Collector::get_history( $mk, 30 );
$oldest = ! empty( $history ) ? (int) $history[0]['value'] : null;
$current = (int) $latest[ $mk ];
$out[ $mk ] = array(
'rows' => $current,
'delta_rows' => null !== $oldest ? $current - $oldest : null,
);
}
return $out;
}
}
// Fallback: live COUNT(*) for warm + archive tables (pre-v2.6.2 installs).
global $wpdb;
$out = array();
$keys = array( 'wpdo_warm', 'wpdo_archive' );
foreach ( $keys as $slug ) {
$table = $wpdb->prefix . $slug;
$exists = (int) $wpdb->get_var(
$wpdb->prepare( // phpcs:ignore WordPress.DB
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
$table
)
);
if ( 0 === $exists ) {
continue;
}
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:ignore WordPress.DB
$out[ $slug ] = array(
'rows' => $count,
'delta_rows' => null,
);
}
return $out;
}
/**
* Return the total byte size of autoloaded options.
*
* @return int Bytes in autoloaded options.
*/
private static function measure_autoload(): int {
global $wpdb;
return (int) $wpdb->get_var( "SELECT COALESCE(SUM(LENGTH(option_value)),0) FROM `{$wpdb->options}` WHERE autoload = 'yes'" ); // phpcs:ignore WordPress.DB
}
}
@@ -0,0 +1,491 @@
<?php
// phpcs:ignore WPDO.AntiEAV -- platform diagnostic: raw meta inspection for site health
/**
* TMDO_Site_Health — WordPress Site Health integration (v2.2.0 M3).
*
* Registers 7 tests under Tools → Site Health → Status:
* 1. wpdo_schema_drift (critical)
* 2. wpdo_error_budget (recommended)
* 3. wpdo_hook_conflicts (recommended)
* 4. wpdo_autoload_bloat (recommended)
* 5. wpdo_postmeta_explosion (recommended)
* 6. wpdo_orphan_zone_rows (recommended)
* 7. wpdo_missing_snapshot (critical)
*
* Each test result is cached for 5 minutes to keep Site Health responsive.
*
* @package WP_Data_Optimizer
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Site Health test suite. Hook on `init` admin context.
*/
class TMDO_Site_Health {
/** Transient cache TTL for individual checks. */
private const CACHE_TTL = 300;
/** Test name → callable suffix mapping. */
private const TESTS = array(
'wpdo_schema_drift' => 'check_schema_drift',
'wpdo_error_budget' => 'check_error_budget',
'wpdo_hook_conflicts' => 'check_hook_conflicts',
'wpdo_autoload_bloat' => 'check_autoload_bloat',
'wpdo_postmeta_explosion' => 'check_postmeta_explosion',
'wpdo_orphan_zone_rows' => 'check_orphan_zone_rows',
'wpdo_missing_snapshot' => 'check_missing_snapshot',
);
/**
* Hook into Site Health.
*
* @return void
*/
public static function register(): void {
add_filter( 'site_status_tests', array( __CLASS__, 'register_tests' ) );
}
/**
* Register the 7 tests with WP Site Health.
*
* @param array $tests Existing tests.
* @return array
*/
public static function register_tests( array $tests ): array {
foreach ( self::TESTS as $key => $cb_suffix ) {
$tests['direct'][ $key ] = array(
'label' => self::label_for( $key ),
'test' => array( __CLASS__, $cb_suffix ),
);
}
return $tests;
}
/**
* Human-readable label for each test.
*
* @param string $key Test slug.
* @return string
*/
private static function label_for( string $key ): string {
$map = array(
'wpdo_schema_drift' => __( 'WPDO schema drift', '2meet-data-optimizer' ),
'wpdo_error_budget' => __( 'WPDO error budget', '2meet-data-optimizer' ),
'wpdo_hook_conflicts' => __( 'WPDO hook conflicts', '2meet-data-optimizer' ),
'wpdo_autoload_bloat' => __( 'WPDO autoload bloat', '2meet-data-optimizer' ),
'wpdo_postmeta_explosion' => __( 'WPDO postmeta explosion', '2meet-data-optimizer' ),
'wpdo_orphan_zone_rows' => __( 'WPDO orphan zone rows', '2meet-data-optimizer' ),
'wpdo_missing_snapshot' => __( 'WPDO missing snapshot', '2meet-data-optimizer' ),
);
return $map[ $key ] ?? $key;
}
// ─── Test 1: Schema drift ─────────────────────────────────────────────
/**
* Verify all expected v2 tables exist.
*
* @return array Site Health result.
*/
public static function check_schema_drift(): array {
$result = self::cached(
'wpdo_sh_schema_drift',
static function () {
if ( ! class_exists( 'TMDO_Installer' ) ) {
return self::pass( __( 'WPDO installer not loaded.', '2meet-data-optimizer' ) );
}
if ( ! method_exists( 'TMDO_Installer', 'v2_tables_status' ) ) {
return self::pass( __( 'Schema check unavailable on this version.', '2meet-data-optimizer' ) );
}
$status = TMDO_Installer::v2_tables_status();
$missing = array_keys( array_filter( $status, static fn( $exists ) => ! $exists ) );
if ( empty( $missing ) ) {
return self::pass( __( 'All WPDO v2 tables exist.', '2meet-data-optimizer' ) );
}
return self::fail(
__( 'WPDO v2 tables missing', '2meet-data-optimizer' ),
sprintf(
/* translators: %s: comma-separated list of missing table names */
__( 'Missing tables: %s. Run wp wpdo install or re-activate the plugin.', '2meet-data-optimizer' ),
implode( ', ', $missing )
),
'critical'
);
}
);
return self::wrap( 'wpdo_schema_drift', $result );
}
// ─── Test 2: Error budget (last 7 days) ──────────────────────────────
/**
* Count errors in wp_wpdo_errors over the last 7 days.
*
* @return array Site Health result.
*/
public static function check_error_budget(): array {
$result = self::cached(
'wpdo_sh_error_budget',
static function () {
global $wpdb;
$table = $wpdb->prefix . 'wpdo_errors';
$exists = (int) $wpdb->get_var(
$wpdb->prepare( // phpcs:ignore WordPress.DB
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
$table
)
);
if ( 0 === $exists ) {
return self::pass( __( 'Error log not present (yet) — clean.', '2meet-data-optimizer' ) );
}
$threshold = (int) apply_filters( 'wpdo/site_health/error_budget_threshold', 100 );
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}` WHERE created_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 7 DAY)" ); // phpcs:ignore WordPress.DB
if ( $count <= $threshold ) {
return self::pass(
sprintf(
/* translators: %d: error count */
__( '%d errors in the last 7 days (within budget).', '2meet-data-optimizer' ),
$count
)
);
}
return self::fail(
__( 'WPDO error budget exceeded', '2meet-data-optimizer' ),
sprintf(
/* translators: 1: error count, 2: threshold */
__( '%1$d errors in the last 7 days (threshold %2$d). Inspect under Tools → WP Data Optimizer → Logs.', '2meet-data-optimizer' ),
$count,
$threshold
),
'recommended'
);
}
);
return self::wrap( 'wpdo_error_budget', $result );
}
// ─── Test 3: Hook conflicts ──────────────────────────────────────────
/**
* Check for interceptor or hook conflicts via TMDO_Conflict_Monitor.
*
* @return array Site Health result.
*/
public static function check_hook_conflicts(): array {
$result = self::cached(
'wpdo_sh_hook_conflicts',
static function () {
if ( ! class_exists( 'TMDO_Conflict_Monitor' ) ) {
return self::pass( __( 'Conflict monitor not loaded.', '2meet-data-optimizer' ) );
}
$summary = TMDO_Conflict_Monitor::get_summary();
$total = (int) ( $summary['total'] ?? 0 );
if ( 0 === $total ) {
return self::pass( __( 'No interceptor / hook conflicts detected.', '2meet-data-optimizer' ) );
}
return self::fail(
__( 'WPDO hook conflicts detected', '2meet-data-optimizer' ),
sprintf(
/* translators: %d: number of conflicts */
__( '%d interceptor/hook conflicts detected. Run wp wpdo conflict-scan for details.', '2meet-data-optimizer' ),
$total
),
'recommended'
);
}
);
return self::wrap( 'wpdo_hook_conflicts', $result );
}
// ─── Test 4: Autoload bloat ──────────────────────────────────────────
/**
* Check total autoloaded options size against a configurable threshold.
*
* @return array Site Health result.
*/
public static function check_autoload_bloat(): array {
$result = self::cached(
'wpdo_sh_autoload_bloat',
static function () {
global $wpdb;
$threshold_mb = (int) apply_filters( 'wpdo/site_health/autoload_threshold_mb', 5 );
$bytes = (int) $wpdb->get_var( "SELECT SUM(LENGTH(option_value)) FROM `{$wpdb->options}` WHERE autoload = 'yes'" ); // phpcs:ignore WordPress.DB
$mb = $bytes / 1024 / 1024;
if ( $mb < $threshold_mb ) {
return self::pass(
sprintf(
/* translators: 1: actual size in MB */
__( 'Autoload total %1$0.2f MB (under %2$d MB threshold).', '2meet-data-optimizer' ),
$mb,
$threshold_mb
)
);
}
return self::fail(
__( 'Autoload size large', '2meet-data-optimizer' ),
sprintf(
/* translators: 1: actual size in MB, 2: threshold in MB */
__( 'Autoload total %1$0.2f MB exceeds %2$d MB threshold. Consider migrating large autoloaded options to wp_wpdo_uni_options.', '2meet-data-optimizer' ),
$mb,
$threshold_mb
),
'recommended'
);
}
);
return self::wrap( 'wpdo_autoload_bloat', $result );
}
// ─── Test 5: Postmeta explosion ──────────────────────────────────────
/**
* Check whether wp_postmeta row count exceeds the explosion threshold.
*
* @return array Site Health result.
*/
public static function check_postmeta_explosion(): array {
$result = self::cached(
'wpdo_sh_postmeta_explosion',
static function () {
global $wpdb;
$threshold = (int) apply_filters( 'wpdo/site_health/postmeta_threshold', 5_000_000 );
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$wpdb->postmeta}`" ); // phpcs:ignore WordPress.DB
if ( $count < $threshold ) {
return self::pass(
sprintf(
/* translators: %s: row count */
__( 'wp_postmeta has %s rows (under threshold).', '2meet-data-optimizer' ),
number_format_i18n( $count )
)
);
}
// Also check if any zone module is active.
$any_active = false;
if ( class_exists( 'TMDO_Feature_Flags' ) ) {
foreach ( TMDO_Feature_Flags::all() as $state ) {
if ( 'idle' !== $state ) {
$any_active = true;
break;
}
}
}
$severity = $any_active ? 'recommended' : 'critical';
return self::fail(
__( 'wp_postmeta is large', '2meet-data-optimizer' ),
sprintf(
/* translators: %s: row count */
__( 'wp_postmeta has %s rows. Run the Classifier to identify candidates for migration into Hot/Warm/Cold/Archive zones.', '2meet-data-optimizer' ),
number_format_i18n( $count )
),
$severity
);
}
);
return self::wrap( 'wpdo_postmeta_explosion', $result );
}
// ─── Test 6: Orphan zone rows ────────────────────────────────────────
/**
* Detect zone table rows that belong to modules currently in idle state.
*
* @return array Site Health result.
*/
public static function check_orphan_zone_rows(): array {
$result = self::cached(
'wpdo_sh_orphan_zone',
static function () {
global $wpdb;
if ( ! class_exists( 'TMDO_Feature_Flags' ) ) {
return self::pass( __( 'Feature flags not loaded.', '2meet-data-optimizer' ) );
}
$idle_modules = array_keys( array_filter( TMDO_Feature_Flags::all(), static fn( $state ) => 'idle' === $state ) );
$orphans = array();
foreach ( $idle_modules as $module ) {
// Best-effort: zone tables for hot_*/cold_* are dynamically named.
$candidates = array(
$wpdb->prefix . 'wpdo_warm',
$wpdb->prefix . 'wpdo_archive',
);
foreach ( $candidates as $table ) {
$exists = (int) $wpdb->get_var(
$wpdb->prepare( // phpcs:ignore WordPress.DB
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
$table
)
);
if ( 0 === $exists ) {
continue;
}
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:ignore WordPress.DB
if ( $count > 0 ) {
$orphans[ $table ] = $count;
}
}
}
if ( empty( $orphans ) ) {
return self::pass( __( 'No orphan zone rows from idle modules.', '2meet-data-optimizer' ) );
}
$lines = array();
foreach ( $orphans as $t => $n ) {
$lines[] = sprintf( '%s (%s rows)', $t, number_format_i18n( $n ) );
}
return self::fail(
__( 'Orphan zone rows detected', '2meet-data-optimizer' ),
sprintf(
/* translators: %s: list of tables and row counts */
__( 'Modules in idle state but zone tables still hold data: %s. These rows are typically cleanup leftovers — verify before truncating.', '2meet-data-optimizer' ),
implode( ', ', $lines )
),
'recommended'
);
}
);
return self::wrap( 'wpdo_orphan_zone_rows', $result );
}
// ─── Test 7: Missing snapshot ────────────────────────────────────────
/**
* Verify that a recent snapshot exists when modules are in risk states.
*
* @return array Site Health result.
*/
public static function check_missing_snapshot(): array {
$result = self::cached(
'wpdo_sh_missing_snapshot',
static function () {
global $wpdb;
if ( ! class_exists( 'TMDO_Snapshot_Manager' ) || ! class_exists( 'TMDO_Feature_Flags' ) ) {
return self::pass( __( 'Snapshot system not yet available.', '2meet-data-optimizer' ) );
}
// Risk only applies to modules in cutover/cleanup/complete (data is in custom tables).
$risk_modules = array_keys(
array_filter(
TMDO_Feature_Flags::all(),
static fn( $state ) => in_array( $state, array( 'cutover', 'cleanup', 'complete' ), true )
)
);
if ( empty( $risk_modules ) ) {
return self::pass( __( 'No modules in risk state — snapshot not required.', '2meet-data-optimizer' ) );
}
$snap_table = $wpdb->prefix . TMDO_Snapshot_Manager::TABLE_SLUG;
$exists = (int) $wpdb->get_var(
$wpdb->prepare( // phpcs:ignore WordPress.DB
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
$snap_table
)
);
if ( 0 === $exists ) {
return self::fail(
__( 'Snapshot table missing', '2meet-data-optimizer' ),
__( 'Snapshot system table not present. Run wp wpdo install.', '2meet-data-optimizer' ),
'critical'
);
}
$days = (int) apply_filters( 'wpdo/site_health/snapshot_max_age_days', 7 );
$recent = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM `{$snap_table}` WHERE created_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- {$snap_table} is $wpdb->prefix + TABLE_SLUG constant (no user input)
$days
)
);
if ( $recent > 0 ) {
return self::pass(
sprintf(
/* translators: 1: count, 2: days */
__( 'Found %1$d snapshot(s) within the last %2$d days.', '2meet-data-optimizer' ),
$recent,
$days
)
);
}
return self::fail(
__( 'No recent WPDO snapshot', '2meet-data-optimizer' ),
sprintf(
/* translators: 1: comma-separated module names, 2: days */
__( 'Modules in risk state (%1$s) but no snapshot in last %2$d days. Run: wp wpdo snapshot create --trigger=manual --notes="catch-up safety net"', '2meet-data-optimizer' ),
implode( ', ', $risk_modules ),
$days
),
'critical'
);
}
);
return self::wrap( 'wpdo_missing_snapshot', $result );
}
// ─── helpers ──────────────────────────────────────────────────────────
/**
* Wrap a raw check result with required Site Health envelope fields.
*
* @param string $key Test slug.
* @param array $result Raw result with keys label, status, description, severity.
* @return array
*/
private static function wrap( string $key, array $result ): array {
$result['test'] = $key;
$result['badge'] = array(
'label' => 'WPDO',
'color' => 'blue',
);
return $result;
}
/**
* Cache a callable's return value via transient.
*
* @param string $key Transient key.
* @param callable $producer Callable returning result array.
* @return array
*/
private static function cached( string $key, callable $producer ): array {
$cached = get_transient( $key );
if ( is_array( $cached ) ) {
return $cached;
}
$result = $producer();
set_transient( $key, $result, self::CACHE_TTL );
return $result;
}
/**
* Build a "pass" result envelope.
*
* @param string $description Body text.
* @return array
*/
private static function pass( string $description ): array {
return array(
'label' => __( 'WPDO check passed', '2meet-data-optimizer' ),
'status' => 'good',
'description' => '<p>' . esc_html( $description ) . '</p>',
'severity' => 'good',
'actions' => '',
);
}
/**
* Build a fail / warning result envelope.
*
* @param string $label Test heading.
* @param string $description Body.
* @param string $severity 'critical' | 'recommended'.
* @return array
*/
private static function fail( string $label, string $description, string $severity ): array {
return array(
'label' => $label,
'status' => 'critical' === $severity ? 'critical' : 'recommended',
'description' => '<p>' . esc_html( $description ) . '</p>',
'severity' => $severity,
'actions' => '<p><a href="' . esc_url( admin_url( 'tools.php?page=wp-data-optimizer' ) ) . '">' . esc_html__( 'Open WPDO admin', '2meet-data-optimizer' ) . '</a></p>',
);
}
}
@@ -0,0 +1,236 @@
<?php
/**
* TMDO_Site_Metrics_Collector — daily EAV health snapshot writer.
*
* Writes structured rows to `wpdo_site_metrics` once per day (via
* `wpdo_collect_site_metrics` cron action, scheduled at 05:00 UTC).
*
* Metric keys written per run:
* eav.postmeta_rows / eav.usermeta_rows / eav.termmeta_rows / eav.commentmeta_rows
* flat.hot_rows / flat.cold_rows / flat.warm_rows
* custom_tables.total_rows / custom_tables.table_count
* errors.last_24h / shadow_diffs.last_24h
*
* Monthly Summary (`TMDO_Monthly_Summary`) reads these rows for the
* `zone_growth` section instead of querying live tables, so the monthly
* rollup is fast even on large databases.
*
* @package WP_Data_Optimizer
* @since 2.6.2
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Collects site-wide EAV health metrics and writes to wpdo_site_metrics.
*/
class TMDO_Site_Metrics_Collector {
/**
* Cron hook name.
*
* @var string
*/
public const CRON_HOOK = 'wpdo_collect_site_metrics';
/**
* How many days of daily rows to retain before pruning.
*
* @var int
*/
private const RETENTION_DAYS = 90;
/**
* Register the cron handler and return the instance for chaining.
*
* @return void
*/
public static function register(): void {
add_action( self::CRON_HOOK, array( __CLASS__, 'collect' ) );
}
/**
* Collect all site metrics and persist them to wpdo_site_metrics.
*
* Called by the daily cron. Safe to call manually (e.g., via WP-CLI).
*
* @param bool $dry_run When true, collect but do not write to the DB.
* @return array<string,int> Map of metric_key => metric_value collected.
*/
public static function collect( bool $dry_run = false ): array {
global $wpdb;
$now = TMDO_DB::now();
$metrics = array();
// ── EAV row counts ────────────────────────────────────────────────
$eav_tables = array(
'eav.postmeta_rows' => $wpdb->postmeta,
'eav.usermeta_rows' => $wpdb->usermeta,
'eav.termmeta_rows' => $wpdb->termmeta,
'eav.commentmeta_rows' => $wpdb->commentmeta,
);
foreach ( $eav_tables as $key => $table ) {
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:ignore WordPress.DB
$metrics[ $key ] = $count;
}
// ── Flat-table row counts (hot + cold dynamic tables, warm) ───────
$hot_rows = 0;
$cold_rows = 0;
if ( class_exists( 'TMDO_Schema_Registry' ) ) {
$registry = TMDO_Schema_Registry::instance();
foreach ( $registry->get_hot_post_types() as $pt ) {
$ht = $wpdb->prefix . 'wpdo_hot_' . sanitize_key( $pt );
$exists = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $ht ) ); // phpcs:ignore WordPress.DB
if ( $exists ) {
$hot_rows += (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$ht}`" ); // phpcs:ignore WordPress.DB
}
}
foreach ( $registry->get_cold_post_types() as $pt ) {
$ct = $wpdb->prefix . 'wpdo_cold_' . sanitize_key( $pt );
$exists = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $ct ) ); // phpcs:ignore WordPress.DB
if ( $exists ) {
$cold_rows += (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$ct}`" ); // phpcs:ignore WordPress.DB
}
}
}
$warm_table = $wpdb->prefix . 'wpdo_warm';
$metrics['flat.hot_rows'] = $hot_rows;
$metrics['flat.cold_rows'] = $cold_rows;
$metrics['flat.warm_rows'] = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$warm_table}`" ); // phpcs:ignore WordPress.DB
// ── Custom table row counts ───────────────────────────────────────
$custom_rows = 0;
$custom_count = 0;
if ( class_exists( 'TMDO_Custom_Table_Registry' ) ) {
foreach ( TMDO_Custom_Table_Registry::instance()->all() as $cfg ) {
$tbl = $wpdb->prefix . $cfg['table_name'];
$exists = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $tbl ) ); // phpcs:ignore WordPress.DB
if ( $exists ) {
$custom_rows += (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$tbl}`" ); // phpcs:ignore WordPress.DB
++$custom_count;
}
}
}
$metrics['custom_tables.total_rows'] = $custom_rows;
$metrics['custom_tables.table_count'] = $custom_count;
// ── Error / shadow-diff activity (last 24 h) ─────────────────────
$errors_table = $wpdb->prefix . 'wpdo_errors';
$metrics['errors.last_24h'] = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM `{$errors_table}` WHERE created_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 24 HOUR)" // phpcs:ignore WordPress.DB
);
$shadow_table = $wpdb->prefix . 'wpdo_shadow_diffs';
$shadow_exists = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $shadow_table ) ); // phpcs:ignore WordPress.DB
$metrics['shadow_diffs.last_24h'] = $shadow_exists
? (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$shadow_table}` WHERE ts >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 24 HOUR)" ) // phpcs:ignore WordPress.DB
: 0;
if ( $dry_run ) {
return $metrics;
}
// ── Persist each metric to wpdo_site_metrics ─────────────────────
$dest = TMDO_DB::table( 'wpdo_site_metrics' );
foreach ( $metrics as $key => $value ) {
$wpdb->insert(
$dest,
array(
'collected_at' => $now,
'metric_key' => $key,
'metric_value' => $value,
'context' => null,
),
array( '%s', '%s', '%d', '%s' )
);
}
// ── Prune old rows beyond retention window ────────────────────────
$cutoff = gmdate( 'Y-m-d H:i:s', strtotime( '-' . self::RETENTION_DAYS . ' days' ) );
$wpdb->query( $wpdb->prepare( "DELETE FROM {$dest} WHERE collected_at < %s", $cutoff ) ); // phpcs:ignore WordPress.DB
if ( class_exists( 'TMDO_Logger' ) ) {
TMDO_Logger::info(
'site_metrics_collected',
array(
'metric_count' => count( $metrics ),
'postmeta_rows' => $metrics['eav.postmeta_rows'] ?? 0,
'hot_rows' => $metrics['flat.hot_rows'] ?? 0,
'custom_rows' => $metrics['custom_tables.total_rows'] ?? 0,
)
);
}
return $metrics;
}
/**
* Get the most recent snapshot (latest collected_at timestamp).
*
* @return array<string,int> metric_key => metric_value, or empty on miss.
*/
public static function get_latest_snapshot(): array {
global $wpdb;
$dest = TMDO_DB::table( 'wpdo_site_metrics' );
$latest_ts = $wpdb->get_var( "SELECT MAX(collected_at) FROM `{$dest}`" ); // phpcs:ignore WordPress.DB
if ( ! $latest_ts ) {
return array();
}
$rows = (array) $wpdb->get_results(
$wpdb->prepare(
"SELECT metric_key, metric_value FROM `{$dest}` WHERE collected_at = %s", // phpcs:ignore WordPress.DB
$latest_ts
),
ARRAY_A
);
$out = array();
foreach ( $rows as $r ) {
$out[ (string) $r['metric_key'] ] = (int) $r['metric_value'];
}
return $out;
}
/**
* Get daily metric history for a single key over the past N days.
*
* @param string $metric_key Metric key (e.g. 'eav.postmeta_rows').
* @param int $days Number of days of history to return (default 30).
* @return array<array{collected_at:string,value:int}> Oldest-first.
*/
public static function get_history( string $metric_key, int $days = 30 ): array {
global $wpdb;
$dest = TMDO_DB::table( 'wpdo_site_metrics' );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $dest is a validated table name from TMDO_DB::table().
$rows = (array) $wpdb->get_results(
$wpdb->prepare(
"SELECT collected_at, metric_value AS value
FROM `{$dest}`
WHERE metric_key = %s
AND collected_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)
ORDER BY collected_at ASC",
$metric_key,
$days
),
ARRAY_A
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return array_map(
static fn( $r ) => array(
'collected_at' => (string) $r['collected_at'],
'value' => (int) $r['value'],
),
$rows
);
}
}