PluginProbe
WP-ServerInfo / 2.0.0
WP-ServerInfo v2.0.0
3.0.0 trunk 1.00 1.30 1.31 1.40 1.50 1.65 1.66 2.0.0
wp-serverinfo / wp-serverinfo.php

wp-serverinfo.php in WP-ServerInfo 2.0.0, at wp-serverinfo.php

918 lines 38.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: WP-ServerInfo
4 * Plugin URI: https://lesterchan.net/portfolio/programming/php/
5 * Description: Display your host's PHP, MYSQL, memcached & Redis information on your WordPress dashboard.
6 * Version: 2.0.0
7 * Requires at least: 4.6
8 * Requires PHP: 7.2
9 * Author: Lester 'GaMerZ' Chan
10 * Author URI: https://lesterchan.net
11 * License: GPLv2 or later
12 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
13 * Text Domain: wp-serverinfo
14 * Domain Path: /languages
15 *
16 * @package WP-ServerInfo
17 */
18
19 /*
20 Copyright 2026 Lester Chan (email : lesterchan@gmail.com)
21
22 This program is free software; you can redistribute it and/or modify
23 it under the terms of the GNU General Public License as published by
24 the Free Software Foundation; either version 2 of the License, or
25 (at your option) any later version.
26
27 This program is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public License
33 along with this program; if not, write to the Free Software
34 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 */
36
37
38 // Prevent direct access.
39 defined( 'ABSPATH' ) || exit;
40
41 // Plugin version.
42 define( 'WP_SERVERINFO_VERSION', '2.0.0' );
43
44 add_action( 'admin_menu', 'serverinfo_menu' );
45 /**
46 * Register the WP-ServerInfo submenu page under the Dashboard menu.
47 *
48 * @return void
49 */
50 function serverinfo_menu() {
51 if ( function_exists( 'add_submenu_page' ) ) {
52 add_submenu_page( 'index.php', __( 'WP-ServerInfo', 'wp-serverinfo' ), __( 'WP-ServerInfo', 'wp-serverinfo' ), 'manage_options', 'wp-serverinfo', 'display_serverinfo' );
53 }
54 }
55
56
57 /**
58 * Render the WP-ServerInfo admin page as tabbed panels.
59 *
60 * The active panel is chosen via the `tab` query argument; memcached and Redis
61 * tabs appear only when their PHP extension is available.
62 *
63 * @return void
64 */
65 function display_serverinfo() {
66 $tabs = array(
67 'general' => __( 'General', 'wp-serverinfo' ),
68 'php' => __( 'PHP', 'wp-serverinfo' ),
69 'mysql' => __( 'MySQL', 'wp-serverinfo' ),
70 );
71 if ( serverinfo_has_memcached() ) {
72 $tabs['memcached'] = __( 'memcached', 'wp-serverinfo' );
73 }
74 if ( serverinfo_has_redis() ) {
75 $tabs['redis'] = __( 'Redis', 'wp-serverinfo' );
76 }
77
78 // Read-only tab selection driven by the URL; no state change, so no nonce is needed.
79 $active = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only navigation.
80 if ( ! isset( $tabs[ $active ] ) ) {
81 $active = 'general';
82 }
83
84 echo '<style type="text/css">.wrap .widefat tbody tr:hover td { background-color: #f6f7f7; }</style>' . "\n";
85 echo '<div class="wrap">' . "\n";
86 echo '<h1>' . esc_html__( 'WP-ServerInfo', 'wp-serverinfo' ) . '</h1>' . "\n";
87
88 echo '<h2 class="nav-tab-wrapper">';
89 foreach ( $tabs as $tab => $label ) {
90 $url = add_query_arg(
91 array(
92 'page' => 'wp-serverinfo',
93 'tab' => $tab,
94 ),
95 admin_url( 'index.php' )
96 );
97 printf(
98 '<a href="%s" class="nav-tab%s">%s</a>',
99 esc_url( $url ),
100 $active === $tab ? ' nav-tab-active' : '',
101 esc_html( $label )
102 );
103 }
104 echo '</h2>' . "\n";
105
106 switch ( $active ) {
107 case 'php':
108 get_phpinfo();
109 break;
110 case 'mysql':
111 get_mysqlinfo();
112 break;
113 case 'memcached':
114 get_memcachedinfo();
115 break;
116 case 'redis':
117 get_redisinfo();
118 break;
119 default:
120 get_generalinfo();
121 break;
122 }
123
124 echo '</div>' . "\n";
125 }
126
127
128 /**
129 * Output the General Overview panel.
130 *
131 * @return void
132 */
133 function get_generalinfo() {
134 global $is_IIS;
135 if ( is_rtl() ) : ?>
136 <style type="text/css">
137 #GeneralOverview table,
138 #GeneralOverview th,
139 #GeneralOverview td {
140 direction: ltr;
141 text-align: left;
142 }
143 #GeneralOverview h2 {
144 padding: 0.5em 0 0;
145 }
146 </style>
147 <?php
148 endif;
149 ?>
150 <div id="GeneralOverview">
151 <h2><?php esc_html_e( 'General Overview', 'wp-serverinfo' ); ?></h2>
152 <table class="widefat">
153 <thead>
154 <tr>
155 <th><?php esc_html_e( 'Variable Name', 'wp-serverinfo' ); ?></th>
156 <th><?php esc_html_e( 'Value', 'wp-serverinfo' ); ?></th>
157 <th><?php esc_html_e( 'Variable Name', 'wp-serverinfo' ); ?></th>
158 <th><?php esc_html_e( 'Value', 'wp-serverinfo' ); ?></th>
159 </tr>
160 </thead>
161 <tbody>
162 <tr>
163 <td><?php esc_html_e( 'OS', 'wp-serverinfo' ); ?></td>
164 <td><?php echo esc_html( PHP_OS ); ?></td>
165 <td><?php esc_html_e( 'Database Data Disk Usage', 'wp-serverinfo' ); ?></td>
166 <td><?php echo esc_html( format_filesize( get_mysql_data_usage() ) ); ?></td>
167 </tr>
168 <tr class="alternate">
169 <td><?php esc_html_e( 'Server', 'wp-serverinfo' ); ?></td>
170 <td><?php echo esc_html( sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ?? '' ) ) ); ?></td>
171 <td><?php esc_html_e( 'Database Index Disk Usage', 'wp-serverinfo' ); ?></td>
172 <td><?php echo esc_html( format_filesize( get_mysql_index_usage() ) ); ?></td>
173 </tr>
174 <tr>
175 <td>PHP</td>
176 <td>v<?php echo esc_html( PHP_VERSION ); ?></td>
177 <td><?php esc_html_e( 'MYSQL Maximum Packet Size', 'wp-serverinfo' ); ?></td>
178 <td><?php echo esc_html( format_filesize( get_mysql_max_allowed_packet() ) ); ?></td>
179 </tr>
180 <tr class="alternate">
181 <td>MYSQL</td>
182 <td>v<?php echo esc_html( get_mysql_version() ); ?></td>
183 <td><?php esc_html_e( 'MYSQL Maximum No. Connection', 'wp-serverinfo' ); ?></td>
184 <td><?php echo esc_html( number_format_i18n( get_mysql_max_allowed_connections() ) ); ?></td>
185 </tr>
186 <tr>
187 <td>GD</td>
188 <td><?php echo esc_html( get_gd_version() ); ?></td>
189 <td><?php esc_html_e( 'MYSQL Query Cache Size', 'wp-serverinfo' ); ?></td>
190 <td><?php echo esc_html( format_filesize( get_mysql_query_cache_size() ) ); ?></td>
191 </tr>
192 <tr class="alternate">
193 <td><?php esc_html_e( 'Server Hostname', 'wp-serverinfo' ); ?></td>
194 <td><?php echo esc_html( sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ?? '' ) ) ); ?></td>
195 <td><?php esc_html_e( 'PHP Short Tag', 'wp-serverinfo' ); ?></td>
196 <td><?php echo esc_html( get_php_short_tag() ); ?></td>
197 </tr>
198 <tr>
199 <td><?php esc_html_e( 'Server IP:Port', 'wp-serverinfo' ); ?></td>
200 <td><?php echo esc_html( sanitize_text_field( wp_unslash( $is_IIS ? ( $_SERVER['LOCAL_ADDR'] ?? '' ) : ( $_SERVER['SERVER_ADDR'] ?? '' ) ) ) ); ?>:<?php echo esc_html( sanitize_text_field( wp_unslash( $_SERVER['SERVER_PORT'] ?? '' ) ) ); ?></td>
201 <td><?php esc_html_e( 'PHP Max Script Execute Time', 'wp-serverinfo' ); ?></td>
202 <td><?php echo esc_html( get_php_max_execution() ); ?>s</td>
203 </tr>
204 <tr class="alternate">
205 <td><?php esc_html_e( 'Server Document Root', 'wp-serverinfo' ); ?></td>
206 <td><?php echo esc_html( sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ?? '' ) ) ); ?></td>
207 <td><?php esc_html_e( 'PHP Memory Limit', 'wp-serverinfo' ); ?></td>
208 <td><?php echo esc_html( format_php_size( get_php_memory_limit() ) ); ?></td>
209 </tr>
210 <tr>
211 <td><?php esc_html_e( 'Server Date/Time', 'wp-serverinfo' ); ?></td>
212 <td><?php echo esc_html( mysql2date( sprintf( /* translators: 1: date format, 2: time format */ __( '%1$s @ %2$s', 'wp-serverinfo' ), get_option( 'date_format' ), get_option( 'time_format' ) ), current_time( 'mysql' ) ) ); ?></td>
213 <td><?php esc_html_e( 'PHP Max Upload Size', 'wp-serverinfo' ); ?></td>
214 <td><?php echo esc_html( format_php_size( get_php_upload_max() ) ); ?></td>
215 </tr>
216 <tr class="alternate">
217 <td><?php esc_html_e( 'Server Load', 'wp-serverinfo' ); ?></td>
218 <td><?php echo esc_html( get_serverLoad() ); ?></td>
219 <td><?php esc_html_e( 'PHP Max Post Size', 'wp-serverinfo' ); ?></td>
220 <td><?php echo esc_html( format_php_size( get_php_post_max() ) ); ?></td>
221 </tr>
222 </tbody>
223 </table>
224 </div>
225 <?php
226 }
227
228
229 /**
230 * Output the PHP Information panel, built from structured PHP data.
231 *
232 * @return void
233 */
234 function get_phpinfo() {
235 echo '<div id="PHPinfo">' . "\n";
236 echo '<h2>PHP ' . esc_html( phpversion() ) . '</h2>' . "\n";
237
238 // Summary built from structured PHP data (no phpinfo() HTML scraping).
239 $summary = array(
240 __( 'PHP Version', 'wp-serverinfo' ) => phpversion(),
241 __( 'Zend Engine Version', 'wp-serverinfo' ) => zend_version(),
242 __( 'Server API', 'wp-serverinfo' ) => php_sapi_name(),
243 __( 'Loaded Configuration File', 'wp-serverinfo' ) => php_ini_loaded_file() ? php_ini_loaded_file() : __( 'N/A', 'wp-serverinfo' ),
244 __( 'Loaded Extensions', 'wp-serverinfo' ) => implode( ', ', get_loaded_extensions() ),
245 );
246 echo '<br class="clear" />' . "\n";
247 echo '<table class="widefat"><tbody>' . "\n";
248 foreach ( $summary as $label => $value ) {
249 echo '<tr><td><strong>' . esc_html( $label ) . '</strong></td><td>' . esc_html( $value ) . '</td></tr>' . "\n";
250 }
251 echo '</tbody></table>' . "\n";
252
253 // Configuration directives from ini_get_all().
254 $ini = function_exists( 'ini_get_all' ) ? ini_get_all( null, true ) : false;
255 if ( ! empty( $ini ) ) {
256 ksort( $ini );
257 echo '<br class="clear" />' . "\n";
258 echo '<table class="widefat"><thead><tr><th>' . esc_html__( 'Directive', 'wp-serverinfo' ) . '</th><th>' . esc_html__( 'Local Value', 'wp-serverinfo' ) . '</th><th>' . esc_html__( 'Master Value', 'wp-serverinfo' ) . '</th></tr></thead><tbody>' . "\n";
259 foreach ( $ini as $directive => $values ) {
260 $local = isset( $values['local_value'] ) ? $values['local_value'] : '';
261 $global = isset( $values['global_value'] ) ? $values['global_value'] : '';
262 echo '<tr><td>' . esc_html( $directive ) . '</td><td>' . esc_html( $local ) . '</td><td>' . esc_html( $global ) . '</td></tr>' . "\n";
263 }
264 echo '</tbody></table>' . "\n";
265 }
266 echo '</div>' . "\n";
267 }
268
269
270 /**
271 * Output the MYSQL Information panel (server variables).
272 *
273 * @return void
274 */
275 function get_mysqlinfo() {
276 global $wpdb;
277 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- live server introspection; results must not be cached.
278 $sqlversion = $wpdb->get_var( 'SELECT VERSION() AS version' );
279 $mysqlinfo = $wpdb->get_results( 'SHOW VARIABLES' );
280 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
281 if ( is_rtl() ) :
282 ?>
283 <style type="text/css">
284 #MYSQLinfo,
285 #MYSQLinfo table,
286 #MYSQLinfo th,
287 #MYSQLinfo td {
288 direction: ltr;
289 text-align: left;
290 }
291 #MYSQLinfo h2 {
292 padding: 0.5em 0 0;
293 }
294 </style>
295 <?php
296 endif;
297 echo '<div id="MYSQLinfo">' . "\n";
298 echo '<h2>MYSQL ' . esc_html( $sqlversion ) . "</h2>\n";
299 if ( $mysqlinfo ) {
300 echo '<br class="clear" />' . "\n";
301 echo '<table class="widefat" dir="ltr">' . "\n";
302 echo '<thead><tr><th>' . esc_html__( 'Variable Name', 'wp-serverinfo' ) . '</th><th>' . esc_html__( 'Value', 'wp-serverinfo' ) . '</th></tr></thead><tbody>' . "\n";
303 foreach ( $mysqlinfo as $info ) {
304 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- MySQL column names from SHOW VARIABLES.
305 echo '<tr><td>' . esc_html( $info->Variable_name ) . '</td><td>' . esc_html( $info->Value ) . '</td></tr>' . "\n";
306 }
307 echo '</tbody></table>' . "\n";
308 }
309 echo '</div>' . "\n";
310 }
311
312
313 if ( ! function_exists( 'serverinfo_has_memcached' ) ) {
314 /**
315 * Determine whether a memcached PHP extension is available.
316 *
317 * @return bool True when the Memcached or Memcache extension is loaded.
318 */
319 function serverinfo_has_memcached() {
320 return class_exists( 'Memcached' ) || class_exists( 'Memcache' );
321 }
322 }
323
324
325 if ( ! function_exists( 'serverinfo_get_memcached_stats' ) ) {
326 /**
327 * Fetch memcached server statistics, preferring Memcached over Memcache.
328 *
329 * @return array|false Flat stats array for the local server, or false when unavailable.
330 */
331 function serverinfo_get_memcached_stats() {
332 if ( class_exists( 'Memcached' ) ) {
333 $memcached_obj = new Memcached();
334 $memcached_obj->addServer( 'localhost', 11211 );
335 $stats = $memcached_obj->getStats();
336 // Memcached::getStats() returns an array keyed by "host:port".
337 return is_array( $stats ) && ! empty( $stats ) ? reset( $stats ) : false;
338 } elseif ( class_exists( 'Memcache' ) ) {
339 $memcached_obj = new Memcache();
340 @$memcached_obj->addServer( 'localhost', 11211 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- addServer() can emit a connection warning when memcached is down; suppressed intentionally.
341 return $memcached_obj->getStats();
342 }
343 return false;
344 }
345 }
346
347
348 /**
349 * Output the memcached Information panel.
350 *
351 * Stat descriptions from https://boxpanel.blueboxgrp.com/public/the_vault/index.php/memcached_Tips
352 *
353 * @return void
354 */
355 function get_memcachedinfo() {
356 echo '<div id="memcachedinfo">' . "\n";
357 if ( serverinfo_has_memcached() ) {
358 $memcachedinfo = serverinfo_get_memcached_stats();
359 if ( is_rtl() ) :
360 ?>
361 <style type="text/css">
362 #memcachedinfo,
363 #memcachedinfo table,
364 #memcachedinfo th,
365 #memcachedinfo td {
366 direction: ltr;
367 text-align: left;
368 }
369 #memcachedinfo h2 {
370 padding: 0.5em 0 0;
371 }
372 </style>
373 <?php
374 endif;
375 echo '<h2>memcached ' . esc_html( $memcachedinfo['version'] ?? '' ) . "</h2>\n";
376 if ( $memcachedinfo ) {
377 $cache_hit = ( $memcachedinfo['cmd_get'] > 0 ? ( ( $memcachedinfo['get_hits'] / $memcachedinfo['cmd_get'] ) * 100 ) : 0 );
378 $cache_hit = round( $cache_hit, 2 );
379 $cache_miss = 100 - $cache_hit;
380
381 $usage = ( $memcachedinfo['limit_maxbytes'] > 0 ) ? round( ( ( $memcachedinfo['bytes'] / $memcachedinfo['limit_maxbytes'] ) * 100 ), 2 ) : 0;
382 $uptime = number_format_i18n( ( $memcachedinfo['uptime'] / 60 / 60 / 24 ) );
383
384 echo '<br class="clear" />' . "\n";
385 echo '<table class="widefat" dir="ltr">' . "\n";
386 echo '<thead><tr><th>' . esc_html__( 'Variable Name', 'wp-serverinfo' ) . '</th><th>' . esc_html__( 'Value', 'wp-serverinfo' ) . '</th><th>' . esc_html__( 'Description', 'wp-serverinfo' ) . '</th></tr></thead><tbody>' . "\n";
387
388 // Each row: variable name, pre-formatted value, and description; all three cells are escaped uniformly below.
389 $memcached_rows = array(
390 array( 'pid', $memcachedinfo['pid'], __( 'Process ID', 'wp-serverinfo' ) ),
391 array( 'uptime', $uptime, __( 'Number of days since the process was started', 'wp-serverinfo' ) ),
392 array( 'version', $memcachedinfo['version'], __( 'memcached version', 'wp-serverinfo' ) ),
393 array( 'rusage_user', $memcachedinfo['rusage_user'], __( 'Seconds the cpu has devoted to the process as the user', 'wp-serverinfo' ) ),
394 array( 'rusage_system', $memcachedinfo['rusage_system'], __( 'Seconds the cpu has devoted to the process as the system', 'wp-serverinfo' ) ),
395 array( 'curr_items', number_format_i18n( $memcachedinfo['curr_items'] ), __( 'Total number of items currently in memcached', 'wp-serverinfo' ) ),
396 array( 'total_items', number_format_i18n( $memcachedinfo['total_items'] ), __( 'Total number of items that have passed through memcached', 'wp-serverinfo' ) ),
397 array( 'bytes', format_filesize( $memcachedinfo['bytes'] ) . ' (' . $usage . '%)', __( 'Memory size currently used by curr_items', 'wp-serverinfo' ) ),
398 array( 'limit_maxbytes', format_filesize( $memcachedinfo['limit_maxbytes'] ), __( 'Maximum memory size allocated to memcached', 'wp-serverinfo' ) ),
399 array( 'curr_connections', number_format_i18n( $memcachedinfo['curr_connections'] ), __( 'Total number of open connections to memcached', 'wp-serverinfo' ) ),
400 array( 'total_connections', number_format_i18n( $memcachedinfo['total_connections'] ), __( 'Total number of connections opened since memcached started running', 'wp-serverinfo' ) ),
401 array( 'connection_structures', number_format_i18n( $memcachedinfo['connection_structures'] ), __( 'Number of connection structures allocated by the server', 'wp-serverinfo' ) ),
402 array( 'cmd_get', number_format_i18n( $memcachedinfo['cmd_get'] ), __( 'Total GET commands issued to the server', 'wp-serverinfo' ) ),
403 array( 'cmd_set', number_format_i18n( $memcachedinfo['cmd_set'] ), __( 'Total SET commands issued to the server', 'wp-serverinfo' ) ),
404 array( 'cmd_flush', number_format_i18n( $memcachedinfo['cmd_flush'] ), __( 'Total FLUSH commands issued to the server', 'wp-serverinfo' ) ),
405 array( 'get_hits', number_format_i18n( $memcachedinfo['get_hits'] ) . ' (' . $cache_hit . '%)', __( 'Total number of times a GET command was able to retrieve and return data', 'wp-serverinfo' ) ),
406 array( 'get_misses', number_format_i18n( $memcachedinfo['get_misses'] ) . ' (' . $cache_miss . '%)', __( 'Total number of times a GET command was unable to retrieve and return data', 'wp-serverinfo' ) ),
407 array( 'delete_hits', number_format_i18n( $memcachedinfo['delete_hits'] ), __( 'Total number of times a DELETE command was able to delete data', 'wp-serverinfo' ) ),
408 array( 'delete_misses', number_format_i18n( $memcachedinfo['delete_misses'] ), __( 'Total number of times a DELETE command was unable to delete data', 'wp-serverinfo' ) ),
409 array( 'incr_hits', number_format_i18n( $memcachedinfo['incr_hits'] ), __( 'Total number of times a INCR command was able to increment a value', 'wp-serverinfo' ) ),
410 array( 'incr_misses', number_format_i18n( $memcachedinfo['incr_misses'] ), __( 'Total number of times a INCR command was unable to increment a value', 'wp-serverinfo' ) ),
411 array( 'decr_hits', number_format_i18n( $memcachedinfo['decr_hits'] ), __( 'Total number of times a DECR command was able to decrement a value', 'wp-serverinfo' ) ),
412 array( 'decr_misses', number_format_i18n( $memcachedinfo['decr_misses'] ), __( 'Total number of times a DECR command was unable to decrement a value', 'wp-serverinfo' ) ),
413 array( 'cas_hits', number_format_i18n( $memcachedinfo['cas_hits'] ), __( 'Total number of times a CAS command was able to compare and swap data', 'wp-serverinfo' ) ),
414 array( 'cas_misses', number_format_i18n( $memcachedinfo['cas_misses'] ), __( 'Total number of times a CAS command was unable to compare and swap data', 'wp-serverinfo' ) ),
415 array( 'cas_badval', number_format_i18n( $memcachedinfo['cas_badval'] ), __( 'N/A', 'wp-serverinfo' ) ),
416 array( 'bytes_read', format_filesize( $memcachedinfo['bytes_read'] ), __( 'Total number of bytes input into the server', 'wp-serverinfo' ) ),
417 array( 'bytes_written', format_filesize( $memcachedinfo['bytes_written'] ), __( 'Total number of bytes written by the server', 'wp-serverinfo' ) ),
418 array( 'evictions', number_format_i18n( $memcachedinfo['evictions'] ), __( 'Number of valid items removed from cache to free memory for new items', 'wp-serverinfo' ) ),
419 array( 'reclaimed', number_format_i18n( $memcachedinfo['reclaimed'] ), __( 'Number of items reclaimed', 'wp-serverinfo' ) ),
420 );
421 foreach ( $memcached_rows as $memcached_row ) {
422 echo '<tr><td>' . esc_html( $memcached_row[0] ) . '</td><td>' . esc_html( $memcached_row[1] ) . '</td><td>' . esc_html( $memcached_row[2] ) . '</td></tr>' . "\n";
423 }
424 echo '</tbody></table>' . "\n";
425 }
426 }
427 echo '</div>' . "\n";
428 }
429
430
431 if ( ! function_exists( 'serverinfo_has_redis' ) ) {
432 /**
433 * Determine whether the phpredis extension is available.
434 *
435 * @return bool True when the Redis class is loaded.
436 */
437 function serverinfo_has_redis() {
438 return class_exists( 'Redis' );
439 }
440 }
441
442
443 if ( ! function_exists( 'serverinfo_get_redis_stats' ) ) {
444 /**
445 * Fetch Redis server info from the local instance via phpredis.
446 *
447 * @return array|false Associative INFO array, or false when unavailable.
448 */
449 function serverinfo_get_redis_stats() {
450 if ( ! class_exists( 'Redis' ) ) {
451 return false;
452 }
453 try {
454 $redis = new Redis();
455 // Short timeout so an unreachable server does not stall the admin page.
456 if ( ! $redis->connect( '127.0.0.1', 6379, 1 ) ) {
457 return false;
458 }
459 $info = $redis->info();
460 $redis->close();
461 } catch ( Exception $e ) {
462 return false;
463 }
464 return is_array( $info ) && ! empty( $info ) ? $info : false;
465 }
466 }
467
468
469 /**
470 * Output the Redis Information panel.
471 *
472 * @return void
473 */
474 function get_redisinfo() {
475 echo '<div id="Redisinfo">' . "\n";
476 if ( serverinfo_has_redis() ) {
477 $redisinfo = serverinfo_get_redis_stats();
478 echo '<h2>Redis ' . esc_html( $redisinfo['redis_version'] ?? '' ) . "</h2>\n";
479 if ( $redisinfo ) {
480 $hits = isset( $redisinfo['keyspace_hits'] ) ? (int) $redisinfo['keyspace_hits'] : 0;
481 $misses = isset( $redisinfo['keyspace_misses'] ) ? (int) $redisinfo['keyspace_misses'] : 0;
482 $lookups = $hits + $misses;
483 $hit_pct = $lookups > 0 ? round( ( $hits / $lookups ) * 100, 2 ) : 0;
484
485 echo '<br class="clear" />' . "\n";
486 echo '<table class="widefat" dir="ltr">' . "\n";
487 echo '<thead><tr><th>' . esc_html__( 'Variable Name', 'wp-serverinfo' ) . '</th><th>' . esc_html__( 'Value', 'wp-serverinfo' ) . '</th><th>' . esc_html__( 'Description', 'wp-serverinfo' ) . '</th></tr></thead><tbody>' . "\n";
488
489 // Each row: variable name, pre-formatted value, and description; all three cells are escaped uniformly below.
490 $redis_rows = array(
491 array( 'redis_version', $redisinfo['redis_version'] ?? '', __( 'Redis server version', 'wp-serverinfo' ) ),
492 array( 'redis_mode', $redisinfo['redis_mode'] ?? '', __( 'Server mode (standalone, sentinel or cluster)', 'wp-serverinfo' ) ),
493 array( 'uptime_in_days', number_format_i18n( $redisinfo['uptime_in_days'] ?? 0 ), __( 'Number of days since the server was started', 'wp-serverinfo' ) ),
494 array( 'connected_clients', number_format_i18n( $redisinfo['connected_clients'] ?? 0 ), __( 'Number of client connections', 'wp-serverinfo' ) ),
495 array( 'used_memory', $redisinfo['used_memory_human'] ?? format_filesize( $redisinfo['used_memory'] ?? 0 ), __( 'Memory allocated by Redis', 'wp-serverinfo' ) ),
496 array( 'used_memory_peak', $redisinfo['used_memory_peak_human'] ?? format_filesize( $redisinfo['used_memory_peak'] ?? 0 ), __( 'Peak memory consumed by Redis', 'wp-serverinfo' ) ),
497 array( 'maxmemory', $redisinfo['maxmemory_human'] ?? format_filesize( $redisinfo['maxmemory'] ?? 0 ), __( 'Memory limit configured for Redis', 'wp-serverinfo' ) ),
498 array( 'maxmemory_policy', $redisinfo['maxmemory_policy'] ?? '', __( 'Eviction policy applied when the memory limit is reached', 'wp-serverinfo' ) ),
499 array( 'total_connections_received', number_format_i18n( $redisinfo['total_connections_received'] ?? 0 ), __( 'Total number of connections accepted by the server', 'wp-serverinfo' ) ),
500 array( 'total_commands_processed', number_format_i18n( $redisinfo['total_commands_processed'] ?? 0 ), __( 'Total number of commands processed by the server', 'wp-serverinfo' ) ),
501 array( 'instantaneous_ops_per_sec', number_format_i18n( $redisinfo['instantaneous_ops_per_sec'] ?? 0 ), __( 'Number of commands processed per second', 'wp-serverinfo' ) ),
502 array( 'keyspace_hits', number_format_i18n( $hits ) . ' (' . $hit_pct . '%)', __( 'Number of successful lookups of keys in the main dictionary', 'wp-serverinfo' ) ),
503 array( 'keyspace_misses', number_format_i18n( $misses ), __( 'Number of failed lookups of keys in the main dictionary', 'wp-serverinfo' ) ),
504 array( 'expired_keys', number_format_i18n( $redisinfo['expired_keys'] ?? 0 ), __( 'Total number of key expiration events', 'wp-serverinfo' ) ),
505 array( 'evicted_keys', number_format_i18n( $redisinfo['evicted_keys'] ?? 0 ), __( 'Number of evicted keys due to the memory limit', 'wp-serverinfo' ) ),
506 array( 'connected_slaves', number_format_i18n( $redisinfo['connected_slaves'] ?? 0 ), __( 'Number of connected replicas', 'wp-serverinfo' ) ),
507 );
508 foreach ( $redis_rows as $redis_row ) {
509 echo '<tr><td>' . esc_html( $redis_row[0] ) . '</td><td>' . esc_html( $redis_row[1] ) . '</td><td>' . esc_html( $redis_row[2] ) . '</td></tr>' . "\n";
510 }
511 echo '</tbody></table>' . "\n";
512 }
513 }
514 echo '</div>' . "\n";
515 }
516
517
518 if ( ! function_exists( 'format_filesize' ) ) {
519 /**
520 * Format a byte count into a localized TiB/GiB/MiB/KiB/bytes string.
521 *
522 * @param int|float $raw_size Size in bytes.
523 * @return string Human-readable, localized size.
524 */
525 function format_filesize( $raw_size ) {
526 if ( $raw_size / 1099511627776 > 1 ) {
527 return number_format_i18n( $raw_size / 1099511627776, 1 ) . ' ' . __( 'TiB', 'wp-serverinfo' );
528 } elseif ( $raw_size / 1073741824 > 1 ) {
529 return number_format_i18n( $raw_size / 1073741824, 1 ) . ' ' . __( 'GiB', 'wp-serverinfo' );
530 } elseif ( $raw_size / 1048576 > 1 ) {
531 return number_format_i18n( $raw_size / 1048576, 1 ) . ' ' . __( 'MiB', 'wp-serverinfo' );
532 } elseif ( $raw_size / 1024 > 1 ) {
533 return number_format_i18n( $raw_size / 1024, 1 ) . ' ' . __( 'KiB', 'wp-serverinfo' );
534 } elseif ( $raw_size > 1 ) {
535 return number_format_i18n( $raw_size, 0 ) . ' ' . __( 'bytes', 'wp-serverinfo' );
536 } else {
537 return __( 'unknown', 'wp-serverinfo' );
538 }
539 }
540 }
541
542 /**
543 * Convert a PHP shorthand size (e.g. "128M") into a localized size string.
544 *
545 * @param string|int $size PHP size value, shorthand or numeric.
546 * @return string Localized size, or the original value when not numeric.
547 */
548 function format_php_size( $size ) {
549 if ( ! is_numeric( $size ) ) {
550 if ( strpos( $size, 'M' ) !== false ) {
551 $size = intval( $size ) * 1024 * 1024;
552 } elseif ( strpos( $size, 'K' ) !== false ) {
553 $size = intval( $size ) * 1024;
554 } elseif ( strpos( $size, 'G' ) !== false ) {
555 $size = intval( $size ) * 1024 * 1024 * 1024;
556 }
557 }
558 return is_numeric( $size ) ? format_filesize( $size ) : $size;
559 }
560
561 if ( ! function_exists( 'get_php_short_tag' ) ) {
562 /**
563 * Get the short_open_tag ini state as a localized On/Off string.
564 *
565 * @return string Localized "On" or "Off".
566 */
567 function get_php_short_tag() {
568 if ( ini_get( 'short_open_tag' ) ) {
569 $short_tag = __( 'On', 'wp-serverinfo' );
570 } else {
571 $short_tag = __( 'Off', 'wp-serverinfo' );
572 }
573 return $short_tag;
574 }
575 }
576
577
578 if ( ! function_exists( 'get_php_upload_max' ) ) {
579 /**
580 * Get the upload_max_filesize ini value.
581 *
582 * @return string The configured value, or a localized "N/A".
583 */
584 function get_php_upload_max() {
585 if ( ini_get( 'upload_max_filesize' ) ) {
586 $upload_max = ini_get( 'upload_max_filesize' );
587 } else {
588 $upload_max = __( 'N/A', 'wp-serverinfo' );
589 }
590 return $upload_max;
591 }
592 }
593
594
595 if ( ! function_exists( 'get_php_post_max' ) ) {
596 /**
597 * Get the post_max_size ini value.
598 *
599 * @return string The configured value, or a localized "N/A".
600 */
601 function get_php_post_max() {
602 if ( ini_get( 'post_max_size' ) ) {
603 $post_max = ini_get( 'post_max_size' );
604 } else {
605 $post_max = __( 'N/A', 'wp-serverinfo' );
606 }
607 return $post_max;
608 }
609 }
610
611
612 if ( ! function_exists( 'get_php_max_execution' ) ) {
613 /**
614 * Get the max_execution_time ini value.
615 *
616 * @return string The configured value, or a localized "N/A".
617 */
618 function get_php_max_execution() {
619 if ( ini_get( 'max_execution_time' ) ) {
620 $max_execute = ini_get( 'max_execution_time' );
621 } else {
622 $max_execute = __( 'N/A', 'wp-serverinfo' );
623 }
624 return $max_execute;
625 }
626 }
627
628
629 if ( ! function_exists( 'get_php_memory_limit' ) ) {
630 /**
631 * Get the memory_limit ini value.
632 *
633 * @return string The configured value, or a localized "N/A".
634 */
635 function get_php_memory_limit() {
636 if ( ini_get( 'memory_limit' ) ) {
637 $memory_limit = ini_get( 'memory_limit' );
638 } else {
639 $memory_limit = __( 'N/A', 'wp-serverinfo' );
640 }
641 return $memory_limit;
642 }
643 }
644
645
646 if ( ! function_exists( 'get_mysql_version' ) ) {
647 /**
648 * Get the MySQL server version.
649 *
650 * @return string|null Version string, or null on failure.
651 */
652 function get_mysql_version() {
653 global $wpdb;
654 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- live server introspection; results must not be cached.
655 return $wpdb->get_var( 'SELECT VERSION() AS version' );
656 }
657 }
658
659
660 if ( ! function_exists( 'serverinfo_get_table_status' ) ) {
661 /**
662 * Get SHOW TABLE STATUS rows, cached in a static for the request.
663 *
664 * @return array List of table status row objects.
665 */
666 function serverinfo_get_table_status() {
667 global $wpdb;
668 static $tablesstatus = null;
669 if ( is_null( $tablesstatus ) ) {
670 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- live server introspection, cached in a static for the request.
671 $tablesstatus = $wpdb->get_results( 'SHOW TABLE STATUS' );
672 }
673 return $tablesstatus;
674 }
675 }
676
677
678 if ( ! function_exists( 'get_mysql_data_usage' ) ) {
679 /**
680 * Sum the data length across all database tables.
681 *
682 * @return int Total data length in bytes.
683 */
684 function get_mysql_data_usage() {
685 $data_usage = 0;
686 foreach ( serverinfo_get_table_status() as $tablestatus ) {
687 $data_usage += $tablestatus->Data_length; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- MySQL column name from SHOW TABLE STATUS.
688 }
689
690 return $data_usage;
691 }
692 }
693
694
695 if ( ! function_exists( 'get_mysql_index_usage' ) ) {
696 /**
697 * Sum the index length across all database tables.
698 *
699 * @return int Total index length in bytes.
700 */
701 function get_mysql_index_usage() {
702 $index_usage = 0;
703 foreach ( serverinfo_get_table_status() as $tablestatus ) {
704 $index_usage += $tablestatus->Index_length; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- MySQL column name from SHOW TABLE STATUS.
705 }
706
707 return $index_usage;
708 }
709 }
710
711
712 if ( ! function_exists( 'get_mysql_max_allowed_packet' ) ) {
713 /**
714 * Get the MySQL max_allowed_packet value.
715 *
716 * @return string|null Value in bytes, or null on failure.
717 */
718 function get_mysql_max_allowed_packet() {
719 global $wpdb;
720 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- live server introspection; results must not be cached.
721 $packet_max_query = $wpdb->get_row( "SHOW VARIABLES LIKE 'max_allowed_packet'" );
722
723 return $packet_max_query->Value; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- MySQL column name from SHOW VARIABLES.
724 }
725 }
726
727
728 if ( ! function_exists( 'get_mysql_max_allowed_connections' ) ) {
729 /**
730 * Get the MySQL max_connections value.
731 *
732 * @return string|null Maximum connections, or null on failure.
733 */
734 function get_mysql_max_allowed_connections() {
735 global $wpdb;
736 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- live server introspection; results must not be cached.
737 $connection_max_query = $wpdb->get_row( "SHOW VARIABLES LIKE 'max_connections'" );
738
739 return $connection_max_query->Value; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- MySQL column name from SHOW VARIABLES.
740 }
741 }
742
743 if ( ! function_exists( 'get_mysql_query_cache_size' ) ) {
744 /**
745 * Get the MySQL query_cache_size value.
746 *
747 * Returns 0 on MySQL 8.0+, where the query cache was removed.
748 *
749 * @return string|int Value in bytes, or 0 when unavailable.
750 */
751 function get_mysql_query_cache_size() {
752 global $wpdb;
753 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- live server introspection; results must not be cached.
754 $query_cache_size_query = $wpdb->get_row( "SHOW VARIABLES LIKE 'query_cache_size'" );
755
756 return $query_cache_size_query ? $query_cache_size_query->Value : 0; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- MySQL column name from SHOW VARIABLES.
757 }
758 }
759
760
761 if ( ! function_exists( 'get_gd_version' ) ) {
762 /**
763 * Get the installed GD library version.
764 *
765 * @return string GD version, or a localized "N/A" when undetectable.
766 */
767 function get_gd_version() {
768 if ( function_exists( 'gd_info' ) ) {
769 $gd = gd_info();
770 $gd = $gd['GD Version'];
771 } else {
772 // phpcs:disable WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_phpinfo, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- last-resort GD version probe when gd_info() is unavailable; output is parsed, not displayed.
773 ob_start();
774 phpinfo( 8 );
775 $phpinfo = ob_get_contents();
776 ob_end_clean();
777 $phpinfo = strip_tags( $phpinfo );
778 $phpinfo = stristr( $phpinfo, 'gd version' );
779 $phpinfo = stristr( $phpinfo, 'version' );
780 $gd = substr( $phpinfo, 0, strpos( $phpinfo, "\n" ) );
781 // phpcs:enable WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_phpinfo, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
782 }
783 if ( empty( $gd ) ) {
784 $gd = __( 'N/A', 'wp-serverinfo' );
785 }
786 return $gd;
787 }
788 }
789
790
791 if ( ! function_exists( 'get_serverload' ) ) {
792 /**
793 * Get the current server load average (Unix-like hosts only).
794 *
795 * @return string 1-minute load average, or a localized "N/A".
796 */
797 function get_serverload() {
798 $server_load = '';
799 // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions, WordPress.PHP.DiscouragedPHPFunctions -- probing the host for load average; @-suppression and low-level/shell calls are intentional and gated behind availability and disable_functions checks.
800 if ( PHP_OS !== 'WINNT' && PHP_OS !== 'WIN32' ) {
801 if ( @file_exists( '/proc/loadavg' ) ) {
802 $fh = @fopen( '/proc/loadavg', 'r' );
803 if ( $fh ) {
804 $data = @fread( $fh, 6 );
805 @fclose( $fh );
806 $load_avg = explode( ' ', $data );
807 $server_load = trim( $load_avg[0] );
808 }
809 } else {
810 $disabled_functions = array_map( 'trim', explode( ',', ini_get( 'disable_functions' ) ) );
811 if ( function_exists( 'system' ) && ! in_array( 'system', $disabled_functions, true ) ) {
812 $data = @system( 'uptime' );
813 if ( false !== $data ) {
814 preg_match( '/(.*):{1}(.*)/', $data, $matches );
815 if ( isset( $matches[2] ) ) {
816 $load_arr = explode( ',', $matches[2] );
817 $server_load = trim( $load_arr[0] );
818 }
819 }
820 } elseif ( function_exists( 'exec' ) && ! in_array( 'exec', $disabled_functions, true ) ) {
821 $data = @exec( 'uptime 2>&1', $output, $return_var );
822 if ( 0 === $return_var && ! empty( $data ) ) {
823 preg_match( '/load average[s]?:\s*([0-9\.]+)/', $data, $matches );
824 if ( isset( $matches[1] ) ) {
825 $server_load = $matches[1];
826 }
827 }
828 } elseif ( function_exists( 'shell_exec' ) && ! in_array( 'shell_exec', $disabled_functions, true ) ) {
829 $data = @shell_exec( 'uptime 2>&1' );
830 if ( ! empty( $data ) ) {
831 preg_match( '/load average[s]?:\s*([0-9\.]+)/', $data, $matches );
832 if ( isset( $matches[1] ) ) {
833 $server_load = $matches[1];
834 }
835 }
836 }
837 }
838 }
839 // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions, WordPress.PHP.DiscouragedPHPFunctions
840 if ( empty( $server_load ) ) {
841 $server_load = __( 'N/A', 'wp-serverinfo' );
842 }
843 return $server_load;
844 }
845 }
846
847
848 add_action( 'wp_dashboard_setup', 'serverinfo_register_dashboard_widget' );
849 /**
850 * Register the Server Information dashboard widget for administrators.
851 *
852 * @return void
853 */
854 function serverinfo_register_dashboard_widget() {
855 if ( current_user_can( 'manage_options' ) ) {
856 wp_add_dashboard_widget( 'dashboard_serverinfo', __( 'Server Information', 'wp-serverinfo' ), 'wp_dashboard_serverinfo' );
857 }
858 }
859
860
861 /**
862 * Render the Server Information dashboard widget contents.
863 *
864 * @return void
865 */
866 function wp_dashboard_serverinfo() {
867 if ( is_rtl() ) {
868 echo '<style type="text/css"> #wp-serverinfo ul { padding-left: 15px !important; } </style>';
869 echo '<div id="wp-serverinfo" style="direction: ltr; text-align: left;">';
870 } else {
871 echo '<div id="wp-serverinfo">';
872 }
873 echo '<p><strong>' . esc_html__( 'General', 'wp-serverinfo' ) . '</strong></p>';
874 echo '<ul>';
875 echo '<li>' . esc_html__( 'OS', 'wp-serverinfo' ) . ': <strong>' . esc_html( PHP_OS ) . '</strong></li>';
876 echo '<li>' . esc_html__( 'Server', 'wp-serverinfo' ) . ': <strong>' . esc_html( sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ?? '' ) ) ) . '</strong></li>';
877 echo '<li>' . esc_html__( 'Hostname', 'wp-serverinfo' ) . ': <strong>' . esc_html( sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ?? '' ) ) ) . '</strong></li>';
878 echo '<li>' . esc_html__( 'IP:Port', 'wp-serverinfo' ) . ': <strong>' . esc_html( sanitize_text_field( wp_unslash( $_SERVER['SERVER_ADDR'] ?? '' ) ) ) . ':' . esc_html( sanitize_text_field( wp_unslash( $_SERVER['SERVER_PORT'] ?? '' ) ) ) . '</strong></li>';
879 echo '<li>' . esc_html__( 'Document Root', 'wp-serverinfo' ) . ': <strong>' . esc_html( sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ?? '' ) ) ) . '</strong></li>';
880 echo '</ul>';
881 echo '<p><strong>PHP</strong></p>';
882 echo '<ul>';
883 echo '<li>v<strong>' . esc_html( PHP_VERSION ) . '</strong></li>';
884 echo '<li>GD: <strong>' . esc_html( get_gd_version() ) . '</strong></li>';
885 echo '<li>' . esc_html__( 'Memory Limit', 'wp-serverinfo' ) . ': <strong>' . esc_html( format_php_size( get_php_memory_limit() ) ) . '</strong></li>';
886 echo '<li>' . esc_html__( 'Max Script Execute Time', 'wp-serverinfo' ) . ': <strong>' . esc_html( get_php_max_execution() ) . 's</strong></li>';
887 echo '<li>' . esc_html__( 'Max Post Size', 'wp-serverinfo' ) . ': <strong>' . esc_html( format_php_size( get_php_post_max() ) ) . '</strong></li>';
888 echo '<li>' . esc_html__( 'Max Upload Size', 'wp-serverinfo' ) . ': <strong>' . esc_html( format_php_size( get_php_upload_max() ) ) . '</strong></li>';
889 echo '</ul>';
890 echo '<p><strong>MYSQL</strong></p>';
891 echo '<ul>';
892 echo '<li>v<strong>' . esc_html( get_mysql_version() ) . '</strong></li>';
893 echo '<li>' . esc_html__( 'Maximum No. Connections', 'wp-serverinfo' ) . ': <strong>' . esc_html( number_format_i18n( get_mysql_max_allowed_connections(), 0 ) ) . '</strong></li>';
894 echo '<li>' . esc_html__( 'Maximum Packet Size', 'wp-serverinfo' ) . ': <strong>' . esc_html( format_filesize( get_mysql_max_allowed_packet() ) ) . '</strong></li>';
895 echo '<li>' . esc_html__( 'Data Disk Usage', 'wp-serverinfo' ) . ': <strong>' . esc_html( format_filesize( get_mysql_data_usage() ) ) . '</strong></li>';
896 echo '<li>' . esc_html__( 'Index Disk Usage', 'wp-serverinfo' ) . ': <strong>' . esc_html( format_filesize( get_mysql_index_usage() ) ) . '</strong></li>';
897 echo '</ul>';
898 if ( serverinfo_has_redis() ) {
899 $redisinfo = serverinfo_get_redis_stats();
900 if ( $redisinfo ) {
901 $hits = isset( $redisinfo['keyspace_hits'] ) ? (int) $redisinfo['keyspace_hits'] : 0;
902 $misses = isset( $redisinfo['keyspace_misses'] ) ? (int) $redisinfo['keyspace_misses'] : 0;
903 $lookups = $hits + $misses;
904 $hit_pct = $lookups > 0 ? round( ( $hits / $lookups ) * 100, 2 ) : 0;
905 echo '<p><strong>Redis</strong></p>';
906 echo '<ul>';
907 echo '<li>v<strong>' . esc_html( $redisinfo['redis_version'] ?? '' ) . '</strong></li>';
908 echo '<li>' . esc_html__( 'Uptime', 'wp-serverinfo' ) . ': <strong>' . esc_html( number_format_i18n( $redisinfo['uptime_in_days'] ?? 0 ) ) . ' ' . esc_html__( 'days', 'wp-serverinfo' ) . '</strong></li>';
909 echo '<li>' . esc_html__( 'Used Memory', 'wp-serverinfo' ) . ': <strong>' . esc_html( $redisinfo['used_memory_human'] ?? format_filesize( $redisinfo['used_memory'] ?? 0 ) ) . '</strong></li>';
910 echo '<li>' . esc_html__( 'Connected Clients', 'wp-serverinfo' ) . ': <strong>' . esc_html( number_format_i18n( $redisinfo['connected_clients'] ?? 0 ) ) . '</strong></li>';
911 echo '<li>' . esc_html__( 'Hit Ratio', 'wp-serverinfo' ) . ': <strong>' . esc_html( $hit_pct ) . '%</strong></li>';
912 echo '</ul>';
913 }
914 }
915 echo '<p class="textright"><a href="' . esc_url( admin_url( 'index.php?page=wp-serverinfo' ) ) . '" class="button">' . esc_html__( 'View all', 'wp-serverinfo' ) . '</a></p>';
916 echo '</div>';
917 }
918