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

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