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

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