PluginProbe
SQLite Object Cache / 1.3.2
SQLite Object Cache v1.3.2
1.6.5 trunk 0.1.7 1.0.0 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.4.0 1.4.1 1.5.1 1.5.4 1.5.5 1.5.6 1.5.7 All 30 releases
sqlite-object-cache / includes / lib / class-sqlite-object-cache-statistics.php

class-sqlite-object-cache-statistics.php in SQLite Object Cache 1.3.2, at includes/lib/class-sqlite-object-cache-statistics.php

565 lines 17.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Statistics class file.
4 *
5 * @package SQLite Object Cache
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * statistics class.
14 */
15 class SQLite_Object_Cache_Statistics {
16
17 /**
18 * Associative array with selected cache items names most frequent first.
19 *
20 * @var array
21 */
22 public $selected_names;
23 /**
24 * Associative array with descriptive statistics.
25 *
26 * @var array
27 */
28 public $descriptions;
29 /**
30 * Show the overrun message.
31 * @var bool Overrun detected, show message.
32 */
33 private $overrun_message = false;
34 /**
35 * @var string
36 */
37 private $start_time;
38 /**
39 * @var string
40 */
41 private $end_time;
42 /**
43 * @var array
44 */
45 private $options;
46
47 public function __construct( $options ) {
48 $this->options = $options;
49 }
50
51 /**
52 * Initialize and load data.
53 *
54 * @return void
55 * @throws Exception Announce database failure.
56 */
57 public function init() {
58 global $wp_object_cache;
59
60 $first = PHP_INT_MAX;
61 $last = PHP_INT_MIN;
62
63 $selected_names = array();
64 $opens = array();
65 $selects = array();
66 $get_multiples = array();
67 $get_multiple_keys = array();
68 $inserts = array();
69 $deletes = array();
70 $RAMratios = array();
71 $RAMhits = 0;
72 $RAMmisses = 0;
73 $DISKratios = array();
74 $DISKLookupsPerRequest = array();
75 $SavesPerRequest = array();
76 $DBMSqueriesPerRequest = array();
77 $DISKhits = 0;
78 $DISKmisses = 0;
79
80 if ( ! method_exists( $wp_object_cache, 'sqlite_load_statistics' ) ) {
81 return;
82 }
83
84 if ( method_exists( $wp_object_cache, 'sqlite_remove_expired' ) ) {
85 $wp_object_cache->sqlite_remove_expired( true );
86 }
87
88 foreach ( $wp_object_cache->sqlite_load_statistics() as $data ) {
89 $first = min( $data->time, $first );
90 $last = max( $data->time, $last );
91 $RAMhits += $data->RAMhits;
92 $RAMmisses += $data->RAMmisses;
93 $DISKhits += $data->DISKhits;
94 $DISKmisses += $data->DISKmisses;
95 $DISKLookupsPerRequest[] = $data->DISKhits + $data->DISKmisses;
96 $RAMratio = $data->RAMhits / ( $data->RAMhits + $data->RAMmisses );
97 $RAMratios[] = $RAMratio;
98 $DISKratio = $data->DISKhits / ( $data->DISKhits + $data->DISKmisses );
99 $DISKratios[] = $DISKratio;
100 $opens [] = $data->open;
101 array_push( $selects, ...$data->selects );
102 array_push( $get_multiples, ...$data->get_multiples );
103 array_push( $get_multiple_keys, ...$data->get_multiple_keys );
104 array_push( $inserts, ...$data->inserts );
105 $SavesPerRequest [] = count( $data->inserts );
106 if ( property_exists( $data, 'DBMSqueries' ) && is_numeric( $data->DBMSqueries ) ) {
107 $DBMSqueriesPerRequest [] = $data->DBMSqueries;
108 }
109 array_push( $deletes, ...$data->deletes );
110
111 $this->truncate_if_too_long( $DISKLookupsPerRequest );
112 $this->truncate_if_too_long( $RAMratios );
113 $this->truncate_if_too_long( $DISKratios );
114 $this->truncate_if_too_long( $opens );
115 $this->truncate_if_too_long( $selects );
116 $this->truncate_if_too_long( $get_multiples );
117 $this->truncate_if_too_long( $get_multiple_keys );
118 $this->truncate_if_too_long( $inserts );
119 $this->truncate_if_too_long( $SavesPerRequest );
120 $this->truncate_if_too_long( $DBMSqueriesPerRequest );
121 $this->truncate_if_too_long( $deletes );
122
123 if ( property_exists( $data, 'select_names' ) && is_array( $data->select_names ) ) {
124 foreach ( $data->select_names as $name ) {
125 if ( ! array_key_exists( $name, $selected_names ) ) {
126 $selected_names[ $name ] = 0;
127 }
128 $selected_names[ $name ] ++;
129 }
130 }
131 }
132 $duration = $last - $first;
133 if ( $duration > 0 ) {
134 arsort( $selected_names );
135 $descriptions = array(
136 __( 'RAM hit ratio', 'sqlite-object-cache' ) => $this->descriptive_stats( $RAMratios ),
137 __( 'Disk hit ratio', 'sqlite-object-cache' ) => $this->descriptive_stats( $DISKratios ),
138 __( 'Disk lookups/request', 'sqlite-object-cache' ) => $this->descriptive_stats( $DISKLookupsPerRequest ),
139 __( 'Disk saves/request', 'sqlite-object-cache' ) => $this->descriptive_stats( $SavesPerRequest ),
140 __( 'MySQL queries/request', 'sqlite-object-cache' ) => $this->descriptive_stats( $DBMSqueriesPerRequest ),
141 __( 'Initialization times', 'sqlite-object-cache' ) => $this->descriptive_stats( $opens ),
142 __( 'Get times', 'sqlite-object-cache' ) => $this->descriptive_stats( $selects ),
143 __( 'GetMult times', 'sqlite-object-cache' ) => $this->descriptive_stats( $get_multiples ),
144 __( 'GetMult keys', 'sqlite-object-cache' ) => $this->descriptive_stats( $get_multiple_keys ),
145 __( 'Save times', 'sqlite-object-cache' ) => $this->descriptive_stats( $inserts ),
146 __( 'Delete times', 'sqlite-object-cache' ) => $this->descriptive_stats( $deletes ),
147 );
148
149 $this->descriptions = $descriptions;
150 $this->selected_names = $selected_names;
151 $this->start_time = $this->format_datestamp( $first );
152 $this->end_time = $this->format_datestamp( $last );
153 }
154 }
155
156 /**
157 * Truncate an array to a particular length.
158 *
159 * @param array $observations The array to truncate in place.
160 * @param int $limit Target array length.
161 *
162 * @return void
163 */
164 public function truncate_if_too_long( &$observations, $limit = 999999 ) {
165 if ( count( $observations ) > $limit ) {
166 $this->overrun_message = true;
167 array_splice( $observations, $limit );
168 }
169 }
170
171 /**
172 * Descriptive statistics for an array of numbers.
173 *
174 * @param array $a The array.
175 *
176 * @return array
177 */
178 public function descriptive_stats( array &$a ) {
179 sort( $a );
180 $min = $this->minimum( $a );
181 $max = $this->maximum( $a );
182
183 return array(
184 'n' => count( $a ),
185 '[min' => $min,
186 'p1' => $this->percentile( $a, 0.01 ),
187 'p5' => $this->percentile( $a, 0.05 ),
188 //'p33' => $this->percentile( $a, 0.33 ),
189 'median' => $this->percentile( $a, 0.5 ),
190 'mean' => $this->mean( $a ),
191 //'p67' => $this->percentile( $a, 0.67 ),
192 'p95' => $this->percentile( $a, 0.95 ),
193 'p99' => $this->percentile( $a, 0.99 ),
194 'max]' => $max,
195 'mad' => $this->mad( $a ),
196 'stdev' => $this->stdev( $a ),
197 'range' => $max - $min,
198 );
199 }
200
201 /**
202 * The smallest value in an array.
203 *
204 * @param array $a The array. Must be sorted.
205 *
206 * @return mixed|null
207 */
208 public function minimum( array $a ) {
209
210 return count( $a ) > 0 ? $a[0] : null;
211 }
212
213 /**
214 * The largest value in an array.
215 *
216 * @param array $a The array. Must be sorted.
217 *
218 * @return mixed|null
219 */
220 public function maximum( array $a ) {
221
222 return count( $a ) > 0 ? $a[ count( $a ) - 1 ] : null;
223 }
224
225 /** Percentile.
226 *
227 * @param array $a dataset. Must be sorted.
228 * @param number $p percentile as fraction 0-1.
229 *
230 * @return float
231 */
232 public function percentile( array $a, $p ) {
233 $n = count( $a );
234 if ( ! $n ) {
235 return null;
236 }
237 $i = (int) floor( $n * $p );
238 if ( $i >= $n ) {
239 $i = $n - 1;
240 }
241
242 return $a[ $i ];
243 }
244
245 /**
246 * Arithmetic mean.
247 *
248 * @param array $a dataset.
249 *
250 * @return number
251 */
252 public function mean( array $a ) {
253 $n = count( $a );
254 if ( ! $n ) {
255 return null;
256 }
257 if ( 1 === $n ) {
258 return $a[0];
259 }
260 $acc = 0;
261 foreach ( $a as $v ) {
262 $acc += $v;
263 }
264
265 return $acc / $n;
266 }
267
268 /**
269 * Mean absolute deviation.
270 *
271 * @param array $a dataset.
272 *
273 * @return float|int|null
274 */
275 public function mad( array $a ) {
276 $n = count( $a );
277 if ( ! $n ) {
278 return null;
279 }
280 if ( 1 === $n ) {
281 return 0.0;
282 }
283 $acc = 0;
284 foreach ( $a as $v ) {
285 $acc += $v;
286 }
287 $mean = $acc / $n;
288 $acc = 0;
289 foreach ( $a as $v ) {
290 $acc += abs( $v - $mean );
291 }
292
293 return $acc / $n;
294 }
295
296 /**
297 * Standard deviation.
298 *
299 * @param array $a dataset.
300 *
301 * @return float|null
302 */
303 public function stdev( $a ) {
304 $n = count( $a );
305 if ( ! $n ) {
306 return null;
307 }
308 if ( 1 === $n ) {
309 return 0.0;
310 }
311 $sum = 0.0;
312 $sumsq = 0.0;
313 foreach ( $a as $v ) {
314 $sum += $v;
315 $sumsq += ( $v * $v );
316 }
317 $mean = $sum / $n;
318
319 return sqrt( ( $sumsq / $n ) - ( $mean * $mean ) );
320 }
321
322 /**
323 * Render the statistics display.
324 *
325 * @return void
326 */
327 public function render() {
328
329 echo '<h3>' . esc_html__( 'Cache performance', 'sqlite-object-cache' ) . '</h3>';
330 if ( is_array( $this->descriptions ) ) {
331 echo '<p>' . esc_html( sprintf(
332 /* translators: 1 start time 2 end time both in localized format */
333 __( 'From %1$s to %2$s.', 'sqlite-object-cache' ),
334 $this->start_time, $this->end_time ) . ' ' . __( 'Times in microseconds.', 'sqlite-object-cache' ) ) . '</p>' . PHP_EOL;
335 echo '<table class="sql-object-cache-stats">' . PHP_EOL;
336 $first = true;
337 foreach ( $this->descriptions as $stat => $description ) {
338 if ( $first ) {
339 echo '<thead><tr>';
340 echo '<th scope="col"></th>';
341 foreach ( $description as $item => $value ) {
342 echo '<th scope="col" class="right">' . esc_html( $item ) . '</th>' . PHP_EOL;
343 }
344 echo '</tr></thead><tbody>' . PHP_EOL;
345 $first = false;
346 }
347 echo '<tr>';
348 echo '<th scope="row">' . esc_html( $stat ) . '</th>';
349 foreach ( $description as $value ) {
350 echo '<td>' . esc_html( round( $value, 2 ) ) . '</td>';
351 }
352 echo '</tr>' . PHP_EOL;
353 }
354 echo '</tr></tbody></table>' . PHP_EOL;
355 if ( $this->overrun_message ) {
356 echo '<p>';
357 esc_html_e( 'Some statistics were not processed. Processing them all uses too much RAM.', 'sqlite-object-cache' );
358 echo ' ';
359 esc_html_e( 'Try measuring a smaller random sample of requests.', 'sqlite-object-cache' );
360 echo '</p>' . PHP_EOL;
361 $this->overrun_message = false;
362 }
363 } else {
364 if ( is_array( $this->options ) && array_key_exists( 'capture', $this->options ) && 'on' === $this->options['capture'] ) {
365 echo '<p>' . esc_html__( 'No cache performance statistics have been captured.', 'sqlite-object-cache' ) . ' ';
366 $message =
367 /* translators: 1: a percentage */
368 __( 'The plugin is capturing a random sample of %s%% of requests. It is possible no samples have yet been captured.', 'sqlite-object-cache' );
369 $samplerate = is_numeric( $this->options['samplerate'] ) ? $this->options['samplerate'] : 1;
370 echo esc_html( sprintf( $message, $samplerate ) ) . '</p>';
371 } else {
372 echo '<p>' . esc_html__( 'Cache performance measurement is not enabled.', 'sqlite-object-cache' ) . ' ';
373 echo esc_html__( 'You may enable it on the Settings tab.', 'sqlite-object-cache' ) . '</p>';
374 }
375 }
376
377 if ( is_array( $this->selected_names ) && count( $this->selected_names ) > 0 ) {
378
379 $names = $this->selected_names;
380 uksort( $names, function ( $a, $b ) {
381 /* Descending order of frequency */
382 if ( $this->selected_names[ $a ] !== $this->selected_names[ $b ] ) {
383 return $this->selected_names[ $a ] > $this->selected_names[ $b ] ? - 1 : 1;
384 }
385 $as = explode( '|', $a, 2 );
386 $bs = explode( '|', $b, 2 );
387 /* Ascending order by group name */
388 if ( $as[0] !== $bs[0] ) {
389 return strnatcmp( $as[0], $bs[0] );
390 }
391 /* Ascending order by key name, handling numeric keys correctly. */
392 if ( is_numeric( $as[1] ) && is_numeric( $bs[1] ) ) {
393 if ( (float) $as[1] === (float) $bs[1] ) {
394 return 0;
395 }
396
397 return ( (float) $as[1] ) < ( (float) $bs[1] ) ? - 1 : 1;
398 }
399
400 return strnatcmp( $as[1], $bs[1] );
401 } );
402
403 echo '<h3>' . esc_html__( 'Most frequently looked up cache items', 'sqlite-object-cache' ) . '</h3>' . PHP_EOL;
404
405 echo '<table class="sql-object-cache-items">' . PHP_EOL;
406 $count_threshold = - 1;
407 $first = true;
408 foreach ( $names as $name => $count ) {
409 if ( $first ) {
410 echo '<thead><tr>';
411 echo '<th scope="col">' . esc_html__( "Cache Group", 'sqlite-object-cache' ) . '</th>';
412 echo '<th scope="col">' . esc_html__( "Cache Key", 'sqlite-object-cache' ) . '</th>';
413 echo '<th scope="col">' . esc_html__( "Count", 'sqlite-object-cache' ) . '</th>';
414 echo '</tr></thead><tbody>' . PHP_EOL;
415 $count_threshold = (int) ( $count * 0.7 );
416 $first = false;
417 }
418 if ( $count < $count_threshold ) {
419 break;
420 }
421 $splits = explode( '|', $name, 2 );
422 echo '<tr>';
423 echo '<td>' . esc_html( $splits[0] ) . '</td>';
424 echo '<td>' . esc_html( $splits[1] ) . '</td>';
425 echo '<td>' . esc_html( $count ) . '</td>';
426 echo '</tr>' . PHP_EOL;
427 }
428 }
429 echo '</tbody></table>';
430 }
431
432 /**
433 * Render the usage display.
434 *
435 * @return void
436 */
437 public function render_usage() {
438
439 global $wp_object_cache;
440 if ( ! method_exists( $wp_object_cache, 'sqlite_load_usages' ) ) {
441 return;
442 }
443
444 echo '<h3>' . esc_html__( 'Cache usage', 'sqlite-object-cache' ) . '</h3>';
445 $grouplength = array();
446 $groupcount = array();
447 $length = 0;
448 $count = 0;
449 $earliest = PHP_INT_MAX;
450 $latest = PHP_INT_MIN;
451
452 try {
453
454 foreach ( $wp_object_cache->sqlite_load_usages( true ) as $item ) {
455 $length += $item->length;
456 $count ++;
457 $ts = $item->expires;
458 $earliest = min( $earliest, $ts );
459 $latest = max( $latest, $ts );
460 $splits = explode( '|', $item->name, 2 );
461 $group = $splits[0];
462 $group = preg_replace( '/_[ 0-9._]+$/', '_*', $group );
463 if ( ! array_key_exists( $group, $grouplength ) ) {
464 $grouplength[ $group ] = 0;
465 $groupcount[ $group ] = 0;
466 }
467 $grouplength[ $group ] += $item->length;
468 $groupcount[ $group ] ++;
469 }
470 } catch ( Exception $ex ) {
471 echo '<p>' . esc_html__( 'Cannot load some or all cache items.', 'sqlite-object-cache' ) . '</p>';
472 }
473
474 if ( $count > 0 ) {
475 $groups = array_keys( $grouplength );
476 sort( $groups );
477
478 echo '<p>' . esc_html( sprintf(
479 /* translators: 1 start time 2 end time both in localized format */
480 __( 'Expirations from %1$s to %2$s.', 'sqlite-object-cache' ),
481 $this->format_datestamp( $earliest ), $this->format_datestamp( $latest ) ) . ' ' . __( 'Sizes in MiB.', 'sqlite-object-cache' ) ) . '</p>' . PHP_EOL;
482
483 echo '<table class="sql-object-cache-stats">' . PHP_EOL;
484 /* table headers */
485 echo '<thead><tr>';
486 echo '<th scope="col" class="right">' . esc_html__( 'Cache Group', 'sqlite-object-cache' ) . '</th>';
487 echo '<th scope="col" class="right">' . esc_html__( 'Items', 'sqlite-object-cache' ) . '</th>';
488 echo '<th scope="col" class="right">' . esc_html__( 'Size', 'sqlite-object-cache' ) . '</th>';
489 echo '</tr></thead><tbody>' . PHP_EOL;
490
491 if ( method_exists( $wp_object_cache, 'sqlite_sizes' ) ) {
492 $sizes = $wp_object_cache->sqlite_sizes();
493 $filesize = $sizes['page_size'] * $sizes['total_pages'];
494 $freesize = $sizes['page_size'] * $sizes['free_pages'];
495 $statssize = $sizes['stats_size'];
496 /* filesize row */
497 if ( $filesize ) {
498 echo '<tr>';
499 echo '<th scope="row" class="right">' . esc_html__( 'Total Cache Size', 'sqlite-object-cache' ) . '</th>';
500 echo '<td class="right total"></td>';
501 $sizemib = $filesize / ( 1024 * 1024 );
502 echo '<td class="right total">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
503 echo '</tr>' . PHP_EOL;
504 }
505 /* freesize row */
506 if ( $freesize ) {
507 echo '<tr>';
508 echo '<th scope="row" class="right">' . esc_html__( 'Free Cache Size', 'sqlite-object-cache' ) . '</th>';
509 echo '<td class="right total"></td>';
510 $sizemib = $freesize / ( 1024 * 1024 );
511 echo '<td class="right">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
512 echo '</tr>' . PHP_EOL;
513 }
514 /* statssize row */
515 if ( $statssize ) {
516 $statsitems = $sizes['stats_items'];
517 echo '<tr>';
518 echo '<th scope="row" class="right">' . esc_html__( 'Statistics', 'sqlite-object-cache' ) . '</th>';
519 echo '<td class="right">' . esc_html( number_format_i18n( $statsitems ) ) . '</td>';
520 $sizemib = $statssize / ( 1024 * 1024 );
521 echo '<td class="right">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
522 echo '</tr>' . PHP_EOL;
523 }
524 }
525
526 /* totals row */
527 echo '<tr>';
528 echo '<th scope="row" class="right">' . esc_html__( 'All Groups', 'sqlite-object-cache' ) . '</th>';
529 echo '<td class="right total">' . esc_html( number_format_i18n( $count, 0 ) ) . '</td>';
530 $sizemib = $length / ( 1024 * 1024 );
531 echo '<td class="right total">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
532 echo '</tr>' . PHP_EOL;
533 foreach ( $groups as $group ) {
534 /* detail row */
535 echo '<tr>';
536 echo '<th scope="row" class="right">' . esc_html( $group ) . '</th>';
537 echo '<td class="right">' . esc_html( number_format_i18n( $groupcount[ $group ], 0 ) ) . '</td>';
538 $sizemib = $grouplength[ $group ] / ( 1024 * 1024 );
539 echo '<td class="right">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
540 echo '</tr>' . PHP_EOL;
541 }
542
543 echo '</tbody></table>' . PHP_EOL;
544 } else {
545 echo '<p>' . esc_html__( 'No cache items found.', 'sqlite-object-cache' ) . '</p>';
546 }
547 }
548
549 /**
550 * Format a UNIX timestamp using WP settings.
551 *
552 * @param $stamp
553 *
554 * @return false|string
555 */
556 private
557 function format_datestamp(
558 $stamp
559 ) {
560 $date_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
561
562 return wp_date( $date_format, (int) $stamp );
563 }
564 }
565