options = $options; } /** * Initialize and load data. * * @return void * @throws Exception Announce database failure. */ public function init() { global $wp_object_cache; $first = PHP_INT_MAX; $last = PHP_INT_MIN; $selected_names = array(); $elapseds = array(); $opens = array(); $selects = array(); $gets = array(); $get_multiples = array(); $get_multiple_keys = array(); $inserts = array(); $deletes = array(); $checkpoints = array(); $RAMratios = array(); $DISKratios = array(); $DISKLookupsPerRequest = array(); $SavesPerRequest = array(); $DBMSqueriesPerRequest = array(); $RAM = array(); $APCufetchhit = array(); $APCufetchmiss = array(); $APCustore = array(); if ( ! method_exists( $wp_object_cache, 'sqlite_load_statistics' ) ) { return; } if ( method_exists( $wp_object_cache, 'sqlite_remove_expired' ) ) { $wp_object_cache->sqlite_remove_expired(); } echo '' . PHP_EOL; foreach ( $wp_object_cache->sqlite_load_statistics() as $data ) { $first = min( $data->time, $first ); $last = max( $data->time, $last ); $DISKLookupsPerRequest[] = $data->DISKhits + $data->DISKmisses; if ( ( $data->RAMhits + $data->RAMmisses ) > 0 ) { $RAMratio = $data->RAMhits / ( $data->RAMhits + $data->RAMmisses ); $RAMratios[] = $RAMratio; } if ( ( $data->APCuhits + $data->APCumisses ) > 0 ) { $APCUratio = $data->APCuhits / ( $data->APCuhits + $data->APCumisses ); $APCUratios[] = $APCUratio; } if ( ( $data->DISKhits + $data->DISKmisses ) > 0 ) { $DISKratio = $data->DISKhits / ( $data->DISKhits + $data->DISKmisses ); $DISKratios[] = $DISKratio; } $opens [] = $data->open; $elapseds [] = $data->elapsed * 1E-9; $RAM [] = $data->RAM / ( 1024 * 1024 ); array_push( $selects, ...$data->selects ); array_push( $gets, ...$data->gets ); array_push( $get_multiples, ...$data->get_multiples ); array_push( $get_multiple_keys, ...$data->get_multiple_keys ); array_push( $inserts, ...$data->inserts ); array_push( $APCufetchhit, ...$data->APCufetchhit ); array_push( $APCufetchmiss, ...$data->APCufetchmiss ); array_push( $APCustore, ...$data->APCustore ); $SavesPerRequest [] = count( $data->inserts ); if ( property_exists( $data, 'DBMSqueries' ) && is_numeric( $data->DBMSqueries ) ) { $DBMSqueriesPerRequest [] = $data->DBMSqueries; } array_push( $deletes, ...$data->deletes ); array_push( $checkpoints, ...$data->checkpoints ); $this->truncate_if_too_long( $DISKLookupsPerRequest ); $this->truncate_if_too_long( $RAMratios ); $this->truncate_if_too_long( $DISKratios ); $this->truncate_if_too_long( $APCUratios ); $this->truncate_if_too_long( $opens ); $this->truncate_if_too_long( $elapseds ); $this->truncate_if_too_long( $selects ); $this->truncate_if_too_long( $gets ); $this->truncate_if_too_long( $get_multiples ); $this->truncate_if_too_long( $get_multiple_keys ); $this->truncate_if_too_long( $inserts ); $this->truncate_if_too_long( $SavesPerRequest ); $this->truncate_if_too_long( $DBMSqueriesPerRequest ); $this->truncate_if_too_long( $deletes ); $this->truncate_if_too_long( $checkpoints ); $this->truncate_if_too_long( $RAM ); $this->truncate_if_too_long( $APCufetchhit ); $this->truncate_if_too_long( $APCufetchmiss ); $this->truncate_if_too_long( $APCustore ); if ( property_exists( $data, 'select_names' ) && is_array( $data->select_names ) ) { foreach ( $data->select_names as $name ) { if ( ! array_key_exists( $name, $selected_names ) ) { $selected_names[ $name ] = 0; } $selected_names[ $name ] ++; } } } $duration = $last - $first; if ( $duration > 0 ) { $this->scale( $opens ); $this->scale( $selects ); $this->scale( $gets ); $this->scale( $get_multiples ); $this->scale( $inserts ); $this->scale( $deletes ); $this->scale( $APCufetchhit ); $this->scale( $APCufetchmiss ); $this->scale( $APCustore ); arsort( $selected_names ); $descriptions = array( __( 'RAM hit ratio', 'sqlite-object-cache' ) => $this->descriptive_stats( $RAMratios ), __( 'APCu hit ratio', 'sqlite-object-cache' ) => $this->descriptive_stats( $APCUratios ), __( 'SQLite hit ratio', 'sqlite-object-cache' ) => $this->descriptive_stats( $DISKratios ), __( 'SQLite lookups/request', 'sqlite-object-cache' ) => $this->descriptive_stats( $DISKLookupsPerRequest ), __( 'SQLite saves/request', 'sqlite-object-cache' ) => $this->descriptive_stats( $SavesPerRequest ), __( 'MySQL queries/request', 'sqlite-object-cache' ) => $this->descriptive_stats( $DBMSqueriesPerRequest ), __( 'Request durations (sec)', 'sqlite-object-cache' ) => $this->descriptive_stats( $elapseds ), __( 'Peak RAM usage (MiB)', 'sqlite-object-cache' ) => $this->descriptive_stats( $RAM ), __( 'Initialization times', 'sqlite-object-cache' ) => $this->descriptive_stats( $opens ), __( 'Get times', 'sqlite-object-cache' ) => $this->descriptive_stats( $gets ), __( 'GetMult times', 'sqlite-object-cache' ) => $this->descriptive_stats( $get_multiples ), __( 'GetMult keys', 'sqlite-object-cache' ) => $this->descriptive_stats( $get_multiple_keys ), __( 'Save times', 'sqlite-object-cache' ) => $this->descriptive_stats( $inserts ), __( 'Delete times', 'sqlite-object-cache' ) => $this->descriptive_stats( $deletes ), __( 'Checkpoint times (sec)', 'sqlite-object-cache' ) => $this->descriptive_stats( $checkpoints ), __( 'APCu hit times', 'sqlite-object-cache' ) => $this->descriptive_stats( $APCufetchhit ), __( 'APCu miss times', 'sqlite-object-cache' ) => $this->descriptive_stats( $APCufetchmiss ), __( 'APCu save times', 'sqlite-object-cache' ) => $this->descriptive_stats( $APCustore ), __( 'SQLite hit times', 'sqlite-object-cache' ) => $this->descriptive_stats( $selects ), ); $this->descriptions = $descriptions; $this->selected_names = $selected_names; $this->start_time = $this->format_datestamp( $first ); $this->end_time = $this->format_datestamp( $last ); } } /** * Truncate an array to a particular length. * * @param array $observations The array to truncate in place. * @param int $limit Target array length. * * @return void */ public function truncate_if_too_long( &$observations, $limit = 999999 ) { if ( is_array( $observations ) && count( $observations ) > $limit ) { $this->overrun_message = true; array_splice( $observations, $limit ); } } /** * Descriptive statistics for an array of numbers. * * @param mixed $a The array. * * @return array */ public function descriptive_stats( &$a ) { $a = is_array( $a ) ? $a : array(); sort( $a ); $min = $this->minimum( $a ); $max = $this->maximum( $a ); return array( 'n' => count( $a ), '[min' => $min, 'p1' => $this->percentile( $a, 0.01 ), 'p5' => $this->percentile( $a, 0.05 ), //'p33' => $this->percentile( $a, 0.33 ), 'median' => $this->percentile( $a, 0.5 ), 'mean' => $this->mean( $a ), //'p67' => $this->percentile( $a, 0.67 ), 'p95' => $this->percentile( $a, 0.95 ), 'p99' => $this->percentile( $a, 0.99 ), 'max]' => $max, 'mad' => $this->mad( $a ), 'stdev' => $this->stdev( $a ), 'range' => $max - $min, ); } /** * The smallest value in an array. * * @param array $a The array. Must be sorted. * * @return mixed|null */ public function minimum( array $a ) { return count( $a ) > 0 ? $a[0] : null; } /** * The largest value in an array. * * @param array $a The array. Must be sorted. * * @return mixed|null */ public function maximum( array $a ) { return count( $a ) > 0 ? $a[ count( $a ) - 1 ] : null; } /** Percentile. * * @param array $a dataset. Must be sorted. * @param number $p percentile as fraction 0-1. * * @return float */ public function percentile( array $a, $p ) { $n = count( $a ); if ( ! $n ) { return null; } $i = (int) floor( $n * $p ); if ( $i >= $n ) { $i = $n - 1; } return $a[ $i ]; } /** * Arithmetic mean. * * @param array $a dataset. * * @return number */ public function mean( array $a ) { $n = count( $a ); if ( ! $n ) { return null; } if ( 1 === $n ) { return $a[0]; } $acc = 0; foreach ( $a as $v ) { $acc += $v; } return $acc / $n; } /** * Mean absolute deviation. * * @param array $a dataset. * * @return float|int|null */ public function mad( array $a ) { $n = count( $a ); if ( ! $n ) { return null; } if ( 1 === $n ) { return 0.0; } $acc = 0; foreach ( $a as $v ) { $acc += $v; } $mean = $acc / $n; $acc = 0; foreach ( $a as $v ) { $acc += abs( $v - $mean ); } return $acc / $n; } /** * Standard deviation. * * @param array $a dataset. * * @return float|null */ public function stdev( $a ) { $n = count( $a ); if ( ! $n ) { return null; } if ( 1 === $n ) { return 0.0; } $sum = 0.0; $sumsq = 0.0; foreach ( $a as $v ) { $sum += $v; $sumsq += ( $v * $v ); } $mean = $sum / $n; return sqrt( ( $sumsq / $n ) - ( $mean * $mean ) ); } /** * Render the statistics display. * * @return void */ public function render() { echo '

' . esc_html__( 'Cache performance', 'sqlite-object-cache' ) . '

'; if ( is_array( $this->descriptions ) ) { echo '

' . esc_html( sprintf( /* translators: 1 start time 2 end time both in localized format */ __( 'From %1$s to %2$s.', 'sqlite-object-cache' ), $this->start_time, $this->end_time ) . ' ' . __( 'Times in microseconds, checkpoint and request durations in seconds.', 'sqlite-object-cache' ) ) . '

' . PHP_EOL; echo '' . PHP_EOL; foreach ( $this->descriptions as $stat => $description ) { if ( ! is_array( $description ) || ! array_key_exists( 'n', $description ) || $description['n'] <= 0 ) { continue; } echo ''; echo ''; foreach ( $description as $item => $value ) { echo '' . PHP_EOL; } echo '' . PHP_EOL; break; } echo '' . PHP_EOL; foreach ( $this->descriptions as $stat => $description ) { if ( ! is_array( $description ) || ! array_key_exists( 'n', $description ) || $description['n'] <= 0 ) { continue; } echo ''; echo ''; $decimals = 0; foreach ( $description as $value ) { echo ''; $decimals = 2; } echo '' . PHP_EOL; } echo '' . PHP_EOL; foreach ( $this->descriptions as $stat => $description ) { if ( ! is_array( $description ) || ! array_key_exists( 'n', $description ) || $description['n'] <= 0 ) { continue; } echo ''; echo ''; foreach ( $description as $item => $value ) { echo '' . PHP_EOL; } echo '' . PHP_EOL; break; } echo '
' . esc_html( $item ) . '
' . esc_html( $stat ) . '' . esc_html( number_format_i18n( $value ?: 0.0, $decimals ) ) . '
' . esc_html( $item ) . '
' . PHP_EOL; if ( $this->overrun_message ) { echo '

'; esc_html_e( 'Some statistics were not processed. Processing them all uses too much RAM.', 'sqlite-object-cache' ); echo ' '; esc_html_e( 'Try measuring a smaller random sample of requests.', 'sqlite-object-cache' ); echo '

' . PHP_EOL; $this->overrun_message = false; } } else { if ( is_array( $this->options ) && array_key_exists( 'capture', $this->options ) && 'on' === $this->options['capture'] ) { echo '

' . esc_html__( 'No cache performance statistics have been captured.', 'sqlite-object-cache' ) . ' '; $message = /* translators: 1: a percentage */ __( 'The plugin is capturing a random sample of %s%% of requests. It is possible no samples have yet been captured.', 'sqlite-object-cache' ); $samplerate = is_numeric( $this->options['samplerate'] ) ? $this->options['samplerate'] : 1; echo esc_html( sprintf( $message, $samplerate ) ) . '

'; } else { echo '

' . esc_html__( 'Cache performance measurement is not enabled.', 'sqlite-object-cache' ) . ' '; echo esc_html__( 'You may enable it on the Settings tab.', 'sqlite-object-cache' ) . '

'; } } if ( is_array( $this->selected_names ) && count( $this->selected_names ) > 0 ) { $names = $this->selected_names; uksort( $names, function ( $a, $b ) { /* Descending order of frequency */ if ( $this->selected_names[ $a ] !== $this->selected_names[ $b ] ) { return $this->selected_names[ $a ] > $this->selected_names[ $b ] ? - 1 : 1; } $as = explode( '|', $a, 2 ); $bs = explode( '|', $b, 2 ); /* Ascending order by group name */ if ( $as[0] !== $bs[0] ) { return strnatcmp( $as[0], $bs[0] ); } /* Ascending order by key name, handling numeric keys correctly. */ if ( is_numeric( $as[1] ) && is_numeric( $bs[1] ) ) { if ( (float) $as[1] === (float) $bs[1] ) { return 0; } return ( (float) $as[1] ) < ( (float) $bs[1] ) ? - 1 : 1; } return strnatcmp( $as[1], $bs[1] ); } ); echo '

' . esc_html__( 'Most frequently looked up cache items', 'sqlite-object-cache' ) . '

' . PHP_EOL; echo '' . PHP_EOL; $count_threshold = - 1; $first = true; foreach ( $names as $name => $count ) { if ( $first ) { echo ''; echo ''; echo ''; echo ''; echo '' . PHP_EOL; $count_threshold = (int) ( $count * 0.7 ); $first = false; } if ( $count < $count_threshold ) { break; } $splits = explode( '|', $name, 2 ); echo ''; echo ''; echo ''; echo ''; echo '' . PHP_EOL; } } echo '
' . esc_html__( "Cache Group", 'sqlite-object-cache' ) . '' . esc_html__( "Cache Key", 'sqlite-object-cache' ) . '' . esc_html__( "Count", 'sqlite-object-cache' ) . '
' . esc_html( $splits[0] ) . '' . esc_html( $splits[1] ) . '' . esc_html( $count ) . '
'; } /** * Render the usage display. * * @return void */ public function render_usage() { global $wp_object_cache; if ( ! method_exists( $wp_object_cache, 'sqlite_load_usages' ) ) { return; } echo '

' . esc_html__( 'Cache usage', 'sqlite-object-cache' ) . '

'; $grouplength = array(); $groupcount = array(); $length = 0; $count = 0; $earliest = PHP_INT_MAX; $latest = PHP_INT_MIN; try { foreach ( $wp_object_cache->sqlite_load_usages( true ) as $item ) { $length += $item->length; $count ++; $ts = $item->expires; $earliest = min( $earliest, $ts ); $latest = max( $latest, $ts ); $splits = explode( '|', $item->name, 2 ); $group = $splits[0]; $group = preg_replace( '/^([^_]+)_.+_(relationships)$/', '\1-*-\2', $group ); $group = preg_replace( '/_[ 0-9._]+$/', '_*', $group ); if ( ! array_key_exists( $group, $grouplength ) ) { $grouplength[ $group ] = 0; $groupcount[ $group ] = 0; } $grouplength[ $group ] += $item->length; $groupcount[ $group ] ++; if ( $count >= $this->max_scan ) { echo '

' . esc_html( sprintf( /* translators: 1 a localized number of cache items. */ __( 'Only showing sizes for the first %1$s cache items.', 'sqlite-object-cache' ), number_format_i18n( $this->max_scan, 0 ) ) ) . '

'; break; } } } catch ( Exception $ex ) { echo '

' . esc_html__( 'Cannot load some or all cache items.', 'sqlite-object-cache' ) . '

'; } if ( $count > 0 ) { $groups = array_keys( $grouplength ); sort( $groups ); echo '

' . esc_html( sprintf( /* translators: 1 start time 2 end time both in localized format */ __( 'Expirations from %1$s to %2$s.', 'sqlite-object-cache' ), $this->format_datestamp( $earliest ), $this->format_datestamp( $latest ) ) . ' ' . __( 'Sizes in MiB.', 'sqlite-object-cache' ) ) . '

' . PHP_EOL; echo '' . PHP_EOL; /* table headers */ echo ''; echo ''; echo ''; echo ''; echo '' . PHP_EOL; if ( method_exists( $wp_object_cache, 'sqlite_sizes' ) ) { $sizes = $wp_object_cache->sqlite_sizes(); $pagesize = $sizes['page_size']; $filesize = $sizes['total_pages']; $freesize = $sizes['free_pages']; $usedsize = $filesize - $freesize; $statssize = $sizes['stats_size']; $mmapsize = $sizes['mmap_size']; $has_apcu = defined( 'WP_SQLITE_OBJECT_CACHE_APCU' ) && WP_SQLITE_OBJECT_CACHE_APCU && function_exists( 'apcu_enabled' ) && apcu_enabled(); if ( $has_apcu ) { $stat = apcu_cache_info( true ); $apcu_entries = $stat['num_entries']; $apcu_mem = $stat['mem_size'] / ( 1024 * 1024 ); /* filesize row */ echo ''; echo ''; echo ''; echo ''; echo '' . PHP_EOL; } /* filesize row */ if ( $usedsize ) { echo ''; echo ''; echo ''; $sizemib = ( $usedsize * $pagesize ) / ( 1024.0 * 1024.0 ); echo ''; echo '' . PHP_EOL; } /* freesize row */ if ( $freesize ) { echo ''; echo ''; echo ''; $sizemib = ( $freesize * $pagesize ) / ( 1024 * 1024 ); echo ''; echo '' . PHP_EOL; } /* memory-mapped row */ if ( $mmapsize ) { echo ''; echo ''; echo ''; $sizemib = $mmapsize / ( 1024 * 1024 ); echo ''; echo '' . PHP_EOL; } /* statssize row */ if ( $statssize ) { $statsitems = $sizes['stats_items']; echo ''; echo ''; echo ''; $sizemib = $statssize / ( 1024 * 1024 ); echo ''; echo '' . PHP_EOL; } } /* totals row */ echo ''; echo ''; echo ''; $sizemib = $length / ( 1024 * 1024 ); echo ''; echo '' . PHP_EOL; foreach ( $groups as $group ) { /* detail row */ echo ''; echo ''; echo ''; $sizemib = $grouplength[ $group ] / ( 1024 * 1024 ); echo ''; echo '' . PHP_EOL; } echo '
' . esc_html__( 'Cache Group', 'sqlite-object-cache' ) . '' . esc_html__( 'Items', 'sqlite-object-cache' ) . '' . esc_html__( 'Size', 'sqlite-object-cache' ) . '
' . esc_html__( 'APCu RAM Usage', 'sqlite-object-cache' ) . '' . esc_html( number_format_i18n( $apcu_entries ) ) . '' . esc_html( number_format_i18n( $apcu_mem, 3 ) ) . '
' . esc_html__( 'SQLite Disk Pages Used', 'sqlite-object-cache' ) . '' . esc_html( number_format_i18n( $usedsize ) ) . '' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '
' . esc_html__( 'SQLite Disk Pages Free', 'sqlite-object-cache' ) . '' . esc_html( number_format_i18n( $freesize ) ) . '' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '
' . esc_html__( 'SQLite Memory mapped', 'sqlite-object-cache' ) . '' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '
' . esc_html__( 'Statistics', 'sqlite-object-cache' ) . '' . esc_html( number_format_i18n( $statsitems ) ) . '' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '
' . esc_html__( 'All Groups', 'sqlite-object-cache' ) . '' . esc_html( number_format_i18n( $count, 0 ) ) . '' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '
' . esc_html( $group ) . '' . esc_html( number_format_i18n( $groupcount[ $group ], 0 ) ) . '' . esc_html( number_format_i18n( $sizemib, 3 ) ) . '
' . PHP_EOL; } else { echo '

' . esc_html__( 'No cache items found.', 'sqlite-object-cache' ) . '

'; } } /** * Format a UNIX timestamp using WP settings. * * @param $stamp * * @return false|string */ private function format_datestamp( $stamp ) { $date_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' ); return wp_date( $date_format, (int) $stamp ); } private function scale( array &$observations, $scale = 0.001 ) { if ( is_array( $observations ) ) { foreach ( $observations as $key => $val ) { $observations[ $key ] = $val * $scale; } } } }