Skip to content
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

Add Site Health check module for persistent object cache #111

Merged
merged 48 commits into from Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
2f7c803
initial code import
tillkruss Jan 19, 2022
348fff5
use existing page; still needs section
tillkruss Jan 20, 2022
b6a1cc3
add some basic criteria
tillkruss Jan 20, 2022
60d3862
make it easy to add a thresholds filter later on
tillkruss Jan 20, 2022
8894446
please the formatting gods
tillkruss Jan 20, 2022
234c048
detect object cache services
tillkruss Jan 20, 2022
ae1d6f7
please the gods again
tillkruss Jan 20, 2022
4f89ffe
be more verbal
tillkruss Jan 21, 2022
bc01b45
refine 2nd part of the description
tillkruss Jan 21, 2022
07f12a3
Update load.php
tillkruss Jan 21, 2022
24cb869
revert debug value
tillkruss Jan 21, 2022
c880585
drop the word persistent
tillkruss Jan 24, 2022
ce40e36
docblock fixes; since tags; coding standards
tillkruss Feb 9, 2022
a0201f6
docblocks
tillkruss Feb 9, 2022
21af177
fix filter ref
tillkruss Feb 9, 2022
8d2d709
add more since tags
tillkruss Feb 9, 2022
02f1094
tweak copy
tillkruss Feb 9, 2022
5f51a6f
fix function name
tillkruss Feb 9, 2022
e3bd858
turn conditions into single loop
tillkruss Feb 9, 2022
af3dd69
allow easy bypass of expensive calls
tillkruss Feb 9, 2022
64e4806
made thresholds filterable
tillkruss Feb 9, 2022
6f4d1ce
sort
tillkruss Feb 9, 2022
52c59ce
code styling
tillkruss Feb 9, 2022
0114f3d
remove debug
tillkruss Feb 10, 2022
839aad4
make badge orange when recommended
tillkruss Feb 10, 2022
afd077c
dump ci tests
tillkruss Feb 11, 2022
9135d33
inital tests
tillkruss Feb 11, 2022
eec2d40
gte
tillkruss Feb 11, 2022
38c1767
add alloptions tests
tillkruss Feb 11, 2022
aca709a
only run check in production
tillkruss Feb 17, 2022
80d8a8c
added missing since tags
tillkruss Feb 17, 2022
25e040c
document return values
tillkruss Feb 17, 2022
2e38169
update module name; rename slug
tillkruss Feb 17, 2022
831c85c
use long slug
tillkruss Feb 17, 2022
c992a7c
prefix functions with `perflab_oc_*`
tillkruss Feb 17, 2022
3502b28
prefix all filters
tillkruss Feb 17, 2022
5440969
use prefixed names in tests
tillkruss Feb 17, 2022
db8994f
fix test
tillkruss Feb 17, 2022
a83ae10
Update modules/object-cache/persistent-object-cache-health-check/load…
tillkruss Feb 26, 2022
fb4e749
Update modules/object-cache/persistent-object-cache-health-check/load…
tillkruss Feb 26, 2022
fd07b49
Update modules/object-cache/persistent-object-cache-health-check/load…
tillkruss Feb 26, 2022
873dc12
simplify filter logic
tillkruss Feb 28, 2022
1ea68fd
update tests
tillkruss Feb 28, 2022
ea06461
spacing
tillkruss Feb 28, 2022
badede0
Update modules/object-cache/persistent-object-cache-health-check/load…
tillkruss Feb 28, 2022
6291513
simplify tests
tillkruss Feb 28, 2022
0390c3a
formatting
tillkruss Feb 28, 2022
bbf79d8
remove useless vars from tests
tillkruss Feb 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
246 changes: 246 additions & 0 deletions modules/object-cache/persistent-object-cache-health-check/load.php
@@ -0,0 +1,246 @@
<?php
/**
* Module Name: Persistent Object Cache Health Check
* Description: Recommend a persistent object cache for sites with non-trivial amounts of data.
* Experimental: No
*
* @package performance-lab
* @since 1.0.0
*/

/**
* Adds a health check testing for and suggesting a persistent object cache backend.
*
* @since 1.0.0
*
* @param array $tests An associative array of direct and asynchronous tests.
* @return array An associative array of direct and asynchronous tests.
*/
function perflab_oc_health_add_tests( $tests ) {
if ( wp_get_environment_type() === 'production' ) {
$tests['direct']['persistent_object_cache'] = array(
'label' => 'persistent_object_cache',
'test' => 'perflab_oc_health_persistent_object_cache',
);
}

return $tests;
}
add_filter( 'site_status_tests', 'perflab_oc_health_add_tests' );

/**
* Callback for `persistent_object_cache` health check.
*
* @since 1.0.0
*
* @return array The health check result suggesting persistent object caching.
*/
function perflab_oc_health_persistent_object_cache() {
/**
* Filter the action URL for the persistent object cache health check.
*
* @since 1.0.0
*
* @param string $action_url Learn more link for persistent object cache health check.
*/
$action_url = apply_filters(
'perflab_oc_site_status_persistent_object_cache_url',
/* translators: Localized Support reference. */
__( 'https://wordpress.org/support/article/optimization/#object-caching', 'performance-lab' )
);

$result = array(
'test' => 'persistent_object_cache',
'status' => 'good',
'badge' => array(
'label' => __( 'Performance', 'performance-lab' ),
'color' => 'blue',
),
'label' => __( 'A persistent object cache is being used', 'performance-lab' ),
'description' => sprintf(
'<p>%s</p>',
__( "WordPress performs at its best when a persistent object cache is used. A persistent object cache helps to reduce load on your SQL server significantly and allows WordPress to retrieve your site's content and settings much faster.", 'performance-lab' )
),
'actions' => sprintf(
'<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
esc_url( $action_url ),
__( 'Learn more about persistent object caching.', 'performance-lab' ),
/* translators: Accessibility text. */
__( '(opens in a new tab)', 'performance-lab' )
),
);

if ( wp_using_ext_object_cache() ) {
return $result;
}

if ( ! perflab_oc_health_should_persistent_object_cache() ) {
$result['label'] = __( 'A persistent object cache is not required', 'performance-lab' );

return $result;
}

$available_services = perflab_oc_health_available_object_cache_services();

$notes = __( 'Speak to your web host about what persistent object caches are available and how to enable them.', 'performance-lab' );

if ( ! empty( $available_services ) ) {
$notes .= ' ' . sprintf(
/* translators: Available object caching services. */
__( 'Your host appears to support the following object caching services: %s.', 'performance-lab' ),
implode( ', ', $available_services )
);
}

/**
* Filter the second paragraph of the health check's description
* when suggesting the use of a persistent object cache.
*
* Hosts may want to replace the notes to recommend their preferred object caching solution.
*
* Plugin authors may want to append notes (not replace) on why object caching is recommended for their plugin.
*
* @since 1.0.0
*
* @param string $notes The notes appended to the health check description.
* @param array $available_services The list of available persistent object cache services.
*/
$notes = apply_filters( 'perflab_oc_site_status_persistent_object_cache_notes', $notes, $available_services );

$result['status'] = 'recommended';
$result['label'] = __( 'You should use a persistent object cache', 'performance-lab' );
$result['badge']['color'] = 'orange';
$result['description'] .= sprintf(
'<p>%s</p>',
wp_kses(
$notes,
array(
'a' => array( 'href' => true ),
'code' => true,
'em' => true,
'strong' => true,
)
)
);

return $result;
}

/**
* Determines whether to suggest using a persistent object cache.
*
* @since 1.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @return bool Whether to suggest using a persistent object cache.
*/
function perflab_oc_health_should_persistent_object_cache() {
global $wpdb;

/**
* Filter to force suggestion to use a persistent object cache and bypass threshold checks.
*
* @since 1.0.0
*
* @param bool $suggest Whether to suggest using a persistent object cache.
*/
if ( apply_filters( 'perflab_oc_site_status_suggest_persistent_object_cache', false ) ) {
return true;
}

/**
* Filter the thresholds used to determine whether to suggest the use of a persistent object cache.
*
* @since 1.0.0
*
* @param array $thresholds The list of threshold names and numbers.
*/
$thresholds = apply_filters(
'perflab_oc_site_status_persistent_object_cache_thresholds',
array(
'alloptions_count' => 500,
'alloptions_bytes' => 100000,
'comments_count' => 1000,
'options_count' => 1000,
'posts_count' => 1000,
'terms_count' => 1000,
'users_count' => 1000,
)
);

$alloptions = wp_load_alloptions();

if ( $thresholds['alloptions_count'] < count( $alloptions ) ) {
return true;
}

if ( $thresholds['alloptions_bytes'] < strlen( serialize( $alloptions ) ) ) {
return true;
}

$table_names = implode( "','", array( $wpdb->comments, $wpdb->options, $wpdb->posts, $wpdb->terms, $wpdb->users ) );

// With InnoDB the `TABLE_ROWS` are estimates, which are accurate enough and faster to retrieve than individual `COUNT()` queries.
$results = $wpdb->get_results(
$wpdb->prepare(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT TABLE_NAME AS 'table', TABLE_ROWS AS 'rows', SUM(data_length + index_length) as 'bytes'
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = %s
AND TABLE_NAME IN ('$table_names')
GROUP BY TABLE_NAME;",
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
DB_NAME
),
OBJECT_K
);

$threshold_map = array(
'comments_count' => $wpdb->comments,
'options_count' => $wpdb->options,
'posts_count' => $wpdb->posts,
'terms_count' => $wpdb->terms,
'users_count' => $wpdb->users,
);

foreach ( $threshold_map as $threshold => $table ) {
if ( $thresholds[ $threshold ] <= $results[ $table ]->rows ) {
return true;
}
}

return false;
}

/**
* Returns a list of available persistent object cache services.
*
* @since 1.0.0
*
* @return array The list of available persistent object cache services.
*/
function perflab_oc_health_available_object_cache_services() {
$extensions = array_map(
'extension_loaded',
array(
'APCu' => 'apcu',
'Redis' => 'redis',
'Relay' => 'relay',
'Memcached' => 'memcache', // The `memcached` extension seems unmaintained.
)
);

$services = array_keys( array_filter( $extensions ) );

/**
* Filter the persistent object cache services available to the user.
*
* This can be useful to hide or add services not included in the defaults.
*
* @since 1.0.0
*
* @param array $services The list of available persistent object cache services.
*/
return apply_filters( 'perflab_oc_site_status_available_object_cache_services', $services );
}
@@ -0,0 +1,52 @@
<?php
/**
* Tests for persistent-object-cache-health-check module.
*
* @package performance-lab
* @group persistent-object-cache-health-check
*/

class Object_Cache_Health_Check_Tests extends WP_UnitTestCase {

function test_object_cache_default_thresholds() {
$this->assertFalse(
perflab_oc_health_should_persistent_object_cache()
);
}

function test_object_cache_thresholds_check_can_be_bypassed() {
add_filter( 'perflab_oc_site_status_suggest_persistent_object_cache', '__return_true' );

$this->assertTrue(
perflab_oc_health_should_persistent_object_cache()
);
}

/**
* @dataProvider thresholds
*/
function test_object_cache_thresholds( $threshold, $count ) {
add_filter(
'perflab_oc_site_status_persistent_object_cache_thresholds',
function ( $thresholds ) use ( $threshold, $count ) {
return array_merge( $thresholds, array( $threshold => $count ) );
}
);

$this->assertTrue(
perflab_oc_health_should_persistent_object_cache()
);
}

function thresholds() {
return array(
array( 'comments_count', 0 ),
array( 'posts_count', 0 ),
array( 'terms_count', 1 ),
array( 'options_count', 100 ),
array( 'users_count', 0 ),
array( 'alloptions_count', 100 ),
array( 'alloptions_bytes', 1000 ),
);
}
}