PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / trunk
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score vtrunk
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
← All changes | includes/dropins/memcache-object-cache.php +340 -119 2.2trunk View file →
@@ -1,16 +1,14 @@
1 1 <?php
2 +/**
3 + * Upstream: https://github.com/poweredcache/wp-memcached
4 + * Upstream version: 92d60df2dabac6a70b863a54d95602a0424fe391
5 + */
2 6 defined( 'ABSPATH' ) || exit;
3 7
4 8 // We need Memcache to continue
5 9 if ( class_exists( 'Memcache' ) ):
6 10
7 - /**
8 - * Upstream: 4.0
9 - *
10 - * @author: Ryan Boren, Denis de Bernardy, Matt Martz, Andy Skelton
11 - * @link https://wordpress.org/plugins/memcached
12 - */
13 11
14 12 // Users with setups where multiple installs share a common wp-config.php or $table_prefix
15 13 // can use this to guarantee uniqueness for the keys generated by this object cache
16 14 if ( ! defined( 'WP_CACHE_KEY_SALT' ) ) {
@@ -22,8 +20,14 @@
22 20
23 21 return $wp_object_cache->add( $key, $data, $group, $expire );
24 22 }
25 23
24 + function wp_cache_add_multiple( array $data, $group = '', $expire = 0 ) {
25 + global $wp_object_cache;
26 +
27 + return $wp_object_cache->add_multiple( $data, $group, $expire );
28 + }
29 +
26 30 function wp_cache_incr( $key, $n = 1, $group = '' ) {
27 31 global $wp_object_cache;
28 32
29 33 return $wp_object_cache->incr( $key, $n, $group );
@@ -46,8 +50,14 @@
46 50
47 51 return $wp_object_cache->delete( $key, $group );
48 52 }
49 53
54 + function wp_cache_delete_multiple( array $keys, $group = '' ) {
55 + global $wp_object_cache;
56 +
57 + return $wp_object_cache->delete_multiple( $keys, $group );
58 + }
59 +
50 60 function wp_cache_flush() {
51 61 global $wp_object_cache;
52 62
53 63 return $wp_object_cache->flush();
@@ -52,26 +62,38 @@
52 62
53 63 return $wp_object_cache->flush();
54 64 }
55 65
66 + function wp_cache_flush_runtime() {
67 + global $wp_object_cache;
68 +
69 + return $wp_object_cache->flush_runtime();
70 + }
71 +
56 72 function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
57 73 global $wp_object_cache;
58 74
59 - $value = apply_filters( 'pre_wp_cache_get', false, $key, $group, $force );
60 - if ( false !== $value ) {
61 - $found = true;
62 -
63 - return $value;
75 + if ( function_exists( 'apply_filters' ) ) {
76 + $value = apply_filters( 'pre_wp_cache_get', false, $key, $group, $force );
77 + if ( false !== $value ) {
78 + $found = true;
79 + return $value;
80 + }
64 81 }
65 82
66 83 return $wp_object_cache->get( $key, $group, $force, $found );
67 84 }
68 85
86 + function wp_cache_get_multiple( $keys, $group = '', $force = false ) {
87 + global $wp_object_cache;
88 +
89 + return $wp_object_cache->get_multiple( $keys, $group, $force );
90 + }
91 +
69 92 /**
70 93 * Retrieve multiple cache entries
71 94 *
72 95 * @param array $groups Array of arrays, of groups and keys to retrieve
73 - *
74 96 * @return mixed
75 97 */
76 98 function wp_cache_get_multi( $groups ) {
77 99 global $wp_object_cache;
@@ -93,9 +115,9 @@
93 115
94 116 function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
95 117 global $wp_object_cache;
96 118
97 - if ( defined( 'WP_INSTALLING' ) == false ) {
119 + if ( defined( 'WP_INSTALLING' ) === false ) {
98 120 return $wp_object_cache->set( $key, $data, $group, $expire );
99 121 } else {
100 122 return $wp_object_cache->delete( $key, $group );
101 123 }
@@ -100,8 +122,14 @@
100 122 return $wp_object_cache->delete( $key, $group );
101 123 }
102 124 }
103 125
126 + function wp_cache_set_multiple( array $data, $group = '', $expire = 0 ) {
127 + global $wp_object_cache;
128 +
129 + return $wp_object_cache->set_multiple( $data, $group, $expire );
130 + }
131 +
104 132 function wp_cache_switch_to_blog( $blog_id ) {
105 133 global $wp_object_cache;
106 134
107 135 return $wp_object_cache->switch_to_blog( $blog_id );
@@ -118,34 +146,70 @@
118 146
119 147 $wp_object_cache->add_non_persistent_groups( $groups );
120 148 }
121 149
150 + /**
151 + * Determines whether the object cache implementation supports a particular feature.
152 + *
153 + * @param string $feature Name of the feature to check for. Possible values include:
154 + * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple',
155 + * 'flush_runtime', 'flush_group'.
156 + * @return bool True if the feature is supported, false otherwise.
157 + */
158 + function wp_cache_supports( $feature ) {
159 + switch ( $feature ) {
160 + case 'get_multiple':
161 + case 'flush_runtime':
162 + return true;
163 +
164 + default:
165 + return false;
166 + }
167 + }
168 +
122 169 class WP_Object_Cache {
123 170 var $global_groups = array( 'WP_Object_Cache_global' );
124 171
125 172 var $no_mc_groups = array();
126 173
127 - var $cache = array();
128 - var $mc = array();
129 - var $default_mcs = array();
130 - var $stats = array();
131 - var $group_ops = array();
174 + var $cache = array();
175 + /** @var Memcache[] */
176 + var $mc = array();
177 + var $default_mcs = array();
178 + var $stats = array(
179 + 'get' => 0,
180 + 'get_local' => 0,
181 + 'get_multi' => 0,
182 + 'set' => 0,
183 + 'set_local' => 0,
184 + 'add' => 0,
185 + 'delete' => 0,
186 + 'delete_local' => 0,
187 + 'slow-ops' => 0,
188 + );
189 + var $group_ops = array();
190 + var $cache_hits = 0;
191 + var $cache_misses = 0;
192 + var $global_prefix = '';
193 + var $blog_prefix = '';
194 + var $key_salt = '';
132 195
133 - var $flush_group = 'WP_Object_Cache';
134 - var $global_flush_group = 'WP_Object_Cache_global';
135 - var $flush_key = "flush_number_v4";
136 - var $old_flush_key = "flush_number";
137 - var $flush_number = array();
196 + var $flush_group = 'WP_Object_Cache';
197 + var $global_flush_group = 'WP_Object_Cache_global';
198 + var $flush_key = "flush_number_v4";
199 + var $old_flush_key = "flush_number";
200 + var $flush_number = array();
138 201 var $global_flush_number = null;
139 202
140 - var $cache_enabled = true;
203 + var $cache_enabled = true;
141 204 var $default_expiration = 0;
142 - var $max_expiration = 2592000; // 30 days
205 + var $max_expiration = 2592000; // 30 days
143 206
144 207 var $stats_callback = null;
145 208
146 209 var $connection_errors = array();
147 210
211 + var $time_start = 0;
148 212 var $time_total = 0;
149 213 var $size_total = 0;
150 214 var $slow_op_microseconds = 0.005; // 5 ms
151 215
@@ -156,20 +220,26 @@
156 220 $data = clone $data;
157 221 }
158 222
159 223 if ( in_array( $group, $this->no_mc_groups ) ) {
160 - $this->cache[ $key ] = [
161 - 'value' => $data,
162 - 'found' => false,
163 - ];
224 + if ( ! isset( $this->cache[ $key ] ) ) {
225 + $this->cache[ $key ] = [
226 + 'value' => $data,
227 + 'found' => false,
228 + ];
164 229
165 - return true;
166 - } elseif ( isset( $this->cache[ $key ]['value'] ) && false !== $this->cache[ $key ]['value'] ) {
230 + return true;
231 + }
232 +
167 233 return false;
168 234 }
169 235
170 - $mc =& $this->get_mc( $group );
236 + if ( isset( $this->cache[ $key ][ 'value' ] ) && false !== $this->cache[ $key ][ 'value' ] ) {
237 + return false;
238 + }
171 239
240 + $mc = $this->get_mc( $group );
241 +
172 242 $expire = intval( $expire );
173 243 if ( 0 === $expire || $expire > $this->max_expiration ) {
174 244 $expire = $this->default_expiration;
175 245 }
@@ -175,9 +245,9 @@
175 245 }
176 246
177 247 $size = $this->get_data_size( $data );
178 248 $this->timer_start();
179 - $result = $mc->add( $key, $data, false, $expire );
249 + $result = $mc->add( $key, $data, false, $expire );
180 250 $elapsed = $this->timer_stop();
181 251
182 252 $comment = '';
183 253 if ( isset( $this->cache[ $key ] ) ) {
@@ -193,20 +263,30 @@
193 263 $this->cache[ $key ] = [
194 264 'value' => $data,
195 265 'found' => true,
196 266 ];
197 - } else if ( false === $result && true === isset( $this->cache[ $key ]['value'] ) && false === $this->cache[ $key ]['value'] ) {
267 + } else if ( false === $result && true === isset( $this->cache[$key][ 'value' ] ) && false === $this->cache[$key][ 'value' ] ) {
198 268 /*
199 269 * Here we unset local cache if remote add failed and local cache value is equal to `false` in order
200 270 * to update the local cache anytime we get a new information from remote server. This way, the next
201 271 * cache get will go to remote server and will fetch recent data.
202 272 */
203 - unset( $this->cache[ $key ] );
273 + unset( $this->cache[$key] );
204 274 }
205 275
206 276 return $result;
207 277 }
208 278
279 + public function add_multiple( array $data, $group = '', $expire = 0 ) {
280 + $values = array();
281 +
282 + foreach ( $data as $key => $value ) {
283 + $values[ $key ] = $this->add( $key, $value, $group, $expire );
284 + }
285 +
286 + return $values;
287 + }
288 +
209 289 function add_global_groups( $groups ) {
210 290 if ( ! is_array( $groups ) ) {
211 291 $groups = (array) $groups;
212 292 }
@@ -225,9 +305,9 @@
225 305 }
226 306
227 307 function incr( $id, $n = 1, $group = 'default' ) {
228 308 $key = $this->key( $id, $group );
229 - $mc =& $this->get_mc( $group );
309 + $mc = $this->get_mc( $group );
230 310
231 311 $incremented = $mc->increment( $key, $n );
232 312
233 313 $this->cache[ $key ] = [
@@ -234,22 +314,22 @@
234 314 'value' => $incremented,
235 315 'found' => false !== $incremented,
236 316 ];
237 317
238 - return $this->cache[ $key ]['value'];
318 + return $this->cache[ $key ][ 'value' ];
239 319 }
240 320
241 321 function decr( $id, $n = 1, $group = 'default' ) {
242 322 $key = $this->key( $id, $group );
243 - $mc =& $this->get_mc( $group );
323 + $mc = $this->get_mc( $group );
244 324
245 - $decremented = $mc->decrement( $key, $n );
325 + $decremented = $mc->decrement( $key, $n );
246 326 $this->cache[ $key ] = [
247 327 'value' => $decremented,
248 328 'found' => false !== $decremented,
249 329 ];
250 330
251 - return $this->cache[ $key ]['value'];
331 + return $this->cache[ $key ][ 'value' ];
252 332 }
253 333
254 334 function close() {
255 335 foreach ( $this->mc as $bucket => $mc ) {
@@ -265,12 +345,12 @@
265 345
266 346 return true;
267 347 }
268 348
269 - $mc =& $this->get_mc( $group );
349 + $mc = $this->get_mc( $group );
270 350
271 351 $this->timer_start();
272 - $result = $mc->delete( $key );
352 + $result = $mc->delete( $key );
273 353 $elapsed = $this->timer_stop();
274 354
275 355 $this->group_ops_stats( 'delete', $key, $group, null, $elapsed );
276 356
@@ -280,19 +360,29 @@
280 360
281 361 return $result;
282 362 }
283 363
364 + public function delete_multiple( array $keys, $group = '' ) {
365 + $values = array();
366 +
367 + foreach ( $keys as $key ) {
368 + $values[ $key ] = $this->delete( $key, $group );
369 + }
370 +
371 + return $values;
372 + }
373 +
284 374 // Gets number from all default servers, replicating if needed
285 375 function get_max_flush_number( $group ) {
286 376 $key = $this->key( $this->flush_key, $group );
287 377
288 378 $values = array();
289 - $size = 19; // size of microsecond timestamp serialized
379 + $size = 19; // size of microsecond timestamp serialized
290 380 foreach ( $this->default_mcs as $i => $mc ) {
291 381 $flags = false;
292 382 $this->timer_start();
293 383 $values[ $i ] = $mc->get( $key, $flags );
294 - $elapsed = $this->timer_stop();
384 + $elapsed = $this->timer_stop();
295 385
296 386 if ( empty( $values[ $i ] ) ) {
297 387 $this->group_ops_stats( 'get_flush_number', $key, $group, null, $elapsed, 'not_in_memcache' );
298 388 } else {
@@ -320,11 +410,11 @@
320 410 return $max;
321 411 }
322 412
323 413 function set_flush_number( $value, $group ) {
324 - $key = $this->key( $this->flush_key, $group );
414 + $key = $this->key( $this->flush_key, $group );
325 415 $expire = 0;
326 - $size = 19;
416 + $size = 19;
327 417 foreach ( $this->default_mcs as $i => $mc ) {
328 418 $this->timer_start();
329 419 $mc->set( $key, $value, false, $expire );
330 420 $elapsed = $this->timer_stop();
@@ -337,9 +427,9 @@
337 427 // What if there is no v4 flush number?
338 428 if ( empty( $flush_number ) ) {
339 429 // Look for the v3 flush number key.
340 430 $flush_number = intval( $this->get( $this->old_flush_key, $group ) );
341 - if ( ! empty( $flush_number ) ) {
431 + if ( !empty( $flush_number ) ) {
342 432 // Found v3 flush number. Upgrade to v4 with replication.
343 433 $this->set_flush_number( $flush_number, $group );
344 434 // Delete v3 key so we can't later restore it and find stale keys.
345 435 } else {
@@ -355,9 +445,8 @@
355 445 function get_global_flush_number() {
356 446 if ( ! isset( $this->global_flush_number ) ) {
357 447 $this->global_flush_number = $this->get_flush_number( $this->global_flush_group );
358 448 }
359 -
360 449 return $this->global_flush_number;
361 450 }
362 451
363 452 function get_blog_flush_number() {
@@ -363,10 +452,16 @@
363 452 function get_blog_flush_number() {
364 453 if ( ! isset( $this->flush_number[ $this->blog_prefix ] ) ) {
365 454 $this->flush_number[ $this->blog_prefix ] = $this->get_flush_number( $this->flush_group );
366 455 }
456 + return $this->flush_number[ $this->blog_prefix ];
457 + }
367 458
368 - return $this->flush_number[ $this->blog_prefix ];
459 + function flush_runtime() {
460 + $this->cache = array();
461 + $this->group_ops = array();
462 +
463 + return true;
369 464 }
370 465
371 466 function flush() {
372 467 // Do not use the memcached flush method. It acts on an
@@ -380,11 +475,13 @@
380 475 $flush_number = $this->new_flush_number();
381 476
382 477 $this->rotate_site_keys( $flush_number );
383 478
384 - if ( is_main_site() ) {
479 + if ( function_exists( 'is_main_site' ) && is_main_site() ) {
385 480 $this->rotate_global_keys( $flush_number );
386 481 }
482 +
483 + return true;
387 484 }
388 485
389 486 function rotate_site_keys( $flush_number = null ) {
390 487 if ( is_null( $flush_number ) ) {
@@ -410,19 +507,19 @@
410 507 return intval( microtime( true ) * 1e6 );
411 508 }
412 509
413 510 function get( $id, $group = 'default', $force = false, &$found = null ) {
414 - $key = $this->key( $id, $group );
415 - $mc =& $this->get_mc( $group );
511 + $key = $this->key( $id, $group );
512 + $mc = $this->get_mc( $group );
416 513 $found = true;
417 514
418 515 if ( isset( $this->cache[ $key ] ) && ( ! $force || in_array( $group, $this->no_mc_groups ) ) ) {
419 - if ( isset( $this->cache[ $key ]['value'] ) && is_object( $this->cache[ $key ]['value'] ) ) {
420 - $value = clone $this->cache[ $key ]['value'];
516 + if ( isset( $this->cache[ $key ][ 'value' ] ) && is_object( $this->cache[ $key ][ 'value' ] ) ) {
517 + $value = clone $this->cache[ $key ][ 'value' ];
421 518 } else {
422 - $value = $this->cache[ $key ]['value'];
519 + $value = $this->cache[ $key ][ 'value' ];
423 520 }
424 - $found = $this->cache[ $key ]['found'];
521 + $found = $this->cache[ $key ][ 'found' ];
425 522
426 523 $this->group_ops_stats( 'get_local', $key, $group, null, null, 'local' );
427 524 } else if ( in_array( $group, $this->no_mc_groups ) ) {
428 525 $this->cache[ $key ] = [
@@ -435,9 +532,9 @@
435 532 $this->group_ops_stats( 'get_local', $key, $group, null, null, 'not_in_local' );
436 533 } else {
437 534 $flags = false;
438 535 $this->timer_start();
439 - $value = $mc->get( $key, $flags );
536 + $value = $mc->get( $key, $flags );
440 537 $elapsed = $this->timer_stop();
441 538
442 539 // Value will be unchanged if the key doesn't exist.
443 540 if ( false === $flags ) {
@@ -442,8 +539,27 @@
442 539 // Value will be unchanged if the key doesn't exist.
443 540 if ( false === $flags ) {
444 541 $found = false;
445 542 $value = false;
543 + } elseif ( false === $value && ( $flags & 0xFF01 ) === 0x01 ) {
544 + /*
545 + * The lowest byte is used for flags.
546 + * 0x01 means the value is serialized (MMC_SERIALIZED).
547 + * The second lowest indicates data type: 0 is string, 1 is bool, 3 is long, 7 is double.
548 + * `null` is serialized into a string, thus $flags is 0x0001
549 + * Empty string will correspond to $flags = 0x0000 (not serialized).
550 + * For `false` or `true` $flags will be 0x0100
551 + *
552 + * See:
553 + * - https://github.com/websupport-sk/pecl-memcache/blob/2a5de3c5d9c0bd0acbcf7e6e0b7570f15f89f55b/php7/memcache_pool.h#L61-L76 - PHP 7.x
554 + * - https://github.com/websupport-sk/pecl-memcache/blob/ccf702b14b18fce18a1863e115a7b4c964df952e/src/memcache_pool.h#L57-L76 - PHP 8.x
555 + *
556 + * In PHP 8, they changed the way memcache_get() handles `null`:
557 + * https://github.com/websupport-sk/pecl-memcache/blob/ccf702b14b18fce18a1863e115a7b4c964df952e/src/memcache.c#L2175-L2177
558 + *
559 + * If the return value is `null`, it is silently converted to `false`. We can only rely upon $flags to find out whether `false` is real.
560 + */
561 + $value = null;
446 562 }
447 563
448 564 $this->cache[ $key ] = [
449 565 'value' => $value,
@@ -473,9 +589,9 @@
473 589 function get_multi( $groups ) {
474 590 /*
475 591 format: $get['group-name'] = array( 'key1', 'key2' );
476 592 */
477 - $return = array();
593 + $return = array();
478 594 $return_cache = array(
479 595 'value' => false,
480 596 'found' => false,
481 597 );
@@ -480,34 +596,34 @@
480 596 'found' => false,
481 597 );
482 598
483 599 foreach ( $groups as $group => $ids ) {
484 - $mc =& $this->get_mc( $group );
600 + $mc = $this->get_mc( $group );
485 601 $keys = array();
486 602 $this->timer_start();
487 603
488 604 foreach ( $ids as $id ) {
489 - $key = $this->key( $id, $group );
605 + $key = $this->key( $id, $group );
490 606 $keys[] = $key;
491 607
492 608 if ( isset( $this->cache[ $key ] ) ) {
493 - if ( is_object( $this->cache[ $key ]['value'] ) ) {
494 - $return[ $key ] = clone $this->cache[ $key ]['value'];
609 + if ( is_object( $this->cache[ $key ][ 'value'] ) ) {
610 + $return[ $key ] = clone $this->cache[ $key ][ 'value'];
495 611 $return_cache[ $key ] = [
496 - 'value' => clone $this->cache[ $key ]['value'],
497 - 'found' => $this->cache[ $key ]['found'],
612 + 'value' => clone $this->cache[ $key ][ 'value'],
613 + 'found' => $this->cache[ $key ][ 'found'],
498 614 ];
499 615 } else {
500 - $return[ $key ] = $this->cache[ $key ]['value'];
616 + $return[ $key ] = $this->cache[ $key ][ 'value'];
501 617 $return_cache[ $key ] = [
502 - 'value' => $this->cache[ $key ]['value'],
503 - 'found' => $this->cache[ $key ]['found'],
618 + 'value' => $this->cache[ $key ][ 'value' ],
619 + 'found' => $this->cache[ $key ][ 'found' ],
504 620 ];
505 621 }
506 622
507 623 continue;
508 624 } else if ( in_array( $group, $this->no_mc_groups ) ) {
509 - $return[ $key ] = false;
625 + $return[ $key ] = false;
510 626 $return_cache[ $key ] = [
511 627 'value' => false,
512 628 'found' => false,
513 629 ];
@@ -513,10 +629,10 @@
513 629 ];
514 630
515 631 continue;
516 632 } else {
517 - $fresh_get = $mc->get( $key );
518 - $return[ $key ] = $fresh_get;
633 + $fresh_get = $mc->get( $key );
634 + $return[ $key ] = $fresh_get;
519 635 $return_cache[ $key ] = [
520 636 'value' => $fresh_get,
521 637 'found' => false !== $fresh_get,
522 638 ];
@@ -531,8 +647,59 @@
531 647
532 648 return $return;
533 649 }
534 650
651 + public function get_multiple( $keys, $group = 'default', $force = false ) {
652 + $mc = $this->get_mc( $group );
653 +
654 + $no_mc = in_array( $group, $this->no_mc_groups, true );
655 +
656 + $uncached_keys = array();
657 + $return = array();
658 + $return_cache = array();
659 +
660 + foreach ( $keys as $id ) {
661 + $key = $this->key( $id, $group );
662 +
663 + if ( isset( $this->cache[ $key ] ) && ( ! $force || $no_mc ) ) {
664 + $value = $this->cache[ $key ]['found'] ? $this->cache[ $key ]['value'] : false;
665 + $return[ $id ] = is_object( $value ) ? clone $value : $value;
666 + } else if ( $no_mc ) {
667 + $return[ $id ] = false;
668 + $return_cache[ $key ] = [
669 + 'value' => false,
670 + 'found' => false,
671 + ];
672 + } else {
673 + $uncached_keys[ $id ] = $key;
674 + }
675 + }
676 +
677 + if ( $uncached_keys ) {
678 + $this->timer_start();
679 + $uncached_keys_list = array_values( $uncached_keys );
680 + $values = $mc->get( $uncached_keys_list );
681 + $elapsed = $this->timer_stop();
682 +
683 + $this->group_ops_stats( 'get_multiple', $uncached_keys_list, $group, null, $elapsed );
684 +
685 + foreach ( $uncached_keys as $id => $key ) {
686 + $found = array_key_exists( $key, $values );
687 + $value = $found ? $values[ $key ] : false;
688 +
689 + $return[ $id ] = $value;
690 + $return_cache[ $key ] = [
691 + 'value' => $value,
692 + 'found' => $found,
693 + ];
694 + }
695 + }
696 +
697 + $this->cache = array_merge( $this->cache, $return_cache );
698 +
699 + return $return;
700 + }
701 +
535 702 function flush_prefix( $group ) {
536 703 if ( $group === $this->flush_group || $group === $this->global_flush_group ) {
537 704 // Never flush the flush numbers.
538 705 $number = '_';
@@ -540,9 +707,8 @@
540 707 $number = $this->get_global_flush_number();
541 708 } else {
542 709 $number = $this->get_blog_flush_number();
543 710 }
544 -
545 711 return $number . ':';
546 712 }
547 713
548 714 function key( $key, $group ) {
@@ -563,14 +729,14 @@
563 729 return preg_replace( '/\s+/', '', "$prefix:$group:$key" );
564 730 }
565 731
566 732 function replace( $id, $data, $group = 'default', $expire = 0 ) {
567 - $key = $this->key( $id, $group );
733 + $key = $this->key( $id, $group );
568 734 $expire = intval( $expire );
569 735 if ( 0 === $expire || $expire > $this->max_expiration ) {
570 736 $expire = $this->default_expiration;
571 737 }
572 - $mc =& $this->get_mc( $group );
738 + $mc = $this->get_mc( $group );
573 739
574 740 if ( is_object( $data ) ) {
575 741 $data = clone $data;
576 742 }
@@ -576,9 +742,9 @@
576 742 }
577 743
578 744 $size = $this->get_data_size( $data );
579 745 $this->timer_start();
580 - $result = $mc->replace( $key, $data, false, $expire );
746 + $result = $mc->replace( $key, $data, false, $expire );
581 747 $elapsed = $this->timer_stop();
582 748 $this->group_ops_stats( 'replace', $key, $group, $size, $elapsed );
583 749
584 750 if ( false !== $result ) {
@@ -593,9 +759,9 @@
593 759
594 760 function set( $id, $data, $group = 'default', $expire = 0 ) {
595 761 $key = $this->key( $id, $group );
596 762
597 - if ( isset( $this->cache[ $key ] ) && ( 'checkthedatabaseplease' === $this->cache[ $key ]['value'] ) ) {
763 + if ( isset( $this->cache[ $key ] ) && ( 'checkthedatabaseplease' === $this->cache[ $key ][ 'value' ] ) ) {
598 764 return false;
599 765 }
600 766
601 767 if ( is_object( $data ) ) {
@@ -617,22 +783,32 @@
617 783 if ( 0 === $expire || $expire > $this->max_expiration ) {
618 784 $expire = $this->default_expiration;
619 785 }
620 786
621 - $mc =& $this->get_mc( $group );
787 + $mc = $this->get_mc( $group );
622 788
623 789 $size = $this->get_data_size( $data );
624 790 $this->timer_start();
625 - $result = $mc->set( $key, $data, false, $expire );
791 + $result = $mc->set( $key, $data, false, $expire );
626 792 $elapsed = $this->timer_stop();
627 793 $this->group_ops_stats( 'set', $key, $group, $size, $elapsed );
628 794
629 795 // Update the found cache value with the result of the set in memcache.
630 - $this->cache[ $key ]['found'] = $result;
796 + $this->cache[ $key ][ 'found' ] = $result;
631 797
632 798 return $result;
633 799 }
634 800
801 + public function set_multiple( array $data, $group = '', $expire = 0 ) {
802 + $values = array();
803 +
804 + foreach ( $data as $key => $value ) {
805 + $values[ $key ] = $this->set( $key, $value, $group, $expire );
806 + }
807 +
808 + return $values;
809 + }
810 +
635 811 function switch_to_blog( $blog_id ) {
636 812 global $table_prefix;
637 813
638 814 $blog_id = (int) $blog_id;
@@ -641,17 +817,18 @@
641 817 }
642 818
643 819 function colorize_debug_line( $line, $trailing_html = '' ) {
644 820 $colors = array(
645 - 'get' => 'green',
646 - 'get_local' => 'lightgreen',
647 - 'get_multi' => 'fuchsia',
648 - 'set' => 'purple',
649 - 'set_local' => 'orchid',
650 - 'add' => 'blue',
651 - 'delete' => 'red',
821 + 'get' => 'green',
822 + 'get_local' => 'lightgreen',
823 + 'get_multi' => 'fuchsia',
824 + 'get_multiple' => 'navy',
825 + 'set' => 'purple',
826 + 'set_local' => 'orchid',
827 + 'add' => 'blue',
828 + 'delete' => 'red',
652 829 'delete_local' => 'tomato',
653 - 'slow-ops' => 'crimson',
830 + 'slow-ops' => 'crimson',
654 831 );
655 832
656 833 $cmd = substr( $line, 0, strpos( $line, ' ' ) );
657 834
@@ -696,8 +873,61 @@
696 873 </script>
697 874 ";
698 875 }
699 876
877 + /**
878 + * Returns the collected raw stats.
879 + *
880 + * @return array $stats
881 + */
882 + function get_stats() {
883 + $stats = [];
884 + $stats['totals'] = [
885 + 'query_time' => $this->time_total,
886 + 'size' => $this->size_total,
887 + ];
888 + $stats['operation_counts'] = $this->stats;
889 + $stats['operations'] = [];
890 + $stats['groups'] = [];
891 + $stats['slow-ops'] = [];
892 + $stats['slow-ops-groups'] = [];
893 + foreach ( $this->group_ops as $cache_group => $dataset ) {
894 + if ( empty( $cache_group ) ) {
895 + $cache_group = 'default';
896 + }
897 +
898 + foreach ( $dataset as $data ) {
899 + $operation = $data[0];
900 + $op = [
901 + 'key' => $data[1],
902 + 'size' => $data[2],
903 + 'time' => $data[3],
904 + 'group' => $cache_group,
905 + 'result' => $data[4],
906 + ];
907 +
908 + if ( $cache_group === 'slow-ops' ) {
909 + $key = 'slow-ops';
910 + $groups_key = 'slow-ops-groups';
911 + $op['group'] = $data[5];
912 + $op['backtrace'] = $data[6];
913 + } else {
914 + $key = 'operations';
915 + $groups_key = 'groups';
916 + }
917 +
918 + $stats[ $key ][ $operation ][] = $op;
919 +
920 + if ( ! in_array( $op['group'], $stats[ $groups_key ] ) ) {
921 + $stats[ $groups_key ][] = $op['group'];
922 + }
923 + }
924 + }
925 +
926 + return $stats;
927 + }
928 +
929 +
700 930 function stats() {
701 931 $this->js_toggle();
702 932
703 933 echo '<h2><span>Total memcache query time:</span>' . number_format_i18n( sprintf( '%0.1f', $this->time_total * 1000 ), 1 ) . ' ms</h2>';
@@ -722,15 +952,15 @@
722 952 $active_group = $groups[0];
723 953 // Always show `slow-ops` first
724 954 if ( in_array( 'slow-ops', $groups ) ) {
725 955 $slow_ops_key = array_search( 'slow-ops', $groups );
726 - $slow_ops = $groups[ $slow_ops_key ];
956 + $slow_ops = $groups[ $slow_ops_key ];
727 957 unset( $groups[ $slow_ops_key ] );
728 958 array_unshift( $groups, $slow_ops );
729 959 $active_group = 'slow-ops';
730 960 }
731 961
732 - $total_ops = 0;
962 + $total_ops = 0;
733 963 $group_titles = array();
734 964 foreach ( $groups as $group ) {
735 965 $group_name = $group;
736 966 if ( empty( $group_name ) ) {
@@ -735,17 +965,13 @@
735 965 $group_name = $group;
736 966 if ( empty( $group_name ) ) {
737 967 $group_name = 'default';
738 968 }
739 - $group_ops = count( $this->group_ops[ $group ] );
740 - $group_size = size_format( array_sum( array_map( function ( $op ) {
741 - return $op[2];
742 - }, $this->group_ops[ $group ] ) ), 2 );
743 - $group_time = number_format_i18n( sprintf( '%0.1f', array_sum( array_map( function ( $op ) {
744 - return $op[3];
745 - }, $this->group_ops[ $group ] ) ) * 1000 ), 1 );
746 - $total_ops += $group_ops;
747 - $group_title = "{$group_name} [$group_ops][$group_size][{$group_time} ms]";
969 + $group_ops = count( $this->group_ops[ $group ] );
970 + $group_size = size_format( array_sum( array_map( function ( $op ) { return $op[2]; }, $this->group_ops[ $group ] ) ), 2 );
971 + $group_time = number_format_i18n( sprintf( '%0.1f', array_sum( array_map( function ( $op ) { return $op[3]; }, $this->group_ops[ $group ] ) ) * 1000 ), 1 );
972 + $total_ops += $group_ops;
973 + $group_title = "{$group_name} [$group_ops][$group_size][{$group_time} ms]";
748 974 $group_titles[ $group ] = $group_title;
749 975 echo "\t<li><a href='#' onclick='memcachedToggleVisibility( \"object-cache-stats-menu-target-" . esc_js( $group_name ) . "\", \"object-cache-stats-menu-target-\" );'>" . esc_html( $group_title ) . "</a></li>\n";
750 976 }
751 977 echo "</ul>\n";
@@ -776,9 +1002,9 @@
776 1002 $line = "{$arr[0]} ";
777 1003
778 1004 // key
779 1005 $json_encoded_key = json_encode( $arr[1] );
780 - $line .= $json_encoded_key . " ";
1006 + $line .= $json_encoded_key . " ";
781 1007
782 1008 // comment
783 1009 if ( ! empty( $arr[4] ) ) {
784 1010 $line .= "{$arr[4]} ";
@@ -797,16 +1023,20 @@
797 1023 // backtrace
798 1024 $bt_link = '';
799 1025 if ( isset( $arr[6] ) ) {
800 1026 $key_hash = md5( $index . $json_encoded_key );
801 - $bt_link = " <small><a href='#' onclick='memcachedToggleVisibility( \"object-cache-stats-debug-$key_hash\" );'>Toggle Backtrace</a></small>";
802 - $bt_link .= "<pre id='object-cache-stats-debug-$key_hash' style='display:none'>" . esc_html( $arr[6] ) . "</pre>";
1027 + $bt_link = " <small><a href='#' onclick='memcachedToggleVisibility( \"object-cache-stats-debug-$key_hash\" );'>Toggle Backtrace</a></small>";
1028 + $bt_link .= "<pre id='object-cache-stats-debug-$key_hash' style='display:none'>" . esc_html( $arr[6] ) . "</pre>";
803 1029 }
804 1030
805 1031 return $this->colorize_debug_line( $line, $bt_link );
806 1032 }
807 1033
808 - function &get_mc( $group ) {
1034 + /**
1035 + * @param int|string $group
1036 + * @return Memcache
1037 + */
1038 + function get_mc( $group ) {
809 1039 if ( isset( $this->mc[ $group ] ) ) {
810 1040 return $this->mc[ $group ];
811 1041 }
812 1042
@@ -828,20 +1058,8 @@
828 1058 }
829 1059 }
830 1060
831 1061 function __construct() {
832 - $this->stats = array(
833 - 'get' => 0,
834 - 'get_local' => 0,
835 - 'get_multi' => 0,
836 - 'set' => 0,
837 - 'set_local' => 0,
838 - 'add' => 0,
839 - 'delete' => 0,
840 - 'delete_local' => 0,
841 - 'slow-ops' => 0,
842 - );
843 -
844 1062 global $memcached_servers;
845 1063
846 1064 if ( isset( $memcached_servers ) ) {
847 1065 $buckets = $memcached_servers;
@@ -857,17 +1075,18 @@
857 1075
858 1076 foreach ( $buckets as $bucket => $servers ) {
859 1077 $this->mc[ $bucket ] = new Memcache();
860 1078
861 - foreach ( $servers as $i => $server ) {
1079 + foreach ( $servers as $i => $server ) {
862 1080 if ( 'unix://' == substr( $server, 0, 7 ) ) {
863 1081 $node = $server;
864 1082 $port = 0;
865 1083 } else {
866 - list ( $node, $port ) = explode( ':', $server );
867 -
868 - if ( ! $port ) {
1084 + if ( false === strpos( $server, ':' ) ) {
1085 + $node = $server;
869 1086 $port = ini_get( 'memcache.default_port' );
1087 + } else {
1088 + list ( $node, $port ) = explode( ':', $server, 2 );
870 1089 }
871 1090
872 1091 $port = intval( $port );
873 1092
@@ -922,8 +1141,9 @@
922 1141 $this->size_total += $size;
923 1142
924 1143 $keys = $this->strip_memcached_keys( $keys );
925 1144
1145 + // @codeCoverageIgnoreStart
926 1146 if ( $time > $this->slow_op_microseconds && 'get_multi' !== $op ) {
927 1147 $this->increment_stat( 'slow-ops' );
928 1148 $backtrace = null;
929 1149 if ( function_exists( 'wp_debug_backtrace_summary' ) ) {
@@ -930,8 +1150,9 @@
930 1150 $backtrace = wp_debug_backtrace_summary();
931 1151 }
932 1152 $this->group_ops['slow-ops'][] = array( $op, $keys, $size, $time, $comment, $group, $backtrace );
933 1153 }
1154 + // @codeCoverageIgnoreEnd
934 1155
935 1156 $this->group_ops[ $group ][] = array( $op, $keys, $size, $time, $comment );
936 1157 }
937 1158
@@ -951,9 +1172,9 @@
951 1172 if ( ! empty( $this->key_salt ) ) {
952 1173 $offset = strpos( $value, ':' ) + 1;
953 1174 }
954 1175
955 - $start = strpos( $value, ':', $offset );
1176 + $start = strpos( $value, ':', $offset );
956 1177 $keys[ $key ] = substr( $value, $start + 1 );
957 1178 }
958 1179
959 1180 if ( 1 === count( $keys ) ) {
@@ -969,9 +1190,9 @@
969 1190 return true;
970 1191 }
971 1192
972 1193 function timer_stop() {
973 - $time_total = microtime( true ) - $this->time_start;
1194 + $time_total = microtime( true ) - $this->time_start;
974 1195 $this->time_total += $time_total;
975 1196
976 1197 return $time_total;
977 1198 }