PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.1.0
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.1.0
1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 All 29 releases
xspeed / includes / modules / Health / HealthModule.php

HealthModule.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.1.0, at includes/modules/Health/HealthModule.php

194 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Health module — read-only diagnostic surface for the dashboard.
4 *
5 * No settings_schema (this is a status panel, not a configuration
6 * surface). The React side renders a custom panel (HealthCard, declared
7 * via `ui_metadata.custom_panel`) instead of going through ModulePanel's
8 * schema-driven path.
9 *
10 * Data sources are all existing services:
11 * - Health::checks() — diagnostic rows
12 * - Cache::get_stats() — cached_pages / size / last_purge /
13 * hits_24h / misses_24h / hit_ratio
14 * - Hit_Counter::buckets() — 24 hourly buckets for the sparkline
15 * - Activity_Log::entries() — newest-first event log
16 *
17 * Tier: Free (per FEATURES.md "Cache Insights" — Cache Performance +
18 * Last 24h chart are Free; Recommendations + Frequently-missed-URLs
19 * stay Pro).
20 *
21 * @package XSpeed
22 */
23
24 declare(strict_types=1);
25
26 namespace XSpeed\Modules\Health;
27
28 defined( 'ABSPATH' ) || exit;
29
30 use XSpeed\Activity_Log;
31 use XSpeed\Cache;
32 use XSpeed\Health;
33 use XSpeed\Hit_Counter;
34 use XSpeed\Module;
35
36 final class HealthModule extends Module {
37
38 public const SLUG = 'health';
39 public const TIER = self::TIER_FREE;
40 public const VERSION = '1.0.0';
41
42 /**
43 * Surface the most important diagnostics in WordPress's built-in
44 * Site Health screen (Tools → Site Health → Status). Admins who
45 * never open the xSpeed dashboard still get a heads-up when
46 * static-rewrite is missing on Apache/LiteSpeed or when the nginx
47 * snippet hasn't been pasted yet — both lead to a 5-10× slowdown
48 * vs the optimal cache hit path.
49 */
50 public function boot(): void {
51 add_filter( 'site_status_tests', array( $this, 'register_site_status_tests' ) );
52 }
53
54 public function register_site_status_tests( array $tests ): array {
55 $tests['direct']['xspeed_static_rewrite'] = array(
56 'label' => __( 'xSpeed static-rewrite cache', 'xspeed' ),
57 'test' => array( $this, 'site_status_static_rewrite' ),
58 );
59 return $tests;
60 }
61
62 /**
63 * Site Health test row. Reports green when the .htaccess block is
64 * present (Apache/LiteSpeed) or yellow with the nginx snippet
65 * embedded when nginx is detected. Skipped entirely when cache is
66 * disabled — no point telling the user to install a rewrite they
67 * haven't opted into.
68 */
69 public function site_status_static_rewrite(): array {
70 $result = array(
71 'label' => __( 'xSpeed static-rewrite cache is active', 'xspeed' ),
72 'status' => 'good',
73 'badge' => array(
74 'label' => __( 'Performance', 'xspeed' ),
75 'color' => 'blue',
76 ),
77 'description' => '<p>' . esc_html__( 'Cache hits bypass PHP for ~5-15ms TTFB.', 'xspeed' ) . '</p>',
78 'test' => 'xspeed_static_rewrite',
79 );
80
81 $cache_enabled = (bool) ( \XSpeed\Settings::get()['cache_enabled'] ?? false );
82 if ( ! $cache_enabled ) {
83 $result['label'] = __( 'xSpeed cache is disabled', 'xspeed' );
84 $result['status'] = 'recommended';
85 $result['description'] = '<p>' . esc_html__( 'Enable the page cache in the xSpeed dashboard to start serving cached HTML for non-logged-in visitors.', 'xspeed' ) . '</p>';
86 return $result;
87 }
88
89 $server_type = \XSpeed\Server::type();
90 if ( \XSpeed\Server::APACHE === $server_type || \XSpeed\Server::LITESPEED === $server_type ) {
91 if ( ! \XSpeed\Cache::rewrite_installed() ) {
92 $result['label'] = __( 'xSpeed .htaccess rewrite block is missing', 'xspeed' );
93 $result['status'] = 'recommended';
94 $result['description'] = '<p>' . esc_html__( 'Without the static-rewrite block, cache hits go through the PHP drop-in (~85ms TTFB) instead of the web server (~5-15ms). Toggle Enable Cache off and on in xSpeed to reinstall the block.', 'xspeed' ) . '</p>';
95 }
96 return $result;
97 }
98
99 if ( \XSpeed\Server::NGINX === $server_type ) {
100 $snippet = \XSpeed\Cache::nginx_snippet();
101 $result['label'] = __( 'xSpeed nginx server config required', 'xspeed' );
102 $result['status'] = 'recommended';
103 $result['description'] = '<p>' . esc_html__( 'xSpeed can\'t write nginx config from PHP. Paste this snippet into your site\'s server { } block, then reload nginx so cache hits serve without booting PHP:', 'xspeed' ) . '</p>'
104 . '<pre style="white-space:pre;overflow-x:auto;background:#f6f7f7;border:1px solid #c3c4c7;border-radius:4px;padding:12px;font-size:12px;line-height:1.4;">'
105 . esc_html( (string) $snippet )
106 . '</pre>';
107 return $result;
108 }
109
110 // Unknown / IIS — no server-level rewrite path available; PHP
111 // drop-in is the best we can offer. Don't flag as broken.
112 $result['label'] = __( 'xSpeed PHP drop-in cache active', 'xspeed' );
113 $result['status'] = 'recommended';
114 $result['description'] = '<p>' . esc_html__( 'Static-rewrite caching needs Apache, LiteSpeed, or nginx. The PHP drop-in is still serving cache hits at ~85ms TTFB on this server.', 'xspeed' ) . '</p>';
115 return $result;
116 }
117
118 public function ui_metadata(): array {
119 return array(
120 'label' => 'Health',
121 'icon' => 'HeartPulse',
122 'description' => 'Diagnostics, hit ratio, and recent cache activity.',
123 // Health is the single host page for all Insights (FBS-83633):
124 // a Recommendations action card + Cache / Visitors / PageSpeed
125 // tabs. HealthPanel renders the Free cache diagnostics (the old
126 // HealthCard) as the Cache tab and hosts the Pro insight panels
127 // as the other tabs via ProSlot.
128 'custom_panel' => 'HealthPanel',
129 );
130 }
131
132 // No settings — explicit empty so Module::rest_routes() doesn't
133 // auto-wire the schema-driven GET+POST.
134 public function settings_schema(): array {
135 return array();
136 }
137
138 public function rest_routes(): array {
139 return array(
140 array(
141 'path' => '/',
142 'methods' => 'GET',
143 'callback' => array( $this, 'rest_get_payload' ),
144 ),
145 );
146 }
147
148 public function cli_commands(): array {
149 return array(
150 array(
151 'name' => 'xspeed health',
152 'callback' => array( $this, 'cli_handler' ),
153 'shortdesc' => 'Print diagnostic checks + cache stats + recent activity.',
154 'synopsis' => array(),
155 ),
156 );
157 }
158
159 /**
160 * Single endpoint that backs the dashboard panel. Refreshed lazily by
161 * the React side; cheap enough that aggregating into one response is
162 * the right call (the buckets array is at most 24 entries; activity
163 * is capped at 50).
164 */
165 public function rest_get_payload( \WP_REST_Request $request ) {
166 return rest_ensure_response( $this->payload() );
167 }
168
169 public function payload(): array {
170 return array(
171 'checks' => Health::checks(),
172 'stats' => Cache::get_stats(),
173 'buckets' => Hit_Counter::buckets(),
174 'activity' => Activity_Log::entries(),
175 );
176 }
177
178 public function cli_handler( array $args, array $assoc ): void {
179 $payload = $this->payload();
180 \WP_CLI::log( '== Checks ==' );
181 foreach ( $payload['checks'] as $c ) {
182 \WP_CLI::log( sprintf( '[%s] %s — %s', strtoupper( $c['tone'] ), $c['label'], $c['detail'] ) );
183 }
184 \WP_CLI::log( '' );
185 \WP_CLI::log( '== Stats (24h) ==' );
186 \WP_CLI::log( sprintf( 'Hits %d · Misses %d · Hit ratio %.2f%%', $payload['stats']['hits_24h'], $payload['stats']['misses_24h'], $payload['stats']['hit_ratio'] * 100 ) );
187 \WP_CLI::log( '' );
188 \WP_CLI::log( '== Recent activity ==' );
189 foreach ( array_slice( $payload['activity'], 0, 10 ) as $e ) {
190 \WP_CLI::log( sprintf( '%s [%s] %s', gmdate( 'Y-m-d H:i:s', $e['ts'] ), $e['severity'], $e['message'] ) );
191 }
192 }
193 }
194