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

549 lines 16.3 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 $filesize = null;
444 try {
445 if ( method_exists( $wp_object_cache, 'sqlite_files' ) ) {
446 foreach ( $wp_object_cache->sqlite_files() as $file ) {
447 ob_start();
448 $filesize = filesize( $file );
449 ob_end_clean();
450 break;
451 }
452 }
453 } catch ( Exception $ex ) {
454 $filesize = null;
455 }
456
457 echo '<h3>' . esc_html__( 'Cache usage', 'sqlite-object-cache' ) . '</h3>';
458 $grouplength = array();
459 $groupcount = array();
460 $length = 0;
461 $count = 0;
462 $earliest = PHP_INT_MAX;
463 $latest = PHP_INT_MIN;
464
465 try {
466
467 foreach ( $wp_object_cache->sqlite_load_usages( true ) as $item ) {
468 $length += $item->length;
469 $count ++;
470 $ts = $item->expires;
471 $earliest = min( $earliest, $ts );
472 $latest = max( $latest, $ts );
473 $splits = explode( '|', $item->name, 2 );
474 $group = $splits[0];
475 $group = preg_replace( '/_[ 0-9._]+$/', '_*', $group );
476 if ( ! array_key_exists( $group, $grouplength ) ) {
477 $grouplength[ $group ] = 0;
478 $groupcount[ $group ] = 0;
479 }
480 $grouplength[ $group ] += $item->length;
481 $groupcount[ $group ] ++;
482 }
483 } catch ( Exception $ex ) {
484 echo '<p>' . esc_html__( 'Cannot load some or all cache items.', 'sqlite-object-cache' ) . '</p>';
485 }
486
487 if ( $count > 0 ) {
488 $groups = array_keys( $grouplength );
489 sort( $groups );
490
491 echo '<p>' . esc_html( sprintf(
492 /* translators: 1 start time 2 end time both in localized format */
493 __( 'Expirations from %1$s to %2$s.', 'sqlite-object-cache' ),
494 $this->format_datestamp( $earliest ), $this->format_datestamp( $latest ) ) . ' ' . __( 'Sizes in MiB.', 'sqlite-object-cache' ) ) . '</p>' . PHP_EOL;
495
496 echo '<table class="sql-object-cache-stats">' . PHP_EOL;
497 /* table headers */
498 echo '<thead><tr>';
499 echo '<th scope="col" class="right">' . esc_html__( 'Cache Group', 'sqlite-object-cache' ) . '</th>';
500 echo '<th scope="col" class="right">' . esc_html__( 'Items', 'sqlite-object-cache' ) . '</th>';
501 echo '<th scope="col" class="right">' . esc_html__( 'Size', 'sqlite-object-cache' ) . '</th>';
502 echo '</tr></thead><tbody>' . PHP_EOL;
503
504 /* filesize row */
505 if ( $filesize ) {
506 echo '<tr>';
507 echo '<th scope="row" class="right">' . esc_html__( 'Cache File Size', 'sqlite-object-cache' ) . '</th>';
508 echo '<td class="right total"></td>';
509 $sizemib = $filesize / ( 1024 * 1024 );
510 echo '<td class="right total">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
511 echo '</tr>' . PHP_EOL;
512 }
513 /* totals row */
514 echo '<tr>';
515 echo '<th scope="row" class="right">' . esc_html__( 'All Groups', 'sqlite-object-cache' ) . '</th>';
516 echo '<td class="right total">' . esc_html( number_format_i18n( $count, 0 ) ) . '</td>';
517 $sizemib = $length / ( 1024 * 1024 );
518 echo '<td class="right total">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
519 echo '</tr>' . PHP_EOL;
520 foreach ( $groups as $group ) {
521 /* detail row */
522 echo '<tr>';
523 echo '<th scope="row" class="right">' . esc_html( $group ) . '</th>';
524 echo '<td class="right">' . esc_html( number_format_i18n( $groupcount[ $group ], 0 ) ) . '</td>';
525 $sizemib = $grouplength[ $group ] / ( 1024 * 1024 );
526 echo '<td class="right">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
527 echo '</tr>' . PHP_EOL;
528 }
529
530 echo '</tbody></table>' . PHP_EOL;
531 } else {
532 echo '<p>' . esc_html__( 'No cache items found.', 'sqlite-object-cache' ) . '</p>';
533 }
534 }
535
536 /**
537 * Format a UNIX timestamp using WP settings.
538 *
539 * @param $stamp
540 *
541 * @return false|string
542 */
543 private function format_datestamp( $stamp ) {
544 $date_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
545
546 return wp_date( $date_format, (int) $stamp );
547 }
548 }
549