From 7df2852b092bfb03f004bc194840789b44c15923 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 20:30:36 +0000 Subject: [PATCH 1/5] Initial plan From b51a75feeb4a09bf668bfb1fc39f5d156122c4ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 20:37:01 +0000 Subject: [PATCH 2/5] feat: integrate with persistent object caches by tracking redis_calls metric Agent-Logs-Url: https://github.com/wp-cli/profile-command/sessions/d00efdc1-7144-4e26-b76c-880aeca9a768 Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/profile-eval.feature | 131 +++++++++++++++++++++++++++++++++- src/Command.php | 4 ++ src/Logger.php | 22 ++++-- src/Profiler.php | 21 ++++-- 4 files changed, 164 insertions(+), 14 deletions(-) diff --git a/features/profile-eval.feature b/features/profile-eval.feature index 6a0a694a..d710982f 100644 --- a/features/profile-eval.feature +++ b/features/profile-eval.feature @@ -10,10 +10,10 @@ Feature: Profile arbitary code execution } """ - When I run `wp profile eval 'wp_cli_do_nothing();' --fields=query_time,query_count,cache_ratio,cache_hits,cache_misses,request_time,request_count` + When I run `wp profile eval 'wp_cli_do_nothing();' --fields=query_time,query_count,cache_ratio,cache_hits,cache_misses,redis_calls,request_time,request_count` Then STDOUT should be a table containing rows: - | query_time | query_count | cache_ratio | cache_hits | cache_misses | request_time | request_count | - | 0s | 0 | | 0 | 0 | 0s | 0 | + | query_time | query_count | cache_ratio | cache_hits | cache_misses | redis_calls | request_time | request_count | + | 0s | 0 | | 0 | 0 | 0 | 0s | 0 | Scenario: Profile a function that makes one HTTP request Given a WP install @@ -48,3 +48,128 @@ Feature: Profile arbitary code execution Then STDOUT should be a table containing rows: | callback | cache_hits | cache_misses | | function(){} | 0 | 1 | + + Scenario: redis_calls shows 0 when no persistent object cache is active + Given a WP install + + When I run `wp profile eval 'wp_cache_get( "foo" );' --fields=cache_misses,redis_calls` + Then STDOUT should be a table containing rows: + | cache_misses | redis_calls | + | 1 | 0 | + + Scenario: redis_calls tracks calls to a persistent object cache that exposes redis_calls + Given a WP install + And a wp-content/object-cache.php file: + """ + add( $key, $data, $group, (int) $expire ); + } + function wp_cache_add_global_groups( $groups ) { + global $wp_object_cache; + $wp_object_cache->add_global_groups( $groups ); + } + function wp_cache_add_non_persistent_groups( $groups ) {} + function wp_cache_close() { return true; } + function wp_cache_decr( $key, $offset = 1, $group = '' ) { + global $wp_object_cache; + return $wp_object_cache->decr( $key, $offset, $group ); + } + function wp_cache_delete( $key, $group = '' ) { + global $wp_object_cache; + return $wp_object_cache->delete( $key, $group ); + } + function wp_cache_flush() { + global $wp_object_cache; + return $wp_object_cache->flush(); + } + function wp_cache_get( $key, $group = '', $force = false, &$found = null ) { + global $wp_object_cache; + return $wp_object_cache->get( $key, $group, $force, $found ); + } + function wp_cache_incr( $key, $offset = 1, $group = '' ) { + global $wp_object_cache; + return $wp_object_cache->incr( $key, $offset, $group ); + } + function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) { + global $wp_object_cache; + return $wp_object_cache->replace( $key, $data, $group, (int) $expire ); + } + function wp_cache_set( $key, $data, $group = '', $expire = 0 ) { + global $wp_object_cache; + return $wp_object_cache->set( $key, $data, $group, (int) $expire ); + } + function wp_cache_switch_to_blog( $blog_id ) {} + class WP_Object_Cache_With_Redis_Calls { + public $cache = array(); + public $cache_hits = 0; + public $cache_misses = 0; + public $redis_calls = array(); + private $global_groups = array(); + private function cache_key( $key, $group ) { + return $group . ':' . $key; + } + private function track( $command ) { + $this->redis_calls[ $command ] = isset( $this->redis_calls[ $command ] ) ? $this->redis_calls[ $command ] + 1 : 1; + } + public function add( $key, $data, $group = 'default', $expire = 0 ) { + if ( isset( $this->cache[ $this->cache_key( $key, $group ) ] ) ) { return false; } + return $this->set( $key, $data, $group, $expire ); + } + public function set( $key, $data, $group = 'default', $expire = 0 ) { + $this->cache[ $this->cache_key( $key, $group ) ] = $data; + $this->track( 'set' ); + return true; + } + public function get( $key, $group = 'default', $force = false, &$found = null ) { + $this->track( 'get' ); + if ( isset( $this->cache[ $this->cache_key( $key, $group ) ] ) ) { + $found = true; + $this->cache_hits++; + return $this->cache[ $this->cache_key( $key, $group ) ]; + } + $found = false; + $this->cache_misses++; + return false; + } + public function delete( $key, $group = 'default' ) { + $cache_key = $this->cache_key( $key, $group ); + if ( ! isset( $this->cache[ $cache_key ] ) ) { return false; } + unset( $this->cache[ $cache_key ] ); + $this->track( 'del' ); + return true; + } + public function flush() { $this->cache = array(); return true; } + public function decr( $key, $offset = 1, $group = 'default' ) { + $cache_key = $this->cache_key( $key, $group ); + if ( ! isset( $this->cache[ $cache_key ] ) ) { return false; } + $this->cache[ $cache_key ] = max( 0, (int) $this->cache[ $cache_key ] - $offset ); + $this->track( 'decrby' ); + return $this->cache[ $cache_key ]; + } + public function incr( $key, $offset = 1, $group = 'default' ) { + $cache_key = $this->cache_key( $key, $group ); + if ( ! isset( $this->cache[ $cache_key ] ) ) { return false; } + $this->cache[ $cache_key ] = (int) $this->cache[ $cache_key ] + $offset; + $this->track( 'incrby' ); + return $this->cache[ $cache_key ]; + } + public function replace( $key, $data, $group = 'default', $expire = 0 ) { + if ( ! isset( $this->cache[ $this->cache_key( $key, $group ) ] ) ) { return false; } + return $this->set( $key, $data, $group, $expire ); + } + public function add_global_groups( $groups ) { + foreach ( (array) $groups as $group ) { $this->global_groups[ $group ] = true; } + } + } + """ + + When I run `wp profile eval 'wp_cache_set( "foo", "bar" ); wp_cache_get( "foo" ); wp_cache_get( "foo" );' --fields=cache_hits,cache_misses,redis_calls` + Then STDOUT should be a table containing rows: + | cache_hits | cache_misses | redis_calls | + | 2 | 0 | 3 | diff --git a/src/Command.php b/src/Command.php index 1f7d4463..213b05dd 100644 --- a/src/Command.php +++ b/src/Command.php @@ -164,6 +164,7 @@ public function stage( $args, $assoc_args ) { 'cache_ratio', 'cache_hits', 'cache_misses', + 'redis_calls', 'request_time', 'request_count', ); @@ -178,6 +179,7 @@ public function stage( $args, $assoc_args ) { 'cache_ratio', 'cache_hits', 'cache_misses', + 'redis_calls', 'hook_time', 'hook_count', 'request_time', @@ -300,6 +302,7 @@ public function hook( $args, $assoc_args ) { 'cache_ratio', 'cache_hits', 'cache_misses', + 'redis_calls', 'request_time', 'request_count', ); @@ -522,6 +525,7 @@ private static function profile_eval_ish( $assoc_args, $profile_callback, $order 'cache_ratio', 'cache_hits', 'cache_misses', + 'redis_calls', 'request_time', 'request_count', ) diff --git a/src/Logger.php b/src/Logger.php index ff2882bb..c231a51a 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -29,6 +29,8 @@ class Logger { /** @var string|null */ public $cache_ratio = null; /** @var int */ + public $redis_calls = 0; + /** @var int */ public $hook_count = 0; /** @var float */ public $hook_time = 0; @@ -44,6 +46,8 @@ class Logger { private $cache_hit_offset = null; /** @var int|null */ private $cache_miss_offset = null; + /** @var int|null */ + private $redis_calls_offset = null; /** @var float|null */ private $hook_start_time = null; /** @var int */ @@ -119,6 +123,9 @@ public function start() { } $this->cache_hit_offset = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0; $this->cache_miss_offset = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0; + if ( isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ) { + $this->redis_calls_offset = (int) array_sum( $wp_object_cache->redis_calls ); + } } /** @@ -164,11 +171,16 @@ public function stop() { } } - $this->start_time = null; - $this->query_offset = null; - $this->cache_hit_offset = null; - $this->cache_miss_offset = null; - $key = array_search( $this, self::$active_loggers, true ); + if ( ! is_null( $this->redis_calls_offset ) && isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ) { + $this->redis_calls = (int) array_sum( $wp_object_cache->redis_calls ) - $this->redis_calls_offset; + } + + $this->start_time = null; + $this->query_offset = null; + $this->cache_hit_offset = null; + $this->cache_miss_offset = null; + $this->redis_calls_offset = null; + $key = array_search( $this, self::$active_loggers, true ); if ( false !== $key ) { unset( self::$active_loggers[ $key ] ); diff --git a/src/Profiler.php b/src/Profiler.php index 37ae2ef8..7d523550 100644 --- a/src/Profiler.php +++ b/src/Profiler.php @@ -63,6 +63,8 @@ class Profiler { private $tick_cache_hit_offset = null; /** @var int|null */ private $tick_cache_miss_offset = null; + /** @var int|null */ + private $tick_redis_calls_offset = null; /** @var bool */ private $is_admin_request = false; @@ -403,6 +405,7 @@ public function handle_function_tick() { 'cache_hits' => 0, 'cache_misses' => 0, 'cache_ratio' => null, + 'redis_calls' => 0, ); } @@ -439,6 +442,11 @@ public function handle_function_tick() { $ratio = ( $logger_data['cache_hits'] / $total ) * 100; $logger_data['cache_ratio'] = round( $ratio, 2 ) . '%'; } + + if ( ! is_null( $this->tick_redis_calls_offset ) && isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ) { + $current_redis_calls = isset( $logger_data['redis_calls'] ) && is_numeric( $logger_data['redis_calls'] ) ? $logger_data['redis_calls'] : 0; + $logger_data['redis_calls'] = ( (int) array_sum( $wp_object_cache->redis_calls ) - $this->tick_redis_calls_offset ) + (int) $current_redis_calls; + } } $this->loggers[ $callback_hash ] = $logger_data; } @@ -479,12 +487,13 @@ public function handle_function_tick() { } } - $this->tick_callback = $callback; - $this->tick_location = $location; - $this->tick_start_time = microtime( true ); - $this->tick_query_offset = ! empty( $wpdb->queries ) ? count( $wpdb->queries ) : 0; - $this->tick_cache_hit_offset = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0; - $this->tick_cache_miss_offset = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0; + $this->tick_callback = $callback; + $this->tick_location = $location; + $this->tick_start_time = microtime( true ); + $this->tick_query_offset = ! empty( $wpdb->queries ) ? count( $wpdb->queries ) : 0; + $this->tick_cache_hit_offset = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0; + $this->tick_cache_miss_offset = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0; + $this->tick_redis_calls_offset = isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ? (int) array_sum( $wp_object_cache->redis_calls ) : null; } /** From b9f600ddd6254fffe7fdf4c42388bf48c40c242b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 20:38:14 +0000 Subject: [PATCH 3/5] refactor: improve readability of redis_calls tracking code per review feedback Agent-Logs-Url: https://github.com/wp-cli/profile-command/sessions/d00efdc1-7144-4e26-b76c-880aeca9a768 Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/profile-eval.feature | 29 ++++++++++++++++++++++------- src/Profiler.php | 3 ++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/features/profile-eval.feature b/features/profile-eval.feature index d710982f..8baf0dd2 100644 --- a/features/profile-eval.feature +++ b/features/profile-eval.feature @@ -118,7 +118,9 @@ Feature: Profile arbitary code execution $this->redis_calls[ $command ] = isset( $this->redis_calls[ $command ] ) ? $this->redis_calls[ $command ] + 1 : 1; } public function add( $key, $data, $group = 'default', $expire = 0 ) { - if ( isset( $this->cache[ $this->cache_key( $key, $group ) ] ) ) { return false; } + if ( isset( $this->cache[ $this->cache_key( $key, $group ) ] ) ) { + return false; + } return $this->set( $key, $data, $group, $expire ); } public function set( $key, $data, $group = 'default', $expire = 0 ) { @@ -139,32 +141,45 @@ Feature: Profile arbitary code execution } public function delete( $key, $group = 'default' ) { $cache_key = $this->cache_key( $key, $group ); - if ( ! isset( $this->cache[ $cache_key ] ) ) { return false; } + if ( ! isset( $this->cache[ $cache_key ] ) ) { + return false; + } unset( $this->cache[ $cache_key ] ); $this->track( 'del' ); return true; } - public function flush() { $this->cache = array(); return true; } + public function flush() { + $this->cache = array(); + return true; + } public function decr( $key, $offset = 1, $group = 'default' ) { $cache_key = $this->cache_key( $key, $group ); - if ( ! isset( $this->cache[ $cache_key ] ) ) { return false; } + if ( ! isset( $this->cache[ $cache_key ] ) ) { + return false; + } $this->cache[ $cache_key ] = max( 0, (int) $this->cache[ $cache_key ] - $offset ); $this->track( 'decrby' ); return $this->cache[ $cache_key ]; } public function incr( $key, $offset = 1, $group = 'default' ) { $cache_key = $this->cache_key( $key, $group ); - if ( ! isset( $this->cache[ $cache_key ] ) ) { return false; } + if ( ! isset( $this->cache[ $cache_key ] ) ) { + return false; + } $this->cache[ $cache_key ] = (int) $this->cache[ $cache_key ] + $offset; $this->track( 'incrby' ); return $this->cache[ $cache_key ]; } public function replace( $key, $data, $group = 'default', $expire = 0 ) { - if ( ! isset( $this->cache[ $this->cache_key( $key, $group ) ] ) ) { return false; } + if ( ! isset( $this->cache[ $this->cache_key( $key, $group ) ] ) ) { + return false; + } return $this->set( $key, $data, $group, $expire ); } public function add_global_groups( $groups ) { - foreach ( (array) $groups as $group ) { $this->global_groups[ $group ] = true; } + foreach ( (array) $groups as $group ) { + $this->global_groups[ $group ] = true; + } } } """ diff --git a/src/Profiler.php b/src/Profiler.php index 7d523550..59b8e174 100644 --- a/src/Profiler.php +++ b/src/Profiler.php @@ -445,7 +445,8 @@ public function handle_function_tick() { if ( ! is_null( $this->tick_redis_calls_offset ) && isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ) { $current_redis_calls = isset( $logger_data['redis_calls'] ) && is_numeric( $logger_data['redis_calls'] ) ? $logger_data['redis_calls'] : 0; - $logger_data['redis_calls'] = ( (int) array_sum( $wp_object_cache->redis_calls ) - $this->tick_redis_calls_offset ) + (int) $current_redis_calls; + $delta = (int) array_sum( $wp_object_cache->redis_calls ) - $this->tick_redis_calls_offset; + $logger_data['redis_calls'] = $delta + (int) $current_redis_calls; } } $this->loggers[ $callback_hash ] = $logger_data; From 20c95b975d9d439960e1d5016c70105999db281a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 12:22:36 +0000 Subject: [PATCH 4/5] refactor: rename redis_calls to cache_calls and support multiple object cache backends Agent-Logs-Url: https://github.com/wp-cli/profile-command/sessions/96870b9d-2d37-4ac3-b192-b5d30a51d5fe Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/profile-eval.feature | 24 +++++++++---------- src/Command.php | 8 +++---- src/Logger.php | 45 +++++++++++++++++++++++++++++------ src/Profiler.php | 17 +++++++------ 4 files changed, 64 insertions(+), 30 deletions(-) diff --git a/features/profile-eval.feature b/features/profile-eval.feature index 8baf0dd2..3e3e8e34 100644 --- a/features/profile-eval.feature +++ b/features/profile-eval.feature @@ -10,9 +10,9 @@ Feature: Profile arbitary code execution } """ - When I run `wp profile eval 'wp_cli_do_nothing();' --fields=query_time,query_count,cache_ratio,cache_hits,cache_misses,redis_calls,request_time,request_count` + When I run `wp profile eval 'wp_cli_do_nothing();' --fields=query_time,query_count,cache_ratio,cache_hits,cache_misses,cache_calls,request_time,request_count` Then STDOUT should be a table containing rows: - | query_time | query_count | cache_ratio | cache_hits | cache_misses | redis_calls | request_time | request_count | + | query_time | query_count | cache_ratio | cache_hits | cache_misses | cache_calls | request_time | request_count | | 0s | 0 | | 0 | 0 | 0 | 0s | 0 | Scenario: Profile a function that makes one HTTP request @@ -49,22 +49,22 @@ Feature: Profile arbitary code execution | callback | cache_hits | cache_misses | | function(){} | 0 | 1 | - Scenario: redis_calls shows 0 when no persistent object cache is active + Scenario: cache_calls shows 0 when no persistent object cache is active Given a WP install - When I run `wp profile eval 'wp_cache_get( "foo" );' --fields=cache_misses,redis_calls` + When I run `wp profile eval 'wp_cache_get( "foo" );' --fields=cache_misses,cache_calls` Then STDOUT should be a table containing rows: - | cache_misses | redis_calls | + | cache_misses | cache_calls | | 1 | 0 | - Scenario: redis_calls tracks calls to a persistent object cache that exposes redis_calls + Scenario: cache_calls tracks calls to a persistent object cache that exposes cache_calls Given a WP install And a wp-content/object-cache.php file: """ set( $key, $data, $group, (int) $expire ); } function wp_cache_switch_to_blog( $blog_id ) {} - class WP_Object_Cache_With_Redis_Calls { + class WP_Object_Cache_With_Cache_Calls { public $cache = array(); public $cache_hits = 0; public $cache_misses = 0; - public $redis_calls = array(); + public $cache_calls = array(); private $global_groups = array(); private function cache_key( $key, $group ) { return $group . ':' . $key; } private function track( $command ) { - $this->redis_calls[ $command ] = isset( $this->redis_calls[ $command ] ) ? $this->redis_calls[ $command ] + 1 : 1; + $this->cache_calls[ $command ] = isset( $this->cache_calls[ $command ] ) ? $this->cache_calls[ $command ] + 1 : 1; } public function add( $key, $data, $group = 'default', $expire = 0 ) { if ( isset( $this->cache[ $this->cache_key( $key, $group ) ] ) ) { @@ -184,7 +184,7 @@ Feature: Profile arbitary code execution } """ - When I run `wp profile eval 'wp_cache_set( "foo", "bar" ); wp_cache_get( "foo" ); wp_cache_get( "foo" );' --fields=cache_hits,cache_misses,redis_calls` + When I run `wp profile eval 'wp_cache_set( "foo", "bar" ); wp_cache_get( "foo" ); wp_cache_get( "foo" );' --fields=cache_hits,cache_misses,cache_calls` Then STDOUT should be a table containing rows: - | cache_hits | cache_misses | redis_calls | + | cache_hits | cache_misses | cache_calls | | 2 | 0 | 3 | diff --git a/src/Command.php b/src/Command.php index 213b05dd..eb11a18f 100644 --- a/src/Command.php +++ b/src/Command.php @@ -164,7 +164,7 @@ public function stage( $args, $assoc_args ) { 'cache_ratio', 'cache_hits', 'cache_misses', - 'redis_calls', + 'cache_calls', 'request_time', 'request_count', ); @@ -179,7 +179,7 @@ public function stage( $args, $assoc_args ) { 'cache_ratio', 'cache_hits', 'cache_misses', - 'redis_calls', + 'cache_calls', 'hook_time', 'hook_count', 'request_time', @@ -302,7 +302,7 @@ public function hook( $args, $assoc_args ) { 'cache_ratio', 'cache_hits', 'cache_misses', - 'redis_calls', + 'cache_calls', 'request_time', 'request_count', ); @@ -525,7 +525,7 @@ private static function profile_eval_ish( $assoc_args, $profile_callback, $order 'cache_ratio', 'cache_hits', 'cache_misses', - 'redis_calls', + 'cache_calls', 'request_time', 'request_count', ) diff --git a/src/Logger.php b/src/Logger.php index c231a51a..977a3b13 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -29,7 +29,7 @@ class Logger { /** @var string|null */ public $cache_ratio = null; /** @var int */ - public $redis_calls = 0; + public $cache_calls = 0; /** @var int */ public $hook_count = 0; /** @var float */ @@ -47,7 +47,7 @@ class Logger { /** @var int|null */ private $cache_miss_offset = null; /** @var int|null */ - private $redis_calls_offset = null; + private $cache_calls_offset = null; /** @var float|null */ private $hook_start_time = null; /** @var int */ @@ -123,8 +123,9 @@ public function start() { } $this->cache_hit_offset = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0; $this->cache_miss_offset = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0; - if ( isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ) { - $this->redis_calls_offset = (int) array_sum( $wp_object_cache->redis_calls ); + $total = self::get_object_cache_calls( $wp_object_cache ); + if ( ! is_null( $total ) ) { + $this->cache_calls_offset = $total; } } @@ -171,15 +172,18 @@ public function stop() { } } - if ( ! is_null( $this->redis_calls_offset ) && isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ) { - $this->redis_calls = (int) array_sum( $wp_object_cache->redis_calls ) - $this->redis_calls_offset; + if ( ! is_null( $this->cache_calls_offset ) && isset( $wp_object_cache ) ) { + $total = self::get_object_cache_calls( $wp_object_cache ); + if ( ! is_null( $total ) ) { + $this->cache_calls = $total - $this->cache_calls_offset; + } } $this->start_time = null; $this->query_offset = null; $this->cache_hit_offset = null; $this->cache_miss_offset = null; - $this->redis_calls_offset = null; + $this->cache_calls_offset = null; $key = array_search( $this, self::$active_loggers, true ); if ( false !== $key ) { @@ -239,4 +243,31 @@ public function stop_request_timer() { } $this->request_start_time = null; } + + /** + * Get the total number of object cache backend calls from the active cache. + * + * Supports multiple implementations: + * - WP Redis: $wp_object_cache->redis_calls (array of command => count) + * - Redis Object Cache: $wp_object_cache->cache_calls (array of command => count) + * - Object Cache Pro: $wp_object_cache->metrics()->storeReads + storeWrites + * + * @param object $wp_object_cache + * @return int|null Total call count, or null if not supported. + */ + public static function get_object_cache_calls( $wp_object_cache ) { + if ( isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ) { + return (int) array_sum( $wp_object_cache->redis_calls ); + } + if ( isset( $wp_object_cache->cache_calls ) && is_array( $wp_object_cache->cache_calls ) ) { + return (int) array_sum( $wp_object_cache->cache_calls ); + } + if ( method_exists( $wp_object_cache, 'metrics' ) ) { + $metrics = $wp_object_cache->metrics(); + if ( is_object( $metrics ) && isset( $metrics->storeReads ) && isset( $metrics->storeWrites ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + return (int) $metrics->storeReads + (int) $metrics->storeWrites; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + } + } + return null; + } } diff --git a/src/Profiler.php b/src/Profiler.php index 59b8e174..0ec74d4f 100644 --- a/src/Profiler.php +++ b/src/Profiler.php @@ -64,7 +64,7 @@ class Profiler { /** @var int|null */ private $tick_cache_miss_offset = null; /** @var int|null */ - private $tick_redis_calls_offset = null; + private $tick_cache_calls_offset = null; /** @var bool */ private $is_admin_request = false; @@ -405,7 +405,7 @@ public function handle_function_tick() { 'cache_hits' => 0, 'cache_misses' => 0, 'cache_ratio' => null, - 'redis_calls' => 0, + 'cache_calls' => 0, ); } @@ -443,10 +443,13 @@ public function handle_function_tick() { $logger_data['cache_ratio'] = round( $ratio, 2 ) . '%'; } - if ( ! is_null( $this->tick_redis_calls_offset ) && isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ) { - $current_redis_calls = isset( $logger_data['redis_calls'] ) && is_numeric( $logger_data['redis_calls'] ) ? $logger_data['redis_calls'] : 0; - $delta = (int) array_sum( $wp_object_cache->redis_calls ) - $this->tick_redis_calls_offset; - $logger_data['redis_calls'] = $delta + (int) $current_redis_calls; + if ( ! is_null( $this->tick_cache_calls_offset ) ) { + $total = Logger::get_object_cache_calls( $wp_object_cache ); + if ( ! is_null( $total ) ) { + $current_cache_calls = isset( $logger_data['cache_calls'] ) && is_numeric( $logger_data['cache_calls'] ) ? $logger_data['cache_calls'] : 0; + $delta = $total - $this->tick_cache_calls_offset; + $logger_data['cache_calls'] = $delta + (int) $current_cache_calls; + } } } $this->loggers[ $callback_hash ] = $logger_data; @@ -494,7 +497,7 @@ public function handle_function_tick() { $this->tick_query_offset = ! empty( $wpdb->queries ) ? count( $wpdb->queries ) : 0; $this->tick_cache_hit_offset = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0; $this->tick_cache_miss_offset = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0; - $this->tick_redis_calls_offset = isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ? (int) array_sum( $wp_object_cache->redis_calls ) : null; + $this->tick_cache_calls_offset = Logger::get_object_cache_calls( $wp_object_cache ); } /** From e13fa72dbc05bcc649bedfafc5eb888aabef2388 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 13:49:15 +0000 Subject: [PATCH 5/5] fix: guard get_object_cache_calls() against null wp_object_cache Agent-Logs-Url: https://github.com/wp-cli/profile-command/sessions/06c52567-fd1e-4fe3-a0d8-53c22f5c8e8b Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Logger.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Logger.php b/src/Logger.php index 977a3b13..2c5a4c35 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -252,10 +252,13 @@ public function stop_request_timer() { * - Redis Object Cache: $wp_object_cache->cache_calls (array of command => count) * - Object Cache Pro: $wp_object_cache->metrics()->storeReads + storeWrites * - * @param object $wp_object_cache + * @param object|null $wp_object_cache * @return int|null Total call count, or null if not supported. */ public static function get_object_cache_calls( $wp_object_cache ) { + if ( ! is_object( $wp_object_cache ) ) { + return null; + } if ( isset( $wp_object_cache->redis_calls ) && is_array( $wp_object_cache->redis_calls ) ) { return (int) array_sum( $wp_object_cache->redis_calls ); }