monorepo/native/wordpress/maplepress-plugin/includes/class-maplepress-system-info.php

454 lines
13 KiB
PHP

<?php
/**
* System Information Collector
*
* Collects detailed system information for diagnosing performance bottlenecks.
*
* @package MaplePress
*/
class MaplePress_System_Info {
/**
* Get all system information.
*
* @return array System information array.
*/
public static function get_all_info() {
return array(
'php' => self::get_php_info(),
'php_fpm' => self::get_php_fpm_info(),
'server' => self::get_server_info(),
'wordpress' => self::get_wordpress_info(),
'database' => self::get_database_info(),
'performance' => self::get_performance_info(),
);
}
/**
* Get PHP configuration information.
*
* @return array PHP configuration.
*/
private static function get_php_info() {
return array(
'version' => PHP_VERSION,
'sapi' => php_sapi_name(),
'memory_limit' => ini_get( 'memory_limit' ),
'max_execution_time' => ini_get( 'max_execution_time' ),
'max_input_time' => ini_get( 'max_input_time' ),
'post_max_size' => ini_get( 'post_max_size' ),
'upload_max_filesize' => ini_get( 'upload_max_filesize' ),
'max_input_vars' => ini_get( 'max_input_vars' ),
'display_errors' => ini_get( 'display_errors' ),
'error_reporting' => ini_get( 'error_reporting' ),
'opcache_enabled' => function_exists( 'opcache_get_status' ) && opcache_get_status() !== false,
'opcache_status' => function_exists( 'opcache_get_status' ) ? opcache_get_status() : null,
);
}
/**
* Get PHP-FPM configuration information.
*
* @return array PHP-FPM configuration.
*/
private static function get_php_fpm_info() {
$info = array(
'is_fpm' => false,
'pool_name' => 'N/A',
'pm_type' => 'N/A',
'max_children' => 'N/A',
'start_servers' => 'N/A',
'min_spare_servers' => 'N/A',
'max_spare_servers' => 'N/A',
'max_requests' => 'N/A',
'status_url' => 'N/A',
'active_processes' => 'N/A',
'total_processes' => 'N/A',
'idle_processes' => 'N/A',
'listen_queue' => 'N/A',
'listen_queue_len' => 'N/A',
'config_file' => 'N/A',
);
// Check if running under PHP-FPM
if ( php_sapi_name() === 'fpm-fcgi' || php_sapi_name() === 'cgi-fcgi' ) {
$info['is_fpm'] = true;
// Try to find PHP-FPM config file
$possible_config_paths = array(
'/etc/php/' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '/fpm/pool.d/www.conf',
'/usr/local/etc/php-fpm.d/www.conf',
'/etc/php-fpm.d/www.conf',
'/etc/php/fpm/pool.d/www.conf',
);
foreach ( $possible_config_paths as $path ) {
if ( file_exists( $path ) && is_readable( $path ) ) {
$info['config_file'] = $path;
$config = self::parse_php_fpm_config( $path );
if ( $config ) {
$info = array_merge( $info, $config );
}
break;
}
}
// Try to get PHP-FPM status via FastCGI
$status = self::get_php_fpm_status();
if ( $status ) {
$info = array_merge( $info, $status );
}
}
return $info;
}
/**
* Parse PHP-FPM configuration file.
*
* @param string $config_file Path to config file.
* @return array|null Parsed configuration or null.
*/
private static function parse_php_fpm_config( $config_file ) {
$config = array();
$content = file_get_contents( $config_file );
if ( ! $content ) {
return null;
}
// Parse key configuration values (ignore comments after values)
$patterns = array(
'pool_name' => '/^\[([^\]]+)\]/m',
'pm_type' => '/^pm\s*=\s*(\w+)/m',
'max_children' => '/^pm\.max_children\s*=\s*(\d+)/m',
'start_servers' => '/^pm\.start_servers\s*=\s*(\d+)/m',
'min_spare_servers' => '/^pm\.min_spare_servers\s*=\s*(\d+)/m',
'max_spare_servers' => '/^pm\.max_spare_servers\s*=\s*(\d+)/m',
'max_requests' => '/^pm\.max_requests\s*=\s*(\d+)/m',
);
foreach ( $patterns as $key => $pattern ) {
if ( preg_match( $pattern, $content, $matches ) ) {
$config[ $key ] = trim( $matches[1] );
}
}
return $config;
}
/**
* Get PHP-FPM status from status endpoint.
*
* @return array|null Status information or null.
*/
private static function get_php_fpm_status() {
// Try common PHP-FPM status URLs
$status_urls = array(
'http://localhost/fpm-status',
'http://127.0.0.1/fpm-status',
'http://localhost/status',
);
foreach ( $status_urls as $url ) {
$response = wp_remote_get( $url . '?json' );
if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
if ( $data ) {
return array(
'pool_name' => $data['pool'] ?? 'N/A',
'total_processes' => $data['total processes'] ?? 'N/A',
'active_processes' => $data['active processes'] ?? 'N/A',
'idle_processes' => $data['idle processes'] ?? 'N/A',
'listen_queue' => $data['listen queue'] ?? 'N/A',
'listen_queue_len' => $data['listen queue len'] ?? 'N/A',
'max_children' => $data['max children reached'] ?? 'N/A',
);
}
}
}
return null;
}
/**
* Get server information.
*
* @return array Server information.
*/
private static function get_server_info() {
return array(
'software' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : 'Unknown',
'os' => PHP_OS,
'hostname' => gethostname(),
'server_addr' => isset( $_SERVER['SERVER_ADDR'] ) ? $_SERVER['SERVER_ADDR'] : 'Unknown',
'document_root' => isset( $_SERVER['DOCUMENT_ROOT'] ) ? $_SERVER['DOCUMENT_ROOT'] : 'Unknown',
'cpu_count' => self::get_cpu_count(),
'memory_total' => self::get_memory_total(),
'memory_available' => self::get_memory_available(),
'load_average' => self::get_load_average(),
);
}
/**
* Get CPU core count.
*
* @return int|string CPU count or 'Unknown'.
*/
private static function get_cpu_count() {
if ( function_exists( 'shell_exec' ) ) {
// Linux
$output = shell_exec( 'nproc 2>/dev/null' );
if ( $output ) {
return (int) trim( $output );
}
// macOS
$output = shell_exec( 'sysctl -n hw.ncpu 2>/dev/null' );
if ( $output ) {
return (int) trim( $output );
}
}
return 'Unknown';
}
/**
* Get total system memory in MB.
*
* @return string Memory in MB or 'Unknown'.
*/
private static function get_memory_total() {
if ( function_exists( 'shell_exec' ) ) {
// Linux
$output = shell_exec( "free -m | grep Mem | awk '{print $2}'" );
if ( $output ) {
return trim( $output ) . ' MB';
}
// macOS
$output = shell_exec( 'sysctl -n hw.memsize 2>/dev/null' );
if ( $output ) {
return round( (int) trim( $output ) / 1024 / 1024 ) . ' MB';
}
}
return 'Unknown';
}
/**
* Get available system memory in MB.
*
* @return string Available memory or 'Unknown'.
*/
private static function get_memory_available() {
if ( function_exists( 'shell_exec' ) ) {
// Linux
$output = shell_exec( "free -m | grep Mem | awk '{print $7}'" );
if ( $output ) {
return trim( $output ) . ' MB';
}
}
return 'Unknown';
}
/**
* Get system load average.
*
* @return string Load average or 'Unknown'.
*/
private static function get_load_average() {
if ( function_exists( 'sys_getloadavg' ) ) {
$load = sys_getloadavg();
return sprintf( '%.2f, %.2f, %.2f', $load[0], $load[1], $load[2] );
}
return 'Unknown';
}
/**
* Get WordPress information.
*
* @return array WordPress information.
*/
private static function get_wordpress_info() {
global $wp_version;
return array(
'version' => $wp_version,
'multisite' => is_multisite(),
'site_url' => get_site_url(),
'home_url' => get_home_url(),
'debug_mode' => defined( 'WP_DEBUG' ) && WP_DEBUG,
'memory_limit' => WP_MEMORY_LIMIT,
'max_memory_limit' => WP_MAX_MEMORY_LIMIT,
'theme' => wp_get_theme()->get( 'Name' ),
'active_plugins' => count( get_option( 'active_plugins', array() ) ),
);
}
/**
* Get database information.
*
* @return array Database information.
*/
private static function get_database_info() {
global $wpdb;
$info = array(
'extension' => $wpdb->db_version(),
'server' => $wpdb->get_var( 'SELECT VERSION()' ),
'client_version' => $wpdb->db_version(),
'database_name' => DB_NAME,
'database_host' => DB_HOST,
'database_charset' => DB_CHARSET,
'table_prefix' => $wpdb->prefix,
);
// Get MySQL configuration
$variables = array(
'max_connections',
'max_allowed_packet',
'query_cache_size',
'innodb_buffer_pool_size',
'tmp_table_size',
'max_heap_table_size',
);
foreach ( $variables as $var ) {
$value = $wpdb->get_var( "SHOW VARIABLES LIKE '$var'" );
if ( $value ) {
$info[ $var ] = $wpdb->get_var( "SHOW VARIABLES LIKE '$var'", 1 );
}
}
// Get current connections
$info['current_connections'] = $wpdb->get_var( "SHOW STATUS LIKE 'Threads_connected'" )
? $wpdb->get_var( "SHOW STATUS LIKE 'Threads_connected'", 1 )
: 'Unknown';
return $info;
}
/**
* Get performance-related information.
*
* @return array Performance information.
*/
private static function get_performance_info() {
return array(
'object_cache' => wp_using_ext_object_cache() ? 'External' : 'Built-in',
'curl_available' => function_exists( 'curl_version' ),
'curl_version' => function_exists( 'curl_version' ) ? curl_version()['version'] : 'N/A',
'allow_url_fopen' => ini_get( 'allow_url_fopen' ) ? 'Yes' : 'No',
'compression' => extension_loaded( 'zlib' ) ? 'Available' : 'Not available',
'https_support' => is_ssl() ? 'Yes' : 'No',
);
}
/**
* Format system info as HTML.
*
* @param array $info System information array.
* @return string HTML output.
*/
public static function format_as_html( $info ) {
ob_start();
?>
<div class="maplepress-system-info">
<?php foreach ( $info as $section => $data ) : ?>
<div class="system-info-section">
<h3 style="margin-top: 0; padding: 10px; background: #f0f0f1; border-left: 4px solid #2271b1;">
<?php echo esc_html( ucwords( str_replace( '_', ' ', $section ) ) ); ?>
</h3>
<table class="widefat" style="margin-top: 0;">
<tbody>
<?php foreach ( $data as $key => $value ) : ?>
<?php
// Check if this is the opcache_status field (which contains a large array)
$is_opcache_status = ( $key === 'opcache_status' && is_array( $value ) );
$field_id = 'maplepress-field-' . sanitize_title( $section . '-' . $key );
?>
<tr>
<td style="width: 250px; font-weight: bold; vertical-align: top;">
<?php echo esc_html( ucwords( str_replace( '_', ' ', $key ) ) ); ?>
<?php if ( $is_opcache_status ) : ?>
<br>
<button type="button"
class="button button-secondary button-small maplepress-toggle-field"
data-target="<?php echo esc_attr( $field_id ); ?>"
style="margin-top: 5px;">
<span class="dashicons dashicons-visibility" style="vertical-align: middle; font-size: 14px;"></span>
<span class="toggle-text">Show</span>
</button>
<?php endif; ?>
</td>
<td>
<?php if ( $is_opcache_status ) : ?>
<div id="<?php echo esc_attr( $field_id ); ?>" style="display: none;">
<pre><?php echo esc_html( print_r( $value, true ) ); ?></pre>
</div>
<span class="opcache-hidden-notice" style="color: #666; font-style: italic;">
Click "Show" to view opcache details
</span>
<?php elseif ( is_array( $value ) ) : ?>
<pre><?php echo esc_html( print_r( $value, true ) ); ?></pre>
<?php elseif ( is_bool( $value ) ) : ?>
<?php echo $value ? '<span style="color: green;">✓ Yes</span>' : '<span style="color: red;">✗ No</span>'; ?>
<?php else : ?>
<?php echo esc_html( $value ); ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endforeach; ?>
</div>
<style>
.maplepress-system-info .system-info-section {
margin-bottom: 30px;
}
.maplepress-system-info pre {
margin: 0;
padding: 10px;
background: #f9f9f9;
border-left: 3px solid #ccc;
overflow-x: auto;
}
</style>
<script>
jQuery(document).ready(function($) {
// Toggle field visibility for opcache_status
$('.maplepress-toggle-field').on('click', function() {
var button = $(this);
var targetId = button.data('target');
var content = $('#' + targetId);
var notice = button.closest('tr').find('.opcache-hidden-notice');
var icon = button.find('.dashicons');
var text = button.find('.toggle-text');
if (content.is(':visible')) {
content.slideUp(300);
notice.show();
icon.removeClass('dashicons-hidden').addClass('dashicons-visibility');
text.text('Show');
} else {
content.slideDown(300);
notice.hide();
icon.removeClass('dashicons-visibility').addClass('dashicons-hidden');
text.text('Hide');
}
});
});
</script>
<?php
return ob_get_clean();
}
}