PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.1.2
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.1.2
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
powered-cache / includes / dropins / memcache-object-cache.php

memcache-object-cache.php in Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score 2.1.2, at includes/dropins/memcache-object-cache.php

991 lines 26.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 defined( 'ABSPATH' ) || exit;
3
4 // We need Memcache to continue
5 if ( class_exists( 'Memcache' ) ):
6
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
14 // Users with setups where multiple installs share a common wp-config.php or $table_prefix
15 // can use this to guarantee uniqueness for the keys generated by this object cache
16 if ( ! defined( 'WP_CACHE_KEY_SALT' ) ) {
17 define( 'WP_CACHE_KEY_SALT', '' );
18 }
19
20 function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
21 global $wp_object_cache;
22
23 return $wp_object_cache->add( $key, $data, $group, $expire );
24 }
25
26 function wp_cache_incr( $key, $n = 1, $group = '' ) {
27 global $wp_object_cache;
28
29 return $wp_object_cache->incr( $key, $n, $group );
30 }
31
32 function wp_cache_decr( $key, $n = 1, $group = '' ) {
33 global $wp_object_cache;
34
35 return $wp_object_cache->decr( $key, $n, $group );
36 }
37
38 function wp_cache_close() {
39 global $wp_object_cache;
40
41 return $wp_object_cache->close();
42 }
43
44 function wp_cache_delete( $key, $group = '' ) {
45 global $wp_object_cache;
46
47 return $wp_object_cache->delete( $key, $group );
48 }
49
50 function wp_cache_flush() {
51 global $wp_object_cache;
52
53 return $wp_object_cache->flush();
54 }
55
56 function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
57 global $wp_object_cache;
58
59 $value = apply_filters( 'pre_wp_cache_get', false, $key, $group, $force );
60 if ( false !== $value ) {
61 $found = true;
62
63 return $value;
64 }
65
66 return $wp_object_cache->get( $key, $group, $force, $found );
67 }
68
69 /**
70 * Retrieve multiple cache entries
71 *
72 * @param array $groups Array of arrays, of groups and keys to retrieve
73 *
74 * @return mixed
75 */
76 function wp_cache_get_multi( $groups ) {
77 global $wp_object_cache;
78
79 return $wp_object_cache->get_multi( $groups );
80 }
81
82 function wp_cache_init() {
83 global $wp_object_cache;
84
85 $wp_object_cache = new WP_Object_Cache();
86 }
87
88 function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) {
89 global $wp_object_cache;
90
91 return $wp_object_cache->replace( $key, $data, $group, $expire );
92 }
93
94 function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
95 global $wp_object_cache;
96
97 if ( defined( 'WP_INSTALLING' ) == false ) {
98 return $wp_object_cache->set( $key, $data, $group, $expire );
99 } else {
100 return $wp_object_cache->delete( $key, $group );
101 }
102 }
103
104 function wp_cache_switch_to_blog( $blog_id ) {
105 global $wp_object_cache;
106
107 return $wp_object_cache->switch_to_blog( $blog_id );
108 }
109
110 function wp_cache_add_global_groups( $groups ) {
111 global $wp_object_cache;
112
113 $wp_object_cache->add_global_groups( $groups );
114 }
115
116 function wp_cache_add_non_persistent_groups( $groups ) {
117 global $wp_object_cache;
118
119 $wp_object_cache->add_non_persistent_groups( $groups );
120 }
121
122 class WP_Object_Cache {
123 var $global_groups = array( 'WP_Object_Cache_global' );
124
125 var $no_mc_groups = array();
126
127 var $cache = array();
128 var $mc = array();
129 var $default_mcs = array();
130 var $stats = array();
131 var $group_ops = array();
132
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();
138 var $global_flush_number = null;
139
140 var $cache_enabled = true;
141 var $default_expiration = 0;
142 var $max_expiration = 2592000; // 30 days
143
144 var $stats_callback = null;
145
146 var $connection_errors = array();
147
148 var $time_total = 0;
149 var $size_total = 0;
150 var $slow_op_microseconds = 0.005; // 5 ms
151
152 function add( $id, $data, $group = 'default', $expire = 0 ) {
153 $key = $this->key( $id, $group );
154
155 if ( is_object( $data ) ) {
156 $data = clone $data;
157 }
158
159 if ( in_array( $group, $this->no_mc_groups ) ) {
160 $this->cache[ $key ] = [
161 'value' => $data,
162 'found' => false,
163 ];
164
165 return true;
166 } elseif ( isset( $this->cache[ $key ]['value'] ) && false !== $this->cache[ $key ]['value'] ) {
167 return false;
168 }
169
170 $mc =& $this->get_mc( $group );
171
172 $expire = intval( $expire );
173 if ( 0 === $expire || $expire > $this->max_expiration ) {
174 $expire = $this->default_expiration;
175 }
176
177 $size = $this->get_data_size( $data );
178 $this->timer_start();
179 $result = $mc->add( $key, $data, false, $expire );
180 $elapsed = $this->timer_stop();
181
182 $comment = '';
183 if ( isset( $this->cache[ $key ] ) ) {
184 $comment .= ' [lc already]';
185 }
186 if ( false === $result ) {
187 $comment .= ' [mc already]';
188 }
189
190 $this->group_ops_stats( 'add', $key, $group, $size, $elapsed, $comment );
191
192 if ( false !== $result ) {
193 $this->cache[ $key ] = [
194 'value' => $data,
195 'found' => true,
196 ];
197 } else if ( false === $result && true === isset( $this->cache[ $key ]['value'] ) && false === $this->cache[ $key ]['value'] ) {
198 /*
199 * Here we unset local cache if remote add failed and local cache value is equal to `false` in order
200 * to update the local cache anytime we get a new information from remote server. This way, the next
201 * cache get will go to remote server and will fetch recent data.
202 */
203 unset( $this->cache[ $key ] );
204 }
205
206 return $result;
207 }
208
209 function add_global_groups( $groups ) {
210 if ( ! is_array( $groups ) ) {
211 $groups = (array) $groups;
212 }
213
214 $this->global_groups = array_merge( $this->global_groups, $groups );
215 $this->global_groups = array_unique( $this->global_groups );
216 }
217
218 function add_non_persistent_groups( $groups ) {
219 if ( ! is_array( $groups ) ) {
220 $groups = (array) $groups;
221 }
222
223 $this->no_mc_groups = array_merge( $this->no_mc_groups, $groups );
224 $this->no_mc_groups = array_unique( $this->no_mc_groups );
225 }
226
227 function incr( $id, $n = 1, $group = 'default' ) {
228 $key = $this->key( $id, $group );
229 $mc =& $this->get_mc( $group );
230
231 $incremented = $mc->increment( $key, $n );
232
233 $this->cache[ $key ] = [
234 'value' => $incremented,
235 'found' => false !== $incremented,
236 ];
237
238 return $this->cache[ $key ]['value'];
239 }
240
241 function decr( $id, $n = 1, $group = 'default' ) {
242 $key = $this->key( $id, $group );
243 $mc =& $this->get_mc( $group );
244
245 $decremented = $mc->decrement( $key, $n );
246 $this->cache[ $key ] = [
247 'value' => $decremented,
248 'found' => false !== $decremented,
249 ];
250
251 return $this->cache[ $key ]['value'];
252 }
253
254 function close() {
255 foreach ( $this->mc as $bucket => $mc ) {
256 $mc->close();
257 }
258 }
259
260 function delete( $id, $group = 'default' ) {
261 $key = $this->key( $id, $group );
262
263 if ( in_array( $group, $this->no_mc_groups ) ) {
264 unset( $this->cache[ $key ] );
265
266 return true;
267 }
268
269 $mc =& $this->get_mc( $group );
270
271 $this->timer_start();
272 $result = $mc->delete( $key );
273 $elapsed = $this->timer_stop();
274
275 $this->group_ops_stats( 'delete', $key, $group, null, $elapsed );
276
277 if ( false !== $result ) {
278 unset( $this->cache[ $key ] );
279 }
280
281 return $result;
282 }
283
284 // Gets number from all default servers, replicating if needed
285 function get_max_flush_number( $group ) {
286 $key = $this->key( $this->flush_key, $group );
287
288 $values = array();
289 $size = 19; // size of microsecond timestamp serialized
290 foreach ( $this->default_mcs as $i => $mc ) {
291 $flags = false;
292 $this->timer_start();
293 $values[ $i ] = $mc->get( $key, $flags );
294 $elapsed = $this->timer_stop();
295
296 if ( empty( $values[ $i ] ) ) {
297 $this->group_ops_stats( 'get_flush_number', $key, $group, null, $elapsed, 'not_in_memcache' );
298 } else {
299 $this->group_ops_stats( 'get_flush_number', $key, $group, $size, $elapsed, 'memcache' );
300 }
301 }
302
303 $max = max( $values );
304
305 if ( ! $max > 0 ) {
306 return false;
307 }
308
309 // Replicate to servers not having the max.
310 $expire = 0;
311 foreach ( $this->default_mcs as $i => $mc ) {
312 if ( $values[ $i ] < $max ) {
313 $this->timer_start();
314 $mc->set( $key, $max, false, $expire );
315 $elapsed = $this->timer_stop();
316 $this->group_ops_stats( 'set_flush_number', $key, $group, $size, $elapsed, 'replication_repair' );
317 }
318 }
319
320 return $max;
321 }
322
323 function set_flush_number( $value, $group ) {
324 $key = $this->key( $this->flush_key, $group );
325 $expire = 0;
326 $size = 19;
327 foreach ( $this->default_mcs as $i => $mc ) {
328 $this->timer_start();
329 $mc->set( $key, $value, false, $expire );
330 $elapsed = $this->timer_stop();
331 $this->group_ops_stats( 'set_flush_number', $key, $group, $size, $elapsed, 'replication' );
332 }
333 }
334
335 function get_flush_number( $group ) {
336 $flush_number = $this->get_max_flush_number( $group );
337 // What if there is no v4 flush number?
338 if ( empty( $flush_number ) ) {
339 // Look for the v3 flush number key.
340 $flush_number = intval( $this->get( $this->old_flush_key, $group ) );
341 if ( ! empty( $flush_number ) ) {
342 // Found v3 flush number. Upgrade to v4 with replication.
343 $this->set_flush_number( $flush_number, $group );
344 // Delete v3 key so we can't later restore it and find stale keys.
345 } else {
346 // No flush number found anywhere. Make a new one. This flushes the cache.
347 $flush_number = $this->new_flush_number();
348 $this->set_flush_number( $flush_number, $group );
349 }
350 }
351
352 return $flush_number;
353 }
354
355 function get_global_flush_number() {
356 if ( ! isset( $this->global_flush_number ) ) {
357 $this->global_flush_number = $this->get_flush_number( $this->global_flush_group );
358 }
359
360 return $this->global_flush_number;
361 }
362
363 function get_blog_flush_number() {
364 if ( ! isset( $this->flush_number[ $this->blog_prefix ] ) ) {
365 $this->flush_number[ $this->blog_prefix ] = $this->get_flush_number( $this->flush_group );
366 }
367
368 return $this->flush_number[ $this->blog_prefix ];
369 }
370
371 function flush() {
372 // Do not use the memcached flush method. It acts on an
373 // entire memcached server, affecting all sites.
374 // Flush is also unusable in some setups, e.g. twemproxy.
375 // Instead, rotate the key prefix for the current site.
376 // Global keys are rotated when flushing on any network's
377 // main site.
378 $this->cache = array();
379
380 $flush_number = $this->new_flush_number();
381
382 $this->rotate_site_keys( $flush_number );
383
384 if ( is_main_site() ) {
385 $this->rotate_global_keys( $flush_number );
386 }
387 }
388
389 function rotate_site_keys( $flush_number = null ) {
390 if ( is_null( $flush_number ) ) {
391 $flush_number = $this->new_flush_number();
392 }
393
394 $this->set_flush_number( $flush_number, $this->flush_group );
395
396 $this->flush_number[ $this->blog_prefix ] = $flush_number;
397 }
398
399 function rotate_global_keys( $flush_number = null ) {
400 if ( is_null( $flush_number ) ) {
401 $flush_number = $this->new_flush_number();
402 }
403
404 $this->set_flush_number( $flush_number, $this->global_flush_group );
405
406 $this->global_flush_number = $flush_number;
407 }
408
409 function new_flush_number() {
410 return intval( microtime( true ) * 1e6 );
411 }
412
413 function get( $id, $group = 'default', $force = false, &$found = null ) {
414 $key = $this->key( $id, $group );
415 $mc =& $this->get_mc( $group );
416 $found = true;
417
418 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'];
421 } else {
422 $value = $this->cache[ $key ]['value'];
423 }
424 $found = $this->cache[ $key ]['found'];
425
426 $this->group_ops_stats( 'get_local', $key, $group, null, null, 'local' );
427 } else if ( in_array( $group, $this->no_mc_groups ) ) {
428 $this->cache[ $key ] = [
429 'value' => $value = false,
430 'found' => false,
431 ];
432
433 $found = false;
434
435 $this->group_ops_stats( 'get_local', $key, $group, null, null, 'not_in_local' );
436 } else {
437 $flags = false;
438 $this->timer_start();
439 $value = $mc->get( $key, $flags );
440 $elapsed = $this->timer_stop();
441
442 // Value will be unchanged if the key doesn't exist.
443 if ( false === $flags ) {
444 $found = false;
445 $value = false;
446 }
447
448 $this->cache[ $key ] = [
449 'value' => $value,
450 'found' => $found,
451 ];
452
453 if ( is_null( $value ) || $value === false ) {
454 $this->group_ops_stats( 'get', $key, $group, null, $elapsed, 'not_in_memcache' );
455 } else if ( 'checkthedatabaseplease' === $value ) {
456 $this->group_ops_stats( 'get', $key, $group, null, $elapsed, 'checkthedatabaseplease' );
457 } else {
458 $size = $this->get_data_size( $value );
459 $this->group_ops_stats( 'get', $key, $group, $size, $elapsed, 'memcache' );
460 }
461 }
462
463 if ( 'checkthedatabaseplease' === $value ) {
464 unset( $this->cache[ $key ] );
465
466 $found = false;
467 $value = false;
468 }
469
470 return $value;
471 }
472
473 function get_multi( $groups ) {
474 /*
475 format: $get['group-name'] = array( 'key1', 'key2' );
476 */
477 $return = array();
478 $return_cache = array(
479 'value' => false,
480 'found' => false,
481 );
482
483 foreach ( $groups as $group => $ids ) {
484 $mc =& $this->get_mc( $group );
485 $keys = array();
486 $this->timer_start();
487
488 foreach ( $ids as $id ) {
489 $key = $this->key( $id, $group );
490 $keys[] = $key;
491
492 if ( isset( $this->cache[ $key ] ) ) {
493 if ( is_object( $this->cache[ $key ]['value'] ) ) {
494 $return[ $key ] = clone $this->cache[ $key ]['value'];
495 $return_cache[ $key ] = [
496 'value' => clone $this->cache[ $key ]['value'],
497 'found' => $this->cache[ $key ]['found'],
498 ];
499 } else {
500 $return[ $key ] = $this->cache[ $key ]['value'];
501 $return_cache[ $key ] = [
502 'value' => $this->cache[ $key ]['value'],
503 'found' => $this->cache[ $key ]['found'],
504 ];
505 }
506
507 continue;
508 } else if ( in_array( $group, $this->no_mc_groups ) ) {
509 $return[ $key ] = false;
510 $return_cache[ $key ] = [
511 'value' => false,
512 'found' => false,
513 ];
514
515 continue;
516 } else {
517 $fresh_get = $mc->get( $key );
518 $return[ $key ] = $fresh_get;
519 $return_cache[ $key ] = [
520 'value' => $fresh_get,
521 'found' => false !== $fresh_get,
522 ];
523 }
524 }
525
526 $elapsed = $this->timer_stop();
527 $this->group_ops_stats( 'get_multi', $keys, $group, null, $elapsed );
528 }
529
530 $this->cache = array_merge( $this->cache, $return_cache );
531
532 return $return;
533 }
534
535 function flush_prefix( $group ) {
536 if ( $group === $this->flush_group || $group === $this->global_flush_group ) {
537 // Never flush the flush numbers.
538 $number = '_';
539 } elseif ( false !== array_search( $group, $this->global_groups ) ) {
540 $number = $this->get_global_flush_number();
541 } else {
542 $number = $this->get_blog_flush_number();
543 }
544
545 return $number . ':';
546 }
547
548 function key( $key, $group ) {
549 if ( empty( $group ) ) {
550 $group = 'default';
551 }
552
553 $prefix = $this->key_salt;
554
555 $prefix .= $this->flush_prefix( $group );
556
557 if ( false !== array_search( $group, $this->global_groups ) ) {
558 $prefix .= $this->global_prefix;
559 } else {
560 $prefix .= $this->blog_prefix;
561 }
562
563 return preg_replace( '/\s+/', '', "$prefix:$group:$key" );
564 }
565
566 function replace( $id, $data, $group = 'default', $expire = 0 ) {
567 $key = $this->key( $id, $group );
568 $expire = intval( $expire );
569 if ( 0 === $expire || $expire > $this->max_expiration ) {
570 $expire = $this->default_expiration;
571 }
572 $mc =& $this->get_mc( $group );
573
574 if ( is_object( $data ) ) {
575 $data = clone $data;
576 }
577
578 $size = $this->get_data_size( $data );
579 $this->timer_start();
580 $result = $mc->replace( $key, $data, false, $expire );
581 $elapsed = $this->timer_stop();
582 $this->group_ops_stats( 'replace', $key, $group, $size, $elapsed );
583
584 if ( false !== $result ) {
585 $this->cache[ $key ] = [
586 'value' => $data,
587 'found' => true,
588 ];
589 }
590
591 return $result;
592 }
593
594 function set( $id, $data, $group = 'default', $expire = 0 ) {
595 $key = $this->key( $id, $group );
596
597 if ( isset( $this->cache[ $key ] ) && ( 'checkthedatabaseplease' === $this->cache[ $key ]['value'] ) ) {
598 return false;
599 }
600
601 if ( is_object( $data ) ) {
602 $data = clone $data;
603 }
604
605 $this->cache[ $key ] = [
606 'value' => $data,
607 'found' => false, // Set to false as not technically found in memcache at this point.
608 ];
609
610 if ( in_array( $group, $this->no_mc_groups ) ) {
611 $this->group_ops_stats( 'set_local', $key, $group, null, null );
612
613 return true;
614 }
615
616 $expire = intval( $expire );
617 if ( 0 === $expire || $expire > $this->max_expiration ) {
618 $expire = $this->default_expiration;
619 }
620
621 $mc =& $this->get_mc( $group );
622
623 $size = $this->get_data_size( $data );
624 $this->timer_start();
625 $result = $mc->set( $key, $data, false, $expire );
626 $elapsed = $this->timer_stop();
627 $this->group_ops_stats( 'set', $key, $group, $size, $elapsed );
628
629 // Update the found cache value with the result of the set in memcache.
630 $this->cache[ $key ]['found'] = $result;
631
632 return $result;
633 }
634
635 function switch_to_blog( $blog_id ) {
636 global $table_prefix;
637
638 $blog_id = (int) $blog_id;
639
640 $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix );
641 }
642
643 function colorize_debug_line( $line, $trailing_html = '' ) {
644 $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',
652 'delete_local' => 'tomato',
653 'slow-ops' => 'crimson',
654 );
655
656 $cmd = substr( $line, 0, strpos( $line, ' ' ) );
657
658 // Start off with a neutral default color...
659 $color_for_cmd = 'brown';
660 // And if the cmd has a specific color, use that instead
661 if ( isset( $colors[ $cmd ] ) ) {
662 $color_for_cmd = $colors[ $cmd ];
663 }
664
665 $cmd2 = "<span style='color:" . esc_attr( $color_for_cmd ) . "; font-weight: bold;'>" . esc_html( $cmd ) . "</span>";
666
667 return $cmd2 . esc_html( substr( $line, strlen( $cmd ) ) ) . "$trailing_html\n";
668 }
669
670 function js_toggle() {
671 echo "
672 <script>
673 function memcachedToggleVisibility( id, hidePrefix ) {
674 var element = document.getElementById( id );
675 if ( ! element ) {
676 return;
677 }
678
679 // Hide all element with `hidePrefix` if given. Used to display only one element at a time.
680 if ( hidePrefix ) {
681 var groupStats = document.querySelectorAll( '[id^=\"' + hidePrefix + '\"]' );
682 groupStats.forEach(
683 function ( element ) {
684 element.style.display = 'none';
685 }
686 );
687 }
688
689 // Toggle the one we clicked.
690 if ( 'none' === element.style.display ) {
691 element.style.display = 'block';
692 } else {
693 element.style.display = 'none';
694 }
695 }
696 </script>
697 ";
698 }
699
700 function stats() {
701 $this->js_toggle();
702
703 echo '<h2><span>Total memcache query time:</span>' . number_format_i18n( sprintf( '%0.1f', $this->time_total * 1000 ), 1 ) . ' ms</h2>';
704 echo "\n";
705 echo '<h2><span>Total memcache size:</span>' . esc_html( size_format( $this->size_total, 2 ) ) . '</h2>';
706 echo "\n";
707
708 foreach ( $this->stats as $stat => $n ) {
709 if ( empty( $n ) ) {
710 continue;
711 }
712
713 echo '<h2>';
714 echo $this->colorize_debug_line( "$stat $n" );
715 echo '</h2>';
716 }
717
718 echo "<ul class='debug-menu-links' style='clear:left;font-size:14px;'>\n";
719 $groups = array_keys( $this->group_ops );
720 usort( $groups, 'strnatcasecmp' );
721
722 $active_group = $groups[0];
723 // Always show `slow-ops` first
724 if ( in_array( 'slow-ops', $groups ) ) {
725 $slow_ops_key = array_search( 'slow-ops', $groups );
726 $slow_ops = $groups[ $slow_ops_key ];
727 unset( $groups[ $slow_ops_key ] );
728 array_unshift( $groups, $slow_ops );
729 $active_group = 'slow-ops';
730 }
731
732 $total_ops = 0;
733 $group_titles = array();
734 foreach ( $groups as $group ) {
735 $group_name = $group;
736 if ( empty( $group_name ) ) {
737 $group_name = 'default';
738 }
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]";
748 $group_titles[ $group ] = $group_title;
749 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 }
751 echo "</ul>\n";
752
753 echo "<div id='object-cache-stats-menu-targets'>\n";
754 foreach ( $groups as $group ) {
755 $group_name = $group;
756 if ( empty( $group_name ) ) {
757 $group_name = 'default';
758 }
759 $current = $active_group == $group ? 'style="display: block"' : 'style="display: none"';
760 echo "<div id='object-cache-stats-menu-target-" . esc_attr( $group_name ) . "' class='object-cache-stats-menu-target' $current>\n";
761 echo '<h3>' . esc_html( $group_titles[ $group ] ) . '</h3>' . "\n";
762 echo "<pre>\n";
763 foreach ( $this->group_ops[ $group ] as $index => $arr ) {
764 printf( '%3d ', $index );
765 echo $this->get_group_ops_line( $index, $arr );
766 }
767 echo "</pre>\n";
768 echo "</div>";
769 }
770
771 echo "</div>";
772 }
773
774 function get_group_ops_line( $index, $arr ) {
775 // operation
776 $line = "{$arr[0]} ";
777
778 // key
779 $json_encoded_key = json_encode( $arr[1] );
780 $line .= $json_encoded_key . " ";
781
782 // comment
783 if ( ! empty( $arr[4] ) ) {
784 $line .= "{$arr[4]} ";
785 }
786
787 // size
788 if ( isset( $arr[2] ) ) {
789 $line .= '(' . size_format( $arr[2], 2 ) . ') ';
790 }
791
792 // time
793 if ( isset( $arr[3] ) ) {
794 $line .= '(' . number_format_i18n( sprintf( '%0.1f', $arr[3] * 1000 ), 1 ) . ' ms)';
795 }
796
797 // backtrace
798 $bt_link = '';
799 if ( isset( $arr[6] ) ) {
800 $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>";
803 }
804
805 return $this->colorize_debug_line( $line, $bt_link );
806 }
807
808 function &get_mc( $group ) {
809 if ( isset( $this->mc[ $group ] ) ) {
810 return $this->mc[ $group ];
811 }
812
813 return $this->mc['default'];
814 }
815
816 function failure_callback( $host, $port ) {
817 $this->connection_errors[] = array(
818 'host' => $host,
819 'port' => $port,
820 );
821 }
822
823 function salt_keys( $key_salt ) {
824 if ( strlen( $key_salt ) ) {
825 $this->key_salt = $key_salt . ':';
826 } else {
827 $this->key_salt = '';
828 }
829 }
830
831 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 global $memcached_servers;
845
846 if ( isset( $memcached_servers ) ) {
847 $buckets = $memcached_servers;
848 } else {
849 $buckets = array( '127.0.0.1:11211' );
850 }
851
852 reset( $buckets );
853
854 if ( is_int( key( $buckets ) ) ) {
855 $buckets = array( 'default' => $buckets );
856 }
857
858 foreach ( $buckets as $bucket => $servers ) {
859 $this->mc[ $bucket ] = new Memcache();
860
861 foreach ( $servers as $i => $server ) {
862 if ( 'unix://' == substr( $server, 0, 7 ) ) {
863 $node = $server;
864 $port = 0;
865 } else {
866 list ( $node, $port ) = explode( ':', $server );
867
868 if ( ! $port ) {
869 $port = ini_get( 'memcache.default_port' );
870 }
871
872 $port = intval( $port );
873
874 if ( ! $port ) {
875 $port = 11211;
876 }
877 }
878
879 $this->mc[ $bucket ]->addServer( $node, $port, true, 1, 1, 15, true, array( $this, 'failure_callback' ) );
880 $this->mc[ $bucket ]->setCompressThreshold( 20000, 0.2 );
881
882 // Prepare individual connections to servers in default bucket for flush_number redundancy
883 if ( 'default' === $bucket ) {
884 $this->default_mcs[ $i ] = new Memcache();
885 $this->default_mcs[ $i ]->addServer( $node, $port, true, 1, 1, 15, true, array( $this, 'failure_callback' ) );
886 }
887 }
888 }
889
890 global $blog_id, $table_prefix;
891
892 $this->global_prefix = '';
893 $this->blog_prefix = '';
894
895 if ( function_exists( 'is_multisite' ) ) {
896 $this->global_prefix = ( is_multisite() || defined( 'CUSTOM_USER_TABLE' ) && defined( 'CUSTOM_USER_META_TABLE' ) ) ? '' : $table_prefix;
897 $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix );
898 }
899
900 $this->salt_keys( WP_CACHE_KEY_SALT );
901
902 $this->cache_hits =& $this->stats['get'];
903 $this->cache_misses =& $this->stats['add'];
904 }
905
906 function increment_stat( $field, $num = 1 ) {
907 if ( ! isset( $this->stats[ $field ] ) ) {
908 $this->stats[ $field ] = $num;
909 } else {
910 $this->stats[ $field ] += $num;
911 }
912 }
913
914 function group_ops_stats( $op, $keys, $group, $size, $time, $comment = '' ) {
915 $this->increment_stat( $op );
916
917 // we have no use of the local ops details for now
918 if ( strpos( $op, '_local' ) ) {
919 return;
920 }
921
922 $this->size_total += $size;
923
924 $keys = $this->strip_memcached_keys( $keys );
925
926 if ( $time > $this->slow_op_microseconds && 'get_multi' !== $op ) {
927 $this->increment_stat( 'slow-ops' );
928 $backtrace = null;
929 if ( function_exists( 'wp_debug_backtrace_summary' ) ) {
930 $backtrace = wp_debug_backtrace_summary();
931 }
932 $this->group_ops['slow-ops'][] = array( $op, $keys, $size, $time, $comment, $group, $backtrace );
933 }
934
935 $this->group_ops[ $group ][] = array( $op, $keys, $size, $time, $comment );
936 }
937
938 /**
939 * Key format: key_salt:flush_number:table_prefix:key_name
940 *
941 * We want to strip the `key_salt:flush_number` part to not leak the memcached keys.
942 * If `key_salt` is set we strip `'key_salt:flush_number`, otherwise just strip the `flush_number` part.
943 */
944 function strip_memcached_keys( $keys ) {
945 if ( ! is_array( $keys ) ) {
946 $keys = [ $keys ];
947 }
948
949 foreach ( $keys as $key => $value ) {
950 $offset = 0;
951 if ( ! empty( $this->key_salt ) ) {
952 $offset = strpos( $value, ':' ) + 1;
953 }
954
955 $start = strpos( $value, ':', $offset );
956 $keys[ $key ] = substr( $value, $start + 1 );
957 }
958
959 if ( 1 === count( $keys ) ) {
960 return $keys[0];
961 }
962
963 return $keys;
964 }
965
966 function timer_start() {
967 $this->time_start = microtime( true );
968
969 return true;
970 }
971
972 function timer_stop() {
973 $time_total = microtime( true ) - $this->time_start;
974 $this->time_total += $time_total;
975
976 return $time_total;
977 }
978
979 function get_data_size( $data ) {
980 if ( is_string( $data ) ) {
981 return strlen( $data );
982 }
983
984 $serialized = serialize( $data );
985
986 return strlen( $serialized );
987 }
988 }
989
990 endif;
991