PluginProbe
SQLite Object Cache / 1.3.6
SQLite Object Cache v1.3.6
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.6, at includes/lib/class-sqlite-object-cache-statistics.php

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