WordPress.org

Make WordPress Core

Changeset 45104


Ignore:
Timestamp:
04/02/2019 11:32:31 PM (6 months ago)
Author:
azaozz
Message:

Site health:

  • Prevent fatal errors from timeouts on the Tools => Site Health => Info tab.
  • Use the get_dirsize() and recurse_dirsize() functions to calculate directory sizes. The results are cached.
  • Introduce "timeout protection" in recurse_dirsize().

Props pento, Clorith, xkon, afercia, jeremyfelt, azaozz.
Fixes #46645.

Location:
trunk/src
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/class-wp-debug-data.php

    r45102 r45104  
    3333    static function debug_data( $locale = null ) {
    3434        global $wpdb;
    35 
    3635        if ( ! empty( $locale ) ) {
    3736            // Change the language used for translations
     
    313312        }
    314313
     314        $size_db = WP_Debug_Data::get_database_size();
     315
    315316        // Go through the various installation directories and calculate their sizes.
    316317        $uploads_dir = wp_upload_dir();
    317         $inaccurate  = false;
    318318
    319319        /*
    320320         * We will be using the PHP max execution time to prevent the size calculations
    321          * from causing a timeout. We provide a default value of 30 seconds, as some
     321         * from causing a timeout. The default value is 30 seconds, and some
    322322         * hosts do not allow you to read configuration values.
    323323         */
    324         $max_execution_time   = 30;
    325         $start_execution_time = microtime( true );
     324        $max_execution_time = 30;
     325
    326326        if ( function_exists( 'ini_get' ) ) {
    327327            $max_execution_time = ini_get( 'max_execution_time' );
     328        }
     329
     330        // Here 20 seconds is a "sensible default" for how long to make the user wait for the directory size calculation.
     331        // When testing 20 seconds seem enough in nearly all cases. The remaining edge cases are likely testing or development sites
     332        // that have very large number of files, for example `node_modules` in plugins or themes, etc.
     333        if ( $max_execution_time > 20  ) {
     334            $max_execution_time = 20;
     335        } elseif ( $max_execution_time > 2 ) {
     336            // If the max_execution_time is set to lower than 20 seconds, reduce it a bit to prevent
     337            // edge-case timeouts that may happen after the size loop has finished running.
     338            $max_execution_time -= 1;
    328339        }
    329340
     
    347358        );
    348359
     360        $timeout = __( 'The directory size calculation has timed out. Usually caused by a very large number of sub-directories and files.' );
     361        $inaccessible = __( 'The size cannot be calculated. The directory is not accessible. Usually caused by invalid permissions.' );
     362        $size_total = 0;
     363
    349364        // Loop over all the directories we want to gather the sizes for.
    350365        foreach ( $size_directories as $size => $attributes ) {
    351             /*
    352              * We run a helper function with a RecursiveIterator, which
    353              * may throw an exception if it can't access directories.
    354              *
    355              * If a failure is detected we mark the result as inaccurate.
    356              */
    357             try {
    358                 $calculated_size = WP_Debug_data::get_directory_size( $attributes['path'], $max_execution_time, $start_execution_time );
    359 
    360                 $size_directories[ $size ]['size'] = $calculated_size;
    361 
    362                 /*
    363                  * If the size returned is -1, this means execution has
    364                  * exceeded the maximum execution time, also denoting an
    365                  * inaccurate value in the end.
    366                  */
    367                 if ( -1 === $calculated_size ) {
    368                     $inaccurate = true;
     366            $dir_size = null; // Default to timeout.
     367
     368            if ( microtime( true ) - WP_START_TIMESTAMP < $max_execution_time ) {
     369                $dir_size = get_dirsize( $attributes['path'], $max_execution_time );
     370            }
     371
     372            if ( $dir_size === false ) {
     373                // Error reading
     374                $dir_size = $inaccessible;
     375                $size_total = null;
     376            } elseif ( $dir_size === null ) {
     377                // Timeout
     378                $dir_size = $timeout;
     379                $size_total = null;
     380            } else {
     381                $is_subdir = ( strpos( $size_directories[ $size ]['path'], ABSPATH ) === 0 );
     382
     383                if ( $size_total !== null && ( $size === 'wordpress' || ! $is_subdir ) ) {
     384                    $size_total += $dir_size;
    369385                }
    370             } catch ( Exception $e ) {
    371                 $inaccurate = true;
    372             }
    373         }
    374 
    375         $size_db = WP_Debug_Data::get_database_size();
    376 
    377         $size_total = $size_directories['wordpress']['size'] + $size_db;
     386
     387                $dir_size = size_format( $dir_size, 2 );
     388            }
     389
     390            $size_directories[ $size ]['size'] = $dir_size;
     391        }
     392
     393        if ( $size_total !== null && $size_db > 0 ) {
     394            $size_total = size_format( $size_total + $size_db, 2 );
     395        } else {
     396            $size_total = __( 'Total size is not available. Some errors were encountered when determining the size of your installation.' );
     397        }
    378398
    379399        $info['wp-paths-sizes']['fields'] = array(
     
    384404            array(
    385405                'label' => __( 'Uploads Directory Size' ),
    386                 'value' => ( -1 === $size_directories['uploads']['size'] ? __( 'Unable to determine the size of this directory' ) : size_format( $size_directories['uploads']['size'], 2 ) ),
     406                'value' => $size_directories['uploads']['size'],
    387407            ),
    388408            array(
     
    396416            array(
    397417                'label' => __( 'Themes Directory Size' ),
    398                 'value' => ( -1 === $size_directories['themes']['size'] ? __( 'Unable to determine the size of this directory' ) : size_format( $size_directories['themes']['size'], 2 ) ),
     418                'value' => $size_directories['themes']['size'],
    399419            ),
    400420            array(
     
    404424            array(
    405425                'label' => __( 'Plugins Directory Size' ),
    406                 'value' => ( -1 === $size_directories['plugins']['size'] ? __( 'Unable to determine the size of this directory' ) : size_format( $size_directories['plugins']['size'], 2 ) ),
     426                'value' => $size_directories['plugins']['size'],
    407427            ),
    408428            array(
     
    412432            array(
    413433                'label' => __( 'WordPress Directory Size' ),
    414                 'value' => size_format( $size_directories['wordpress']['size'], 2 ),
     434                'value' => $size_directories['wordpress']['size'],
    415435            ),
    416436            array(
     
    420440            array(
    421441                'label' => __( 'Total installation size' ),
    422                 'value' => sprintf(
    423                     '%s%s',
    424                     size_format( $size_total, 2 ),
    425                     ( false === $inaccurate ? '' : __( '- Some errors, likely caused by invalid permissions, were encountered when determining the size of your installation. This means the values represented may be inaccurate.' ) )
    426                 ),
     442                'value' => $size_total,
    427443            ),
    428444        );
     
    936952
    937953    /**
    938      * Return the size of a directory, including all subdirectories.
    939      *
    940      * @since 5.2.0
    941      *
    942      * @param string     $path                 The directory to check.
    943      * @param string|int $max_execution_time   How long a PHP script can run on this host.
    944      * @param float      $start_execution_time When we started executing this section of the script.
    945      *
    946      * @return int The directory size, in bytes.
    947      */
    948     public static function get_directory_size( $path, $max_execution_time, $start_execution_time ) {
    949         $size = 0;
    950 
    951         foreach ( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $path ) ) as $file ) {
    952             // Check if the maximum execution time is a value considered "infinite".
    953             if ( 0 !== $max_execution_time && -1 !== $max_execution_time ) {
    954                 $runtime = ( microtime( true ) - $start_execution_time );
    955 
    956                 // If the script has been running as long, or longer, as it is allowed, return a failure message.
    957                 if ( $runtime >= $max_execution_time ) {
    958                     return -1;
    959                 }
    960             }
    961             $size += $file->getSize();
    962         }
    963 
    964         return $size;
    965     }
    966 
    967     /**
    968954     * Fetch the total size of all the database tables for the active database user.
    969955     *
     
    983969        }
    984970
    985         return $size;
     971        return (int) $size;
    986972    }
    987973}
  • trunk/src/wp-includes/default-constants.php

    r44973 r45104  
    2929    define( 'TB_IN_BYTES', 1024 * GB_IN_BYTES );
    3030    /**#@-*/
     31
     32    // Start of run timestamp.
     33    if ( ! defined( 'WP_START_TIMESTAMP' ) ) {
     34        define( 'WP_START_TIMESTAMP', microtime( true ) );
     35    }
    3136
    3237    $current_limit     = @ini_get( 'memory_limit' );
  • trunk/src/wp-includes/functions.php

    r45045 r45104  
    70077007    echo '</p>';
    70087008}
     7009
     7010/**
     7011 * Get the size of a directory.
     7012 *
     7013 * A helper function that is used primarily to check whether
     7014 * a blog has exceeded its allowed upload space.
     7015 *
     7016 * @since MU (3.0.0)
     7017 *
     7018 * @param string $directory Full path of a directory.
     7019 * @param int    $max_execution_time Maximum time to run before giving up. In seconds.
     7020 *                                   The timeout is global and is measured from the moment WordPress started to load.
     7021 * @return int|false|null Size in MB if a valid directory. False if not. Null if timeout.
     7022 */
     7023function get_dirsize( $directory, $max_execution_time = null ) {
     7024    $dirsize = get_transient( 'dirsize_cache' );
     7025
     7026    if ( is_array( $dirsize ) && isset( $dirsize[ $directory ]['size'] ) ) {
     7027        return $dirsize[ $directory ]['size'];
     7028    }
     7029
     7030    if ( ! is_array( $dirsize ) ) {
     7031        $dirsize = array();
     7032    }
     7033
     7034    // Exclude individual site directories from the total when checking the main site of a network
     7035    // as they are subdirectories and should not be counted.
     7036    if ( is_multisite() && is_main_site() ) {
     7037        $dirsize[ $directory ]['size'] = recurse_dirsize( $directory, $directory . '/sites', $max_execution_time );
     7038    } else {
     7039        $dirsize[ $directory ]['size'] = recurse_dirsize( $directory, null, $max_execution_time );
     7040    }
     7041
     7042    set_transient( 'dirsize_cache', $dirsize, HOUR_IN_SECONDS );
     7043    return $dirsize[ $directory ]['size'];
     7044}
     7045
     7046/**
     7047 * Get the size of a directory recursively.
     7048 *
     7049 * Used by get_dirsize() to get a directory's size when it contains
     7050 * other directories.
     7051 *
     7052 * @since MU (3.0.0)
     7053 * @since 4.3.0 $exclude parameter added.
     7054 *
     7055 * @param string $directory Full path of a directory.
     7056 * @param string $exclude   Optional. Full path of a subdirectory to exclude from the total.
     7057 * @param int    $max_execution_time Maximum time to run before giving up. In seconds.
     7058 *                                   The timeout is global and is measured from the moment WordPress started to load.
     7059 * @return int|false|null Size in MB if a valid directory. False if not. Null if timeout.
     7060 */
     7061function recurse_dirsize( $directory, $exclude = null, $max_execution_time = null ) {
     7062    $size = 0;
     7063
     7064    $directory = untrailingslashit( $directory );
     7065
     7066    if ( ! file_exists( $directory ) || ! is_dir( $directory ) || ! is_readable( $directory ) || $directory === $exclude ) {
     7067        return false;
     7068    }
     7069
     7070    if ( ! $max_execution_time ) {
     7071        // Keep the previous behavior but attempt to prevent fatal errors from timeout.
     7072        if ( function_exists( 'ini_get' ) ) {
     7073            $max_execution_time = ini_get( 'max_execution_time' );
     7074        } else {
     7075            // Use PHP default.
     7076            $max_execution_time = 30;
     7077        }
     7078
     7079        // Leave 1 second "buffer" for other operations if $max_execution_time has reasonable value.
     7080        if ( $max_execution_time > 10 ) {
     7081            $max_execution_time -= 1;
     7082        }
     7083    }
     7084
     7085    if ( $handle = opendir( $directory ) ) {
     7086        while ( ( $file = readdir( $handle ) ) !== false ) {
     7087            $path = $directory . '/' . $file;
     7088            if ( $file != '.' && $file != '..' ) {
     7089                if ( is_file( $path ) ) {
     7090                    $size += filesize( $path );
     7091                } elseif ( is_dir( $path ) ) {
     7092                    $handlesize = recurse_dirsize( $path, $exclude, $max_execution_time );
     7093                    if ( $handlesize > 0 ) {
     7094                        $size += $handlesize;
     7095                    }
     7096                }
     7097
     7098                if ( microtime( true ) - WP_START_TIMESTAMP > $max_execution_time ) {
     7099                    // Time exceeded. Give up instead of risking a fatal timeout.
     7100                    $size = null;
     7101                    break;
     7102                }
     7103            }
     7104        }
     7105        closedir( $handle );
     7106    }
     7107    return $size;
     7108}
  • trunk/src/wp-includes/ms-functions.php

    r44926 r45104  
    17791779
    17801780/**
    1781  * Get the size of a directory.
    1782  *
    1783  * A helper function that is used primarily to check whether
    1784  * a blog has exceeded its allowed upload space.
    1785  *
    1786  * @since MU (3.0.0)
    1787  *
    1788  * @param string $directory Full path of a directory.
    1789  * @return int Size of the directory in MB.
    1790  */
    1791 function get_dirsize( $directory ) {
    1792     $dirsize = get_transient( 'dirsize_cache' );
    1793     if ( is_array( $dirsize ) && isset( $dirsize[ $directory ]['size'] ) ) {
    1794         return $dirsize[ $directory ]['size'];
    1795     }
    1796 
    1797     if ( ! is_array( $dirsize ) ) {
    1798         $dirsize = array();
    1799     }
    1800 
    1801     // Exclude individual site directories from the total when checking the main site,
    1802     // as they are subdirectories and should not be counted.
    1803     if ( is_main_site() ) {
    1804         $dirsize[ $directory ]['size'] = recurse_dirsize( $directory, $directory . '/sites' );
    1805     } else {
    1806         $dirsize[ $directory ]['size'] = recurse_dirsize( $directory );
    1807     }
    1808 
    1809     set_transient( 'dirsize_cache', $dirsize, HOUR_IN_SECONDS );
    1810     return $dirsize[ $directory ]['size'];
    1811 }
    1812 
    1813 /**
    1814  * Get the size of a directory recursively.
    1815  *
    1816  * Used by get_dirsize() to get a directory's size when it contains
    1817  * other directories.
    1818  *
    1819  * @since MU (3.0.0)
    1820  * @since 4.3.0 $exclude parameter added.
    1821  *
    1822  * @param string $directory Full path of a directory.
    1823  * @param string $exclude   Optional. Full path of a subdirectory to exclude from the total.
    1824  * @return int|false Size in MB if a valid directory. False if not.
    1825  */
    1826 function recurse_dirsize( $directory, $exclude = null ) {
    1827     $size = 0;
    1828 
    1829     $directory = untrailingslashit( $directory );
    1830 
    1831     if ( ! file_exists( $directory ) || ! is_dir( $directory ) || ! is_readable( $directory ) || $directory === $exclude ) {
    1832         return false;
    1833     }
    1834 
    1835     if ( $handle = opendir( $directory ) ) {
    1836         while ( ( $file = readdir( $handle ) ) !== false ) {
    1837             $path = $directory . '/' . $file;
    1838             if ( $file != '.' && $file != '..' ) {
    1839                 if ( is_file( $path ) ) {
    1840                     $size += filesize( $path );
    1841                 } elseif ( is_dir( $path ) ) {
    1842                     $handlesize = recurse_dirsize( $path, $exclude );
    1843                     if ( $handlesize > 0 ) {
    1844                         $size += $handlesize;
    1845                     }
    1846                 }
    1847             }
    1848         }
    1849         closedir( $handle );
    1850     }
    1851     return $size;
    1852 }
    1853 
    1854 /**
    18551781 * Check an array of MIME types against a whitelist.
    18561782 *
Note: See TracChangeset for help on using the changeset viewer.