diff --git a/collectors/cache.php b/collectors/object-cache.php
similarity index 78%
rename from collectors/cache.php
rename to collectors/object-cache.php
index c05239451..fd863f5fd 100644
--- a/collectors/cache.php
+++ b/collectors/object-cache.php
@@ -12,9 +12,9 @@
/**
* @extends QM_DataCollector
*/
-class QM_Collector_Cache extends QM_DataCollector {
+class QM_Collector_Object_Cache extends QM_DataCollector {
- public $id = 'cache';
+ public $id = 'object-cache';
public function get_storage(): QM_Data {
return new QM_Data_Cache();
@@ -26,8 +26,9 @@ public function get_storage(): QM_Data {
public function process() {
global $wp_object_cache;
- $this->data->has_object_cache = (bool) wp_using_ext_object_cache();
+ $this->data->has = (bool) wp_using_ext_object_cache();
$this->data->cache_hit_percentage = 0;
+ $this->data->cache_extensions = array();
if ( is_object( $wp_object_cache ) ) {
$object_vars = get_object_vars( $wp_object_cache );
@@ -95,7 +96,7 @@ public function process() {
$this->data->display_hit_rate_warning = ( 100 === $this->data->cache_hit_percentage );
if ( function_exists( 'extension_loaded' ) ) {
- $this->data->object_cache_extensions = array_map( 'extension_loaded', array(
+ $this->data->cache_extensions = array_map( 'extension_loaded', array(
'Afterburner' => 'afterburner',
'Relay' => 'relay',
'Redis' => 'redis',
@@ -103,16 +104,7 @@ public function process() {
'Memcache' => 'memcache',
'APCu' => 'apcu',
) );
- $this->data->opcode_cache_extensions = array_map( 'extension_loaded', array(
- 'APC' => 'APC',
- 'Zend OPcache' => 'Zend OPcache',
- ) );
- } else {
- $this->data->object_cache_extensions = array();
- $this->data->opcode_cache_extensions = array();
}
-
- $this->data->has_opcode_cache = array_filter( $this->data->opcode_cache_extensions ) ? true : false;
}
}
@@ -122,9 +114,9 @@ public function process() {
* @param QueryMonitor $qm
* @return array
*/
-function register_qm_collector_cache( array $collectors, QueryMonitor $qm ) {
- $collectors['cache'] = new QM_Collector_Cache();
+function register_qm_collector_object_cache( array $collectors, QueryMonitor $qm ) {
+ $collectors['object-cache'] = new QM_Collector_Object_Cache();
return $collectors;
}
-add_filter( 'qm/collectors', 'register_qm_collector_cache', 20, 2 );
+add_filter( 'qm/collectors', 'register_qm_collector_object_cache', 20, 2 );
diff --git a/collectors/opcode-cache.php b/collectors/opcode-cache.php
new file mode 100644
index 000000000..f1813896c
--- /dev/null
+++ b/collectors/opcode-cache.php
@@ -0,0 +1,171 @@
+
+ */
+class QM_Collector_Opcode_Cache extends QM_DataCollector {
+
+ public $id = 'opcode-cache';
+
+ public function get_storage(): QM_Data {
+ return new QM_Data_Cache();
+ }
+
+ /**
+ * @return void
+ */
+ public function process() {
+ $this->data->has = false;
+ $this->data->cache_hit_percentage = 0;
+ $this->data->cache_extensions = array();
+
+ if ( function_exists( 'extension_loaded' ) ) {
+ $this->data->cache_extensions = array_map( 'extension_loaded', array(
+ 'APC' => 'APC',
+ 'Zend OPcache' => 'Zend OPcache',
+ ) );
+ }
+
+ if ( isset( $this->data->cache_extensions['APC'] ) && $this->data->cache_extensions['APC'] ) {
+ $enabled = ini_get( 'apc.enabled' );
+ $enabled = $enabled === '1' || $enabled === 'On';
+
+ if ( function_exists( 'apc_cache_info' ) ) {
+ $stats = apc_cache_info();
+ if ( is_array( $stats ) ) {
+ $this->data->stats = $stats;
+ if ( isset( $this->data->stats['num_hits'] ) ) {
+ $this->data->stats['cache_hits'] = (int) $this->data->stats['num_hits'];
+ }
+ if ( isset( $stats['num_misses'] ) ) {
+ $this->data->stats['cache_misses'] = (int) $this->data->stats['num_misses'];
+ }
+ }
+ }
+ } else {
+ $enabled = ini_get( 'opcache.enable' );
+ $enabled = $enabled === '1' || $enabled === 'On';
+
+ $restrict_api = ini_get( 'opcache.restrict_api' );
+ $api_available = true;
+ if ( ! empty( $restrict_api ) ) {
+ $restrict_api = trailingslashit( $restrict_api );
+ if ( strpos( __DIR__, $restrict_api ) !== 0 ) {
+ $api_available = false;
+ }
+ }
+
+ if ( $enabled ) {
+ $memory_used = -1;
+ $memory_limit = QM_Util::convert_hr_to_bytes( ini_get( 'opcache.memory_consumption' ) . 'M' ); // memory_consumption is in MB
+ $this->data->usage_meters['opcode-memory'] = array(
+ 'type' => 'bytes',
+ 'used' => $memory_used,
+ 'limit' => $memory_limit,
+ 'label' => __( 'Memory', 'query-monitor' ),
+ );
+
+ $interned_strings_used = -1;
+ $interned_strings_limit = QM_Util::convert_hr_to_bytes( ini_get( 'opcache.interned_strings_buffer' ) . 'M' ); // interned_strings_buffer is in MB as well
+ $this->data->usage_meters['strings-memory'] = array(
+ 'type' => 'bytes',
+ 'used' => $interned_strings_used,
+ 'limit' => $interned_strings_limit,
+ 'label' => __( 'Interned Strings Memory', 'query-monitor' ),
+ );
+
+ $cached_files = -1;
+ $max_files = ini_get( 'opcache.max_accelerated_files' );
+ // According to the documentation: The actual value used will be the first number in the set of prime numbers below which is greater than the specified value.
+ // https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.max-accelerated-files
+ $real_max_files = array( 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 );
+ foreach ( $real_max_files as $v ) {
+ if ( $max_files <= $v ) {
+ $real_max_files = $v;
+ break;
+ }
+ }
+
+ $this->data->usage_meters['cached-scripts'] = array(
+ 'type' => 'count',
+ 'used' => $cached_files,
+ 'limit' => $real_max_files,
+ 'label' => __( 'Cached scripts', 'query-monitor' ),
+ );
+ }
+
+ if ( $enabled && $api_available && function_exists( 'opcache_get_status' ) ) {
+ $full_status = opcache_get_status( true );
+
+ if ( is_array( $full_status ) ) {
+ if ( isset( $full_status['opcache_statistics'] ) ) {
+ $this->data->stats = $full_status['opcache_statistics'];
+
+ // Opcache stats are reflecting the hits/misses since the web server started.
+ // We would need to correlate with the included files to get a more accurate hit/miss count.
+ if ( function_exists( 'get_included_files' ) ) {
+ $files_included = get_included_files();
+ $files_hit = count( array_intersect_key( $full_status['scripts'], array_flip( $files_included ) ) );
+ $files_missed = count( $files_included ) - $files_hit;
+
+ $this->data->stats['cache_hits'] = $files_hit;
+ $this->data->stats['cache_misses'] = $files_missed;
+ }
+ }
+
+ if ( isset( $full_status['memory_usage'] ) ) {
+ $memory_used = (int) $full_status['memory_usage']['used_memory'] + (int) $full_status['memory_usage']['wasted_memory'];
+ $memory_free = (int) $full_status['memory_usage']['free_memory'];
+ $memory_limit = $memory_used + $memory_free;
+ $this->data->usage_meters['opcode-memory']['used'] = $memory_used;
+ $this->data->usage_meters['opcode-memory']['limit'] = $memory_limit;
+ }
+
+ if ( isset( $full_status['interned_strings_usage'] ) ) {
+ $this->data->usage_meters['strings-memory']['used'] = (int) $full_status['interned_strings_usage']['used_memory'];
+ $this->data->usage_meters['strings-memory']['limit'] = (int) $full_status['interned_strings_usage']['buffer_size'];
+ }
+
+ $cached_files = isset( $full_status['opcache_statistics']['num_cached_scripts'] ) ? (int) $full_status['opcache_statistics']['num_cached_scripts'] : 0;
+
+ if ( $cached_files ) {
+ $this->data->usage_meters['cached-scripts']['used'] = $cached_files;
+ }
+ }
+ }
+ }
+
+ $this->data->has = $enabled;
+
+ if ( ! empty( $this->data->stats['cache_hits'] ) ) {
+ $total = $this->data->stats['cache_hits'];
+
+ if ( ! empty( $this->data->stats['cache_misses'] ) ) {
+ $total += $this->data->stats['cache_misses'];
+ }
+
+ $this->data->cache_hit_percentage = ( 100 / $total ) * $this->data->stats['cache_hits'];
+ }
+ }
+}
+
+/**
+ * @param array $collectors
+ * @param QueryMonitor $qm
+ * @return array
+ */
+function register_qm_collector_opcode_cache( array $collectors, QueryMonitor $qm ) {
+ $collectors['opcode-cache'] = new QM_Collector_Opcode_Cache();
+ return $collectors;
+}
+
+add_filter( 'qm/collectors', 'register_qm_collector_opcode_cache', 20, 2 );
diff --git a/data/cache.php b/data/cache.php
index 9234830e5..6b2b47d5a 100644
--- a/data/cache.php
+++ b/data/cache.php
@@ -9,18 +9,13 @@ class QM_Data_Cache extends QM_Data {
/**
* @var bool
*/
- public $has_object_cache;
+ public $has;
/**
* @var bool
*/
public $display_hit_rate_warning;
- /**
- * @var bool
- */
- public $has_opcode_cache;
-
/**
* @var int
*/
@@ -34,11 +29,11 @@ class QM_Data_Cache extends QM_Data {
/**
* @var array
*/
- public $object_cache_extensions;
+ public $cache_extensions;
/**
- * @var array
+ * @var array
*/
- public $opcode_cache_extensions;
+ public $usage_meters = array();
}
diff --git a/output/html/overview.php b/output/html/overview.php
index 5f4c4a0c0..569ee512c 100644
--- a/output/html/overview.php
+++ b/output/html/overview.php
@@ -52,8 +52,11 @@ public function output() {
/** @var QM_Collector_Raw_Request|null $raw_request */
$raw_request = QM_Collectors::get( 'raw_request' );
- /** @var QM_Collector_Cache|null $cache */
- $cache = QM_Collectors::get( 'cache' );
+ /** @var QM_Collector_Object_Cache|null $object_cache */
+ $object_cache = QM_Collectors::get( 'object-cache' );
+
+ /** @var QM_Collector_Opcode_Cache|null $opcode_cache */
+ $opcode_cache = QM_Collectors::get( 'opcode-cache' );
/** @var QM_Collector_HTTP|null $http */
$http = QM_Collectors::get( 'http' );
@@ -260,9 +263,9 @@ public function output() {
echo '';
echo '' . esc_html__( 'Object Cache', 'query-monitor' ) . '
';
- if ( $cache ) {
+ if ( $object_cache ) {
/** @var QM_Data_Cache $cache_data */
- $cache_data = $cache->get_data();
+ $cache_data = $object_cache->get_data();
if ( ! empty( $cache_data->stats ) && ! empty( $cache_data->cache_hit_percentage ) ) {
$cache_hit_percentage = $cache_data->cache_hit_percentage;
@@ -278,7 +281,7 @@ public function output() {
echo '
';
}
- if ( $cache_data->has_object_cache ) {
+ if ( $cache_data->has ) {
echo '';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo self::build_link(
@@ -293,7 +296,7 @@ public function output() {
echo esc_html__( 'Persistent object cache plugin not in use', 'query-monitor' );
echo '
';
- $potentials = array_filter( $cache_data->object_cache_extensions );
+ $potentials = array_filter( $cache_data->cache_extensions );
if ( ! empty( $potentials ) ) {
foreach ( $potentials as $name => $value ) {
@@ -334,15 +337,97 @@ public function output() {
echo '';
- if ( $cache ) {
+ echo '';
+ echo '' . esc_html__( 'Opcode Cache', 'query-monitor' ) . '
';
+
+ if ( $opcode_cache ) {
/** @var QM_Data_Cache $cache_data */
- $cache_data = $cache->get_data();
+ $cache_data = $opcode_cache->get_data();
- echo '';
- echo '' . esc_html__( 'Opcode Cache', 'query-monitor' ) . '
';
+ if ( ! empty( $cache_data->stats ) && ! empty( $cache_data->cache_hit_percentage ) ) {
+ $cache_hit_percentage = $cache_data->cache_hit_percentage;
- if ( $cache_data->has_opcode_cache ) {
- foreach ( array_filter( $cache_data->opcode_cache_extensions ) as $opcache_name => $opcache_state ) {
+ echo '';
+ echo esc_html( sprintf(
+ /* translators: 1: Cache hit rate percentage, 2: number of cache hits, 3: number of cache misses */
+ __( '%1$s%% hit rate (%2$s hits, %3$s misses)', 'query-monitor' ),
+ number_format_i18n( $cache_hit_percentage, 1 ),
+ number_format_i18n( $cache_data->stats['cache_hits'], 0 ),
+ number_format_i18n( $cache_data->stats['cache_misses'], 0 )
+ ) );
+ echo '
';
+ } elseif ( $cache_data->has ) {
+ echo '';
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo QueryMonitor::icon( 'warning' );
+ echo esc_html__( 'Opcode cache statistics are not available', 'query-monitor' );
+ echo '
';
+ }
+
+ foreach ( $cache_data->usage_meters as $meter_name => $meter ) {
+ echo '';
+ echo '' . esc_html( $meter['label'] ) . '
';
+ switch ( $meter['type'] ) {
+ case 'bytes':
+ if ( $meter['used'] >= 0 ) {
+ echo esc_html( sprintf(
+ /* translators: 1: Memory used in bytes, 2: Memory used in megabytes */
+ __( '%1$s bytes (%2$s MB)', 'query-monitor' ),
+ number_format_i18n( $meter['used'] ),
+ number_format_i18n( ( $meter['used'] / 1024 / 1024 ), 1 )
+ ) );
+ if ( $meter['limit'] >= 0 ) {
+ $usage_percentage = ( $meter['used'] / $meter['limit'] ) * 100;
+ echo '
';
+ echo esc_html( sprintf(
+ /* translators: 1: Percentage of limit used, 2: Bytes meter server limit in megabytes */
+ __( '%1$s%% of %2$s MB server limit', 'query-monitor' ),
+ number_format_i18n( $usage_percentage, 1 ),
+ number_format_i18n( $meter['limit'] / 1024 / 1024 )
+ ) );
+ echo '';
+ }
+ } elseif ( $meter['limit'] >= 0 ) {
+ echo '';
+ echo esc_html( sprintf(
+ /* translators: 1: Bytes meter server limit in megabytes */
+ __( '%1$s MB server limit', 'query-monitor' ),
+ number_format_i18n( $meter['limit'] / 1024 / 1024 )
+ ) );
+ echo '';
+ }
+ break;
+ case 'count':
+ if ( $meter['used'] >= 0 ) {
+ echo esc_html( number_format_i18n( $meter['used'] ) );
+
+ if ( $meter['limit'] >= 0 ) {
+ $usage_percentage = ( $meter['used'] / $meter['limit'] ) * 100;
+ echo '
';
+ echo esc_html( sprintf(
+ /* translators: 1: Percentage of limit used, 2: Counter meter server limit */
+ __( '%1$s%% of %2$s server limit', 'query-monitor' ),
+ number_format_i18n( $usage_percentage, 1 ),
+ number_format_i18n( $meter['limit'] )
+ ) );
+ echo '';
+ }
+ } elseif ( $meter['limit'] >= 0 ) {
+ echo '';
+ echo esc_html( sprintf(
+ /* translators: 1: Counter meter server limit */
+ __( '%1$s server limit', 'query-monitor' ),
+ number_format_i18n( $meter['limit'] )
+ ) );
+ echo '';
+ }
+ break;
+ }
+ echo '
';
+ }
+
+ if ( $cache_data->has ) {
+ foreach ( array_filter( $cache_data->cache_extensions ) as $opcache_name => $opcache_state ) {
echo '';
echo esc_html( sprintf(
/* translators: %s: Name of cache driver */
@@ -357,14 +442,36 @@ public function output() {
echo QueryMonitor::icon( 'warning' );
echo esc_html__( 'Opcode cache not in use', 'query-monitor' );
echo '
';
- echo '';
- echo esc_html__( 'Speak to your web host about enabling an opcode cache such as OPcache.', 'query-monitor' );
- echo '
';
- }
- echo '';
+ $potentials = array_filter( $cache_data->cache_extensions );
+
+ if ( ! empty( $potentials ) ) {
+ foreach ( $potentials as $name => $value ) {
+ echo '';
+ echo esc_html(
+ sprintf(
+ /* translators: 1: PHP extension name */
+ __( 'The %1$s opcode extension for PHP is installed but is not enabled. Speak to your web host about enabling it.', 'query-monitor' ),
+ esc_html( $name )
+ )
+ );
+ echo '
';
+ break;
+ }
+ } else {
+ echo '';
+ echo esc_html__( 'Speak to your web host about installing and enabling an opcode cache such as OPcache.', 'query-monitor' );
+ echo '
';
+ }
+ }
+ } else {
+ echo '';
+ echo esc_html__( 'Opcode cache statistics are not available', 'query-monitor' );
+ echo '
';
}
+ echo '';
+
$this->after_non_tabular_output();
}
@@ -395,7 +502,6 @@ public function admin_title( array $title ) {
return $title;
}
-
}
/**
diff --git a/output/raw/cache.php b/output/raw/object-cache.php
similarity index 70%
rename from output/raw/cache.php
rename to output/raw/object-cache.php
index dd7aecbd6..1501b5d62 100644
--- a/output/raw/cache.php
+++ b/output/raw/object-cache.php
@@ -5,12 +5,12 @@
* @package query-monitor
*/
-class QM_Output_Raw_Cache extends QM_Output_Raw {
+class QM_Output_Raw_Object_Cache extends QM_Output_Raw {
/**
* Collector instance.
*
- * @var QM_Collector_Cache Collector.
+ * @var QM_Collector_Object_Cache Collector.
*/
protected $collector;
@@ -49,12 +49,12 @@ public function get_output() {
* @param QM_Collectors $collectors
* @return array
*/
-function register_qm_output_raw_cache( array $output, QM_Collectors $collectors ) {
- $collector = QM_Collectors::get( 'cache' );
+function register_qm_output_raw_object_cache( array $output, QM_Collectors $collectors ) {
+ $collector = QM_Collectors::get( 'object-cache' );
if ( $collector ) {
- $output['cache'] = new QM_Output_Raw_Cache( $collector );
+ $output['object-cache'] = new QM_Output_Raw_Object_Cache( $collector );
}
return $output;
}
-add_filter( 'qm/outputter/raw', 'register_qm_output_raw_cache', 30, 2 );
+add_filter( 'qm/outputter/raw', 'register_qm_output_raw_object_cache', 30, 2 );