Repository navigation
Add Site Health check for Full Page Cache usage #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
felixarntz
merged 25 commits into
WordPress:trunk
from
manuelRod:add/sitehealth-test-full-page-cache
Jun 3, 2022
Merged
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
463486e
First commit, adding amp tests, start refactoring
manuelRod f0c7beb
some refactoring + coding standards
manuelRod 19609d4
refactoring in helper file + wp standards
manuelRod 6f8ca76
Adding first unit tests
manuelRod c295ace
Merge branch 'release/1.0.0-beta.3' into add/sitehealth-test-full-pag…
manuelRod 0a78791
Adding tests
manuelRod b9c704f
test refactor and bugfix
manuelRod f6098e7
ups
manuelRod fea0974
fixing test
manuelRod ce59323
Fixing array header scenario per AMP approach https://github.com/ampp…
manuelRod b548bfb
fixing multisite test
manuelRod d1fb05a
adding aruba cache
manuelRod 30a35b6
removing escaping html, its exported to json and will cause problems
manuelRod bce19f0
little refactor + nitpicks
manuelRod 6ef0b44
lint
manuelRod 3f8f505
Merge branch 'trunk' into add/sitehealth-test-full-page-cache
manuelRod 7a239fa
Adding CODEOWNERS
manuelRod dfad675
replacing @since to n.e.x.t
manuelRod 2e6d1d9
moving things around
manuelRod fded890
updating loopback error test
manuelRod a8c3f7f
Adding @westonruter as code owner
manuelRod e137e28
Update modules/site-health/audit-full-page-cache/load.php
manuelRod 6f5eb4a
Update modules/site-health/audit-full-page-cache/load.php
manuelRod c1dbc3b
Update modules/site-health/audit-full-page-cache/load.php
manuelRod e130163
Update modules/site-health/audit-full-page-cache/load.php
manuelRod File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| <?php | ||
| /** | ||
| * Helper functions used by module. | ||
| * | ||
| * @package performance-lab | ||
| * @since n.e.x.t | ||
| */ | ||
|
|
||
| /** | ||
| * Returns a list of headers and its verification callback to verify if page cache is enabled or not. | ||
| * | ||
| * Note: key is header name and value could be callable function to verify header value. | ||
| * Empty value mean existence of header detect page cache is enabled. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @return array List of client caching headers and their (optional) verification callbacks. | ||
| */ | ||
| function perflab_afpc_get_page_cache_headers() { | ||
|
|
||
| $cache_hit_callback = static function ( $header_value ) { | ||
| return false !== strpos( strtolower( $header_value ), 'hit' ); | ||
| }; | ||
|
|
||
| return array( | ||
| 'cache-control' => static function ( $header_value ) { | ||
| return (bool) preg_match( '/max-age=[1-9]/', $header_value ); | ||
| }, | ||
| 'expires' => static function ( $header_value ) { | ||
| return strtotime( $header_value ) > time(); | ||
| }, | ||
| 'age' => static function ( $header_value ) { | ||
| return is_numeric( $header_value ) && $header_value > 0; | ||
| }, | ||
| 'last-modified' => '', | ||
| 'etag' => '', | ||
| 'x-cache' => $cache_hit_callback, | ||
| 'x-proxy-cache' => $cache_hit_callback, | ||
| 'cf-cache-status' => $cache_hit_callback, | ||
| 'x-kinsta-cache' => $cache_hit_callback, | ||
| 'x-aruba-cache' => $cache_hit_callback, | ||
| 'x-cache-enabled' => static function ( $header_value ) { | ||
| return 'true' === strtolower( $header_value ); | ||
| }, | ||
| 'x-cache-disabled' => static function ( $header_value ) { | ||
| return ( 'on' !== strtolower( $header_value ) ); | ||
| }, | ||
| 'cf-apo-via' => static function ( $header_value ) { | ||
| return false !== strpos( strtolower( $header_value ), 'tcache' ); | ||
| }, | ||
| 'x-srcache-store-status' => $cache_hit_callback, | ||
| 'x-srcache-fetch-status' => $cache_hit_callback, | ||
| 'cf-edge-cache' => static function ( $header_value ) { | ||
| return false !== strpos( strtolower( $header_value ), 'cache' ); | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Checks if site has page cache enabled or not. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @return WP_Error|array { | ||
| * Page caching detection details or else error information. | ||
| * | ||
| * @type bool $advanced_cache_present Whether a page caching plugin is present. | ||
| * @type array[] $page_caching_response_headers Sets of client caching headers for the responses. | ||
| * @type float[] $response_timing Response timings. | ||
| * } | ||
| */ | ||
| function perflab_afpc_check_for_page_caching() { | ||
|
|
||
| /** This filter is documented in wp-includes/class-wp-http-streams.php */ | ||
| $sslverify = apply_filters( 'https_local_ssl_verify', false ); | ||
|
|
||
| $headers = array(); | ||
|
|
||
| // Include basic auth in loopback requests. Note that this will only pass along basic auth when user is | ||
| // initiating the test. If a site requires basic auth, the test will fail when it runs in WP Cron as part of | ||
| // wp_site_health_scheduled_check. This logic is copied from WP_Site_Health::can_perform_loopback() in core. | ||
| if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) { // phpcs:ignore WordPressVIPMinimum.Variables.ServerVariables.BasicAuthentication | ||
| // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPressVIPMinimum.Variables.ServerVariables.BasicAuthentication | ||
| $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); | ||
| } | ||
|
|
||
| $caching_headers = perflab_afpc_get_page_cache_headers(); | ||
| $page_caching_response_headers = array(); | ||
| $response_timing = array(); | ||
|
|
||
| for ( $i = 1; $i <= 3; $i++ ) { | ||
| $start_time = microtime( true ); | ||
| $http_response = wp_remote_get( home_url( '/' ), compact( 'sslverify', 'headers' ) ); | ||
| $end_time = microtime( true ); | ||
|
|
||
| if ( is_wp_error( $http_response ) ) { | ||
| return $http_response; | ||
| } | ||
| if ( wp_remote_retrieve_response_code( $http_response ) !== 200 ) { | ||
| return new WP_Error( | ||
| 'http_' . wp_remote_retrieve_response_code( $http_response ), | ||
| wp_remote_retrieve_response_message( $http_response ) | ||
| ); | ||
| } | ||
|
|
||
| $response_headers = array(); | ||
|
|
||
| foreach ( $caching_headers as $header => $callback ) { | ||
| $header_values = wp_remote_retrieve_header( $http_response, $header ); | ||
| if ( empty( $header_values ) ) { | ||
| continue; | ||
| } | ||
| $header_values = (array) $header_values; | ||
| if ( | ||
| empty( $callback ) | ||
| || | ||
| ( | ||
| is_callable( $callback ) | ||
| && | ||
| count( array_filter( $header_values, $callback ) ) > 0 | ||
| ) | ||
| ) { | ||
| $response_headers[ $header ] = $header_values; | ||
| } | ||
| } | ||
|
|
||
| $page_caching_response_headers[] = $response_headers; | ||
| $response_timing[] = ( $end_time - $start_time ) * 1000; | ||
| } | ||
|
|
||
| return array( | ||
| 'advanced_cache_present' => ( | ||
| file_exists( WP_CONTENT_DIR . '/advanced-cache.php' ) | ||
| && | ||
| ( defined( 'WP_CACHE' ) && WP_CACHE ) | ||
| && | ||
| /** This filter is documented in wp-settings.php */ | ||
| apply_filters( 'enable_loading_advanced_cache_dropin', true ) | ||
| ), | ||
| 'page_caching_response_headers' => $page_caching_response_headers, | ||
| 'response_timing' => $response_timing, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Get page cache details. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @return WP_Error|array { | ||
| * Page cache detail or else a WP_Error if unable to determine. | ||
| * | ||
| * @type string $status Page cache status. Good, Recommended or Critical. | ||
| * @type bool $advanced_cache_present Whether page cache plugin is available or not. | ||
| * @type string[] $headers Client caching response headers detected. | ||
| * @type float $response_time Response time of site. | ||
| * } | ||
| */ | ||
| function perflab_afpc_get_page_cache_detail() { | ||
| $page_cache_detail = perflab_afpc_check_for_page_caching(); | ||
| if ( is_wp_error( $page_cache_detail ) ) { | ||
| return $page_cache_detail; | ||
| } | ||
|
|
||
| // Use the median server response time. | ||
| $response_timings = $page_cache_detail['response_timing']; | ||
| rsort( $response_timings ); | ||
| $page_speed = $response_timings[ floor( count( $response_timings ) / 2 ) ]; | ||
|
|
||
| // Obtain unique set of all client caching response headers. | ||
| $headers = array(); | ||
| foreach ( $page_cache_detail['page_caching_response_headers'] as $page_caching_response_headers ) { | ||
| $headers = array_merge( $headers, array_keys( $page_caching_response_headers ) ); | ||
| } | ||
| $headers = array_unique( $headers ); | ||
|
|
||
| // Page caching is detected if there are response headers or a page caching plugin is present. | ||
| $has_page_caching = ( count( $headers ) > 0 || $page_cache_detail['advanced_cache_present'] ); | ||
|
|
||
| if ( $page_speed && $page_speed < perflab_afpc_get_good_response_time_threshold() ) { | ||
| $result = $has_page_caching ? 'good' : 'recommended'; | ||
| } else { | ||
| $result = 'critical'; | ||
| } | ||
|
|
||
| return array( | ||
| 'status' => $result, | ||
| 'advanced_cache_present' => $page_cache_detail['advanced_cache_present'], | ||
| 'headers' => $headers, | ||
| 'response_time' => $page_speed, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Get the threshold below which a response time is considered good. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @return int Threshold in milliseconds. | ||
| */ | ||
| function perflab_afpc_get_good_response_time_threshold() { | ||
| /** | ||
| * Filters the threshold below which a response time is considered good. | ||
| * | ||
| * @since n.e.x.t | ||
| * @param int $threshold Threshold in milliseconds. Default 600. | ||
| */ | ||
| return (int) apply_filters( 'perflab_afpc_page_cache_good_response_time_threshold', 600 ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| <?php | ||
| /** | ||
| * Module Name: Audit Full Page Cache | ||
| * Description: Adds a check for full page cache in Site Health status. | ||
| * Experimental: No | ||
| * | ||
| * @package performance-lab | ||
| * @since n.e.x.t | ||
| */ | ||
|
|
||
| /** | ||
| * Require helper functions. | ||
| */ | ||
| require_once __DIR__ . '/helper.php'; | ||
|
|
||
| /** | ||
| * Adds tests to site health. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @param array $tests Site Health Tests. | ||
| * @return array | ||
|
manuelRod marked this conversation as resolved.
Outdated
|
||
| */ | ||
| function perflab_afpc_add_full_page_cache_test( $tests ) { | ||
| $tests['async']['perflab_page_cache'] = array( | ||
| 'label' => __( 'Page caching', 'performance-lab' ), | ||
| 'test' => rest_url( 'performance-lab/v1/tests/page-cache' ), | ||
| 'has_rest' => true, | ||
| 'async_direct_test' => 'perflab_afpc_page_cache_test', | ||
| ); | ||
| return $tests; | ||
| } | ||
| add_filter( 'site_status_tests', 'perflab_afpc_add_full_page_cache_test' ); | ||
|
|
||
| /** | ||
| * Callback for perflab_page_cache test. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @return array | ||
|
manuelRod marked this conversation as resolved.
Outdated
|
||
| */ | ||
| function perflab_afpc_page_cache_test() { | ||
| $description = '<p>' . __( 'Page caching enhances the speed and performance of your site by saving and serving static pages instead of calling for a page every time a user visits.', 'performance-lab' ) . '</p>'; | ||
| $description .= '<p>' . __( 'Page caching is detected by looking for an active page caching plugin as well as making three requests to the homepage and looking for one or more of the following HTTP client caching response headers:', 'performance-lab' ) . '</p>'; | ||
| $description .= '<code>' . implode( '</code>, <code>', array_keys( perflab_afpc_get_page_cache_headers() ) ) . '.</code>'; | ||
|
|
||
| $result = array( | ||
| 'badge' => array( | ||
| 'label' => __( 'Performance', 'performance-lab' ), | ||
| 'color' => 'green', | ||
| ), | ||
| 'description' => wp_kses_post( $description ), | ||
| 'test' => 'perflab_page_cache', | ||
| 'status' => 'good', | ||
| 'label' => '', | ||
| 'actions' => sprintf( | ||
| '<p><a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>', | ||
| esc_url( 'https://wordpress.org/support/article/optimization/#Caching' ), | ||
| __( 'Learn more about page caching', 'performance-lab' ), | ||
| /* translators: The accessibility text. */ | ||
| __( '(opens in a new tab)', 'performance-lab' ) | ||
| ), | ||
| ); | ||
|
|
||
| $page_cache_detail = perflab_afpc_get_page_cache_detail(); | ||
|
|
||
| if ( is_wp_error( $page_cache_detail ) ) { | ||
| $result['badge']['color'] = 'orange'; | ||
| $result['label'] = __( 'Unable to detect the presence of page caching', 'performance-lab' ); | ||
| $result['status'] = 'recommended'; | ||
| $error_info = sprintf( | ||
| /* translators: 1 is error message, 2 is error code */ | ||
| __( 'Unable to detect page caching due to possible loopback request problem. Please verify that the loopback request test is passing. Error: %1$s (Code: %2$s)', 'performance-lab' ), | ||
|
manuelRod marked this conversation as resolved.
Outdated
|
||
| $page_cache_detail->get_error_message(), | ||
| $page_cache_detail->get_error_code() | ||
| ); | ||
| $result['description'] = wp_kses_post( "<p>$error_info</p>" ) . $result['description']; | ||
| return $result; | ||
| } | ||
|
manuelRod marked this conversation as resolved.
|
||
|
|
||
| $result['status'] = $page_cache_detail['status']; | ||
|
|
||
| switch ( $page_cache_detail['status'] ) { | ||
| case 'recommended': | ||
| $result['badge']['color'] = 'orange'; | ||
| $result['label'] = __( 'Page caching is not detected but the server response time is OK', 'performance-lab' ); | ||
| break; | ||
| case 'good': | ||
| $result['badge']['color'] = 'green'; | ||
| $result['label'] = __( 'Page caching is detected and the server response time is good', 'performance-lab' ); | ||
| break; | ||
| default: | ||
| $result['badge']['color'] = 'red'; | ||
| if ( empty( $page_cache_detail['headers'] ) && ! $page_cache_detail['advanced_cache_present'] ) { | ||
| $result['label'] = __( 'Page caching is not detected and the server response time is slow', 'performance-lab' ); | ||
| } else { | ||
| $result['label'] = __( 'Page caching is detected but the server response time is still slow', 'performance-lab' ); | ||
| } | ||
| } | ||
|
|
||
| $page_cache_test_summary = array(); | ||
|
|
||
| if ( empty( $page_cache_detail['response_time'] ) ) { | ||
| $page_cache_test_summary[] = '<span class="dashicons dashicons-dismiss"></span> ' . __( 'Server response time could not be determined. Verify that loopback requests are working.', 'performance-lab' ); | ||
| } else { | ||
|
|
||
| $threshold = perflab_afpc_get_good_response_time_threshold(); | ||
| if ( $page_cache_detail['response_time'] < $threshold ) { | ||
| $page_cache_test_summary[] = '<span class="dashicons dashicons-yes-alt"></span> ' . sprintf( | ||
| /* translators: %d is the response time in milliseconds */ | ||
| __( 'Median server response time was %1$s milliseconds. This is less than the recommended %2$s millisecond threshold.', 'performance-lab' ), | ||
| number_format_i18n( $page_cache_detail['response_time'] ), | ||
| number_format_i18n( $threshold ) | ||
| ); | ||
| } else { | ||
| $page_cache_test_summary[] = '<span class="dashicons dashicons-warning"></span> ' . sprintf( | ||
| /* translators: %d is the response time in milliseconds */ | ||
| __( 'Median server response time was %1$s milliseconds. It should be less than the recommended %2$s milliseconds threshold.', 'performance-lab' ), | ||
| number_format_i18n( $page_cache_detail['response_time'] ), | ||
| number_format_i18n( $threshold ) | ||
| ); | ||
| } | ||
|
|
||
| if ( empty( $page_cache_detail['headers'] ) ) { | ||
| $page_cache_test_summary[] = '<span class="dashicons dashicons-warning"></span> ' . __( 'No client caching response headers were detected.', 'performance-lab' ); | ||
| } else { | ||
| $headers_summary = '<span class="dashicons dashicons-yes-alt"></span>'; | ||
| $headers_summary .= sprintf( | ||
| /* translators: Placeholder is number of caching headers */ | ||
| _n( | ||
|
manuelRod marked this conversation as resolved.
Outdated
|
||
| ' There was %d client caching response header detected: ', | ||
| ' There were %d client caching response headers detected: ', | ||
| count( $page_cache_detail['headers'] ), | ||
| 'performance-lab' | ||
| ), | ||
| count( $page_cache_detail['headers'] ) | ||
| ); | ||
| $headers_summary .= '<code>' . implode( '</code>, <code>', $page_cache_detail['headers'] ) . '</code>.'; | ||
| $page_cache_test_summary[] = $headers_summary; | ||
| } | ||
| } | ||
|
|
||
| if ( $page_cache_detail['advanced_cache_present'] ) { | ||
| $page_cache_test_summary[] = '<span class="dashicons dashicons-yes-alt"></span> ' . __( 'A page caching plugin was detected.', 'performance-lab' ); | ||
| } elseif ( ! ( is_array( $page_cache_detail ) && ! empty( $page_cache_detail['headers'] ) ) ) { | ||
| // Note: This message is not shown if client caching response headers were present since an external caching layer may be employed. | ||
| $page_cache_test_summary[] = '<span class="dashicons dashicons-warning"></span> ' . __( 'A page caching plugin was not detected.', 'performance-lab' ); | ||
| } | ||
|
|
||
| $result['description'] .= '<ul><li>' . implode( '</li><li>', $page_cache_test_summary ) . '</li></ul>'; | ||
| return $result; | ||
| } | ||
|
|
||
| /** | ||
| * Register async test REST endpoint. | ||
| * | ||
| * @since n.e.x.t | ||
| */ | ||
| function perflab_afpc_register_async_test_endpoints() { | ||
| register_rest_route( | ||
| 'performance-lab/v1', | ||
| '/tests/page-cache', | ||
| array( | ||
| array( | ||
| 'methods' => WP_REST_Server::READABLE, | ||
| 'callback' => 'perflab_afpc_page_cache_test', | ||
| 'permission_callback' => static function() { | ||
| return current_user_can( 'view_site_health_checks' ); | ||
| }, | ||
| ), | ||
| ) | ||
| ); | ||
| } | ||
| add_action( 'rest_api_init', 'perflab_afpc_register_async_test_endpoints' ); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.