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

540 lines 15.7 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 * Show the overrun message.
19 * @var bool Overrun detected, show message.
20 */
21 private $overrun_message = false;
22 /**
23 * Associative array with selected cache items names most frequent first.
24 *
25 * @var array
26 */
27 public $selected_names;
28
29 /**
30 * Associative array with descriptive statistics.
31 *
32 * @var array
33 */
34 public $descriptions;
35 /**
36 * @var string
37 */
38 private $start_time;
39 /**
40 * @var string
41 */
42 private $end_time;
43 /**
44 * @var array
45 */
46 private $options;
47
48 public function __construct( $options ) {
49 $this->options = $options;
50 }
51
52 /**
53 * Initialize and load data.
54 *
55 * @return void
56 * @throws Exception Announce database failure.
57 */
58 public function init() {
59 global $wp_object_cache;
60
61 $first = PHP_INT_MAX;
62 $last = PHP_INT_MIN;
63
64 $selected_names = [];
65 $opens = [];
66 $selects = [];
67 $inserts = [];
68 $deletes = [];
69 $RAMratios = [];
70 $RAMhits = 0;
71 $RAMmisses = 0;
72 $DISKratios = [];
73 $DISKLookupsPerRequest = [];
74 $SavesPerRequest = [];
75 $DBMSqueriesPerRequest = [];
76 $DISKhits = 0;
77 $DISKmisses = 0;
78
79 if ( ! method_exists( $wp_object_cache, 'sqlite_load_statistics' ) ) {
80 return;
81 }
82
83 if ( method_exists( $wp_object_cache, 'sqlite_clean_up_cache' ) ) {
84 $wp_object_cache->sqlite_clean_up_cache();
85 }
86
87 foreach ( $wp_object_cache->sqlite_load_statistics() as $data ) {
88 $first = min( $data->time, $first );
89 $last = max( $data->time, $last );
90 $RAMhits += $data->RAMhits;
91 $RAMmisses += $data->RAMmisses;
92 $DISKhits += $data->DISKhits;
93 $DISKmisses += $data->DISKmisses;
94 $DISKLookupsPerRequest[] = $data->DISKhits + $data->DISKmisses;
95 $RAMratio = $data->RAMhits / ( $data->RAMhits + $data->RAMmisses );
96 $RAMratios[] = $RAMratio;
97 $DISKratio = $data->DISKhits / ( $data->DISKhits + $data->DISKmisses );
98 $DISKratios[] = $DISKratio;
99 $opens [] = $data->open;
100 array_push( $selects, ...$data->selects );
101 array_push( $inserts, ...$data->inserts );
102 $SavesPerRequest [] = count( $data->inserts );
103 if ( property_exists( $data, 'DBMSqueries' ) && is_numeric( $data->DBMSqueries ) ) {
104 $DBMSqueriesPerRequest [] = $data->DBMSqueries;
105 }
106 array_push( $deletes, ...$data->deletes );
107
108 $this->truncate_if_too_long( $DISKLookupsPerRequest );
109 $this->truncate_if_too_long( $RAMratios );
110 $this->truncate_if_too_long( $DISKratios );
111 $this->truncate_if_too_long( $opens );
112 $this->truncate_if_too_long( $selects );
113 $this->truncate_if_too_long( $inserts );
114 $this->truncate_if_too_long( $SavesPerRequest );
115 $this->truncate_if_too_long( $DBMSqueriesPerRequest );
116 $this->truncate_if_too_long( $deletes );
117
118 if ( property_exists( $data, 'select_names' ) && is_array( $data->select_names ) ) {
119 foreach ( $data->select_names as $name ) {
120 if ( ! array_key_exists( $name, $selected_names ) ) {
121 $selected_names[ $name ] = 0;
122 }
123 $selected_names[ $name ] ++;
124 }
125 }
126 }
127 $duration = $last - $first;
128 if ( $duration > 0 ) {
129 arsort( $selected_names );
130 $descriptions = [
131 __( 'RAM hit ratio', 'sqlite-object-cache' ) => $this->descriptive_stats( $RAMratios ),
132 __( 'Disk hit ratio', 'sqlite-object-cache' ) => $this->descriptive_stats( $DISKratios ),
133 __( 'Disk lookups/request', 'sqlite-object-cache' ) => $this->descriptive_stats( $DISKLookupsPerRequest ),
134 __( 'Disk saves/request', 'sqlite-object-cache' ) => $this->descriptive_stats( $SavesPerRequest ),
135 __( 'MySQL queries/request', 'sqlite-object-cache' ) => $this->descriptive_stats( $DBMSqueriesPerRequest ),
136 __( 'Initialization times', 'sqlite-object-cache' ) => $this->descriptive_stats( $opens ),
137 __( 'Lookup times', 'sqlite-object-cache' ) => $this->descriptive_stats( $selects ),
138 __( 'Save times', 'sqlite-object-cache' ) => $this->descriptive_stats( $inserts ),
139 __( 'Delete times', 'sqlite-object-cache' ) => $this->descriptive_stats( $deletes ),
140 ];
141
142 $this->descriptions = $descriptions;
143 $this->selected_names = $selected_names;
144 $this->start_time = $this->format_datestamp( $first );
145 $this->end_time = $this->format_datestamp( $last );
146 }
147 }
148
149 /**
150 * Truncate an array to a particular length.
151 *
152 * @param array $observations The array to truncate in place.
153 * @param int $limit Target array length.
154 *
155 * @return void
156 */
157 public function truncate_if_too_long( &$observations, $limit = 999999 ) {
158 if ( count( $observations ) > $limit ) {
159 $this->overrun_message = true;
160 array_splice( $observations, $limit );
161 }
162 }
163
164 /**
165 * Descriptive statistics for an array of numbers.
166 *
167 * @param array $a The array.
168 *
169 * @return array
170 */
171 public function descriptive_stats( array &$a ) {
172 sort( $a );
173 $min = $this->minimum( $a );
174 $max = $this->maximum( $a );
175
176 return [
177 'n' => count( $a ),
178 '[min' => $min,
179 'p1' => $this->percentile( $a, 0.01 ),
180 'p5' => $this->percentile( $a, 0.05 ),
181 'median' => $this->percentile( $a, 0.5 ),
182 'mean' => $this->mean( $a ),
183 'p95' => $this->percentile( $a, 0.95 ),
184 'p99' => $this->percentile( $a, 0.99 ),
185 'max]' => $max,
186 'range' => $max - $min,
187 'mad' => $this->mad( $a ),
188 'stdev' => $this->stdev( $a ),
189 ];
190 }
191
192 /**
193 * The smallest value in an array.
194 *
195 * @param array $a The array. Must be sorted.
196 *
197 * @return mixed|null
198 */
199 public function minimum( array $a ) {
200
201 return count( $a ) > 0 ? $a[0] : null;
202 }
203
204 /**
205 * The largest value in an array.
206 *
207 * @param array $a The array. Must be sorted.
208 *
209 * @return mixed|null
210 */
211 public function maximum( array $a ) {
212
213 return count( $a ) > 0 ? $a[ count( $a ) - 1 ] : null;
214 }
215
216 /** Percentile.
217 *
218 * @param array $a dataset. Must be sorted.
219 * @param number $p percentile as fraction 0-1.
220 *
221 * @return float
222 */
223 public function percentile( array $a, $p ) {
224 $n = count( $a );
225 if ( ! $n ) {
226 return null;
227 }
228 $i = (int) floor( $n * $p );
229 if ( $i >= $n ) {
230 $i = $n - 1;
231 }
232
233 return $a[ $i ];
234 }
235
236 /**
237 * Arithmetic mean.
238 *
239 * @param array $a dataset.
240 *
241 * @return number
242 */
243 public function mean( array $a ) {
244 $n = count( $a );
245 if ( ! $n ) {
246 return null;
247 }
248 if ( 1 === $n ) {
249 return $a[0];
250 }
251 $acc = 0;
252 foreach ( $a as $v ) {
253 $acc += $v;
254 }
255
256 return $acc / $n;
257 }
258
259 /**
260 * Mean absolute deviation.
261 *
262 * @param array $a dataset.
263 *
264 * @return float|int|null
265 */
266 public function mad( array $a ) {
267 $n = count( $a );
268 if ( ! $n ) {
269 return null;
270 }
271 if ( 1 === $n ) {
272 return 0.0;
273 }
274 $acc = 0;
275 foreach ( $a as $v ) {
276 $acc += $v;
277 }
278 $mean = $acc / $n;
279 $acc = 0;
280 foreach ( $a as $v ) {
281 $acc += abs( $v - $mean );
282 }
283
284 return $acc / $n;
285 }
286
287 /**
288 * Standard deviation.
289 *
290 * @param array $a dataset.
291 *
292 * @return float|null
293 */
294 public function stdev( $a ) {
295 $n = count( $a );
296 if ( ! $n ) {
297 return null;
298 }
299 if ( 1 === $n ) {
300 return 0.0;
301 }
302 $sum = 0.0;
303 $sumsq = 0.0;
304 foreach ( $a as $v ) {
305 $sum += $v;
306 $sumsq += ( $v * $v );
307 }
308 $mean = $sum / $n;
309
310 return sqrt( ( $sumsq / $n ) - ( $mean * $mean ) );
311 }
312
313 /**
314 * Format a UNIX timestamp using WP settings.
315 *
316 * @param $stamp
317 *
318 * @return false|string
319 */
320 private function format_datestamp( $stamp ) {
321 $date_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
322
323 return wp_date( $date_format, (int) $stamp );
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 $filesize = null;
448 try {
449 if ( method_exists( $wp_object_cache, 'sqlite_files' ) ) {
450 foreach ( $wp_object_cache->sqlite_files() as $file ) {
451 ob_start();
452 $filesize = filesize( $file );
453 ob_end_clean();
454 break;
455 }
456 }
457 } catch ( Exception $ex ) {
458 $filesize = null;
459 }
460
461 echo '<h3>' . esc_html__( 'Cache usage', 'sqlite-object-cache' ) . '</h3>';
462 $grouplength = [];
463 $groupcount = [];
464 $length = 0;
465 $count = 0;
466 $earliest = PHP_INT_MAX;
467 $latest = PHP_INT_MIN;
468
469 try {
470
471 foreach ( $wp_object_cache->sqlite_load_usages( true ) as $item ) {
472 $length += $item->length;
473 $count ++;
474 $ts = $item->expires;
475 $earliest = min( $earliest, $ts );
476 $latest = max( $latest, $ts );
477 $splits = explode( '|', $item->name, 2 );
478 $group = $splits[0];
479 $group = preg_replace( '/_[ 0-9._]+$/', '_*', $group );
480 if ( ! array_key_exists( $group, $grouplength ) ) {
481 $grouplength[ $group ] = 0;
482 $groupcount[ $group ] = 0;
483 }
484 $grouplength[ $group ] += $item->length;
485 $groupcount[ $group ] ++;
486 }
487 } catch ( Exception $ex ) {
488 echo '<p>' . esc_html__( 'Cannot load some or all cache items.', 'sqlite-object-cache' ) . '</p>';
489 }
490
491 if ( $count > 0 ) {
492 $groups = array_keys( $grouplength );
493 sort( $groups );
494
495 echo '<p>' . esc_html( sprintf(
496 /* translators: 1 start time 2 end time both in localized format */
497 __( 'Expirations from %1$s to %2$s.', 'sqlite-object-cache' ),
498 $this->format_datestamp( $earliest ), $this->format_datestamp( $latest ) ) . ' ' . __( 'Sizes in MiB.', 'sqlite-object-cache' ) ) . '</p>' . PHP_EOL;
499
500 echo '<table class="sql-object-cache-stats">' . PHP_EOL;
501 /* table headers */
502 echo '<thead><tr>';
503 echo '<th scope="col" class="right">' . esc_html__( 'Cache Group', 'sqlite-object-cache' ) . '</th>';
504 echo '<th scope="col" class="right">' . esc_html__( 'Items', 'sqlite-object-cache' ) . '</th>';
505 echo '<th scope="col" class="right">' . esc_html__( 'Size', 'sqlite-object-cache' ) . '</th>';
506 echo '</tr></thead><tbody>' . PHP_EOL;
507
508 /* filesize row */
509 if ( $filesize ) {
510 echo '<tr>';
511 echo '<th scope="row" class="right">' . esc_html__( 'Cache File Size', 'sqlite-object-cache' ) . '</th>';
512 echo '<td class="right total"></td>';
513 $sizemib = $filesize / ( 1024 * 1024 );
514 echo '<td class="right total">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
515 echo '</tr>' . PHP_EOL;
516 }
517 /* totals row */
518 echo '<tr>';
519 echo '<th scope="row" class="right">' . esc_html__( 'All Groups', 'sqlite-object-cache' ) . '</th>';
520 echo '<td class="right total">' . esc_html( number_format_i18n( $count, 0 ) ) . '</td>';
521 $sizemib = $length / ( 1024 * 1024 );
522 echo '<td class="right total">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
523 echo '</tr>' . PHP_EOL;
524 foreach ( $groups as $group ) {
525 /* detail row */
526 echo '<tr>';
527 echo '<th scope="row" class="right">' . esc_html( $group ) . '</th>';
528 echo '<td class="right">' . esc_html( number_format_i18n( $groupcount[ $group ], 0 ) ) . '</td>';
529 $sizemib = $grouplength[ $group ] / ( 1024 * 1024 );
530 echo '<td class="right">' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '</td>';
531 echo '</tr>' . PHP_EOL;
532 }
533
534 echo '</tbody></table>' . PHP_EOL;
535 } else {
536 echo '<p>' . esc_html__( 'No cache items found.', 'sqlite-object-cache' ) . '</p>';
537 }
538 }
539 }
540