PUC slows down WP admin with a few plugins hosted on BitBucket
Hi there,
first of all, great work on Plugin Update Checker!
I'm hosting all my custom plugins on a Bitbucket account and pull in updates from there. I noticed though, that it heavily slows down my admin dashboard with around 5 custom plugins, each using PUC. Is there a way to control the interval of update checks, or only let it run if I visit plugins.php? Or do you have any other idea on how to optimize this further?
Thanks!
Do you have any ideas about which part of the update checker could be causing the performance issues? Is the actual update check, or something else? I would recommend doing some profiling with XDebug or a similar tool if possible.
The default check interval is 12 hours. PUC also checks more often on specific pages (e.g. Dashboard -> Updates). You can change the interval by passing the desired interval (in hours) as the fourth argument to buildUpdateChecker. For example:
$myUpdateChecker = Puc_v4_Factory::buildUpdateChecker(
'https://example.com/update.json',
__FILE__,
'unique-plugin-or-theme-slug',
48 // <-- hours
);
You can disable automatic checks by setting this argument to 0. If you do, you'll need to modify your plugin to periodically call $myUpdateChecker->checkForUpdates() to check for updates.
You can also use the puc_check_now-$slug filter to cancel individual update checks. Return boolean false from the filter callback to abort the current check.
Another, more advanced, alternative would be to create a custom subclasses of Puc_v4p9_Scheduler and Puc_v4p9_Vcs_PluginUpdateChecker. This way you could fully control the scheduler behaviour without having to reimplement it from scratch.
Wow @YahnisElsts , thanks for your fast and detailed response!
I don't have any experience with XDebug, but just disabling the automatic updates for PUC by setting the fourth argument to 0 and testing the site once without a call to checkForUpdates() and once with it, I get a load speed difference of 21.35 (!) seconds. Here is a screenshot of my dev tools, with Preserve log set to true. The first request to wp-admin/ takes 1.18s, the second one (with the update check enabled) takes 22.53s:

Just wanted to make sure if there would be optimizations possible inside PUC, before jumping to any premature optimizations on my side ;)
That's a huge difference. If you're interested in investigating this further, I'd be curious to see if the slowdown is just due to a few slow network requests or some kind of a problem with PUC itself (e.g. maybe it's making duplicate BitBucket API requests or something).
You can use the Query Monitor plugin to view HTTP requests made during the current page load. Query Monitor also provides some other performance statistics, though it's not as advanced as a "real" profiler.
OK, I installed Query Monitor and reduced the plugins checking for updates to one. It seems like PUC is making 4 requests:

(blacked out parts of the URLs to keep my BitBucket customer keys secret 😉 )
Maybe some more context about my setup: None of my plugins have a readme.txt file, some have a changelog.md, some don't have any more information than inside the main plugin file. In the case of the plugin I used for testing:
/**
* Plugin Name: RH Updater
* Version: 1.0.5
* Author: Rasso Hilber
* Description: Manage custom plugin updates
* Author URI: https://rassohilber.com
**/
Just tested one of my plugins with a changelog.md (rh-admin-utils):

Here it is 5 requests, the last one to changelog.md.
...sorry for bombarding you with information, but maybe this also helps: I just added a readme.txt to one of the plugins. Now the 404 for that is gone, but it still is 5 requests (3.3595 seconds):

Those requests look fine to me. They're a little slower than I would expect, but other than that PUC seems to be working as designed. It checks a few different places when searching for updates:
- It looks for a "Stable tag: xyz" header in the readme. That's the first request.
- It looks for recent tags that look like version numbers. That's the
/refs/tagsrequest. - Since it doesn't find any updates with the first two requests, it falls back to using the
masterbranch. - It downloads the plugin file to get the new version number. That's the
/master/rh-updater.phprequest. - Finally, if the installed version has a
readme.txtand/or achangelog.mdfile, PUC also downloads those files to get plugin information like the last tested WP version, required version, changelog, and so on.
OK, thanks for your explanation. So the time of 23 seconds for around 8 plugins is not optimizable, right? If so, I will probably add a filter to puc_check_now-$slug to only run on certain admin pages, e.g. plugins.php, 'update.phpandupdate-core.php`.
Added this to my code. It will cancel update checks if not on certain WP admin pages.
add_filter("puc_check_now-$plugin_slug", function($shouldCheck) {
global $pagenow;
if( !in_array($pagenow, ['plugins.php', 'update-core.php', 'update.php']) ) return false;
return $shouldCheck;
});
Hi @YahnisElsts , after fixing this the brute force way like in my comment above, I'm now thinking about how I could optimize my plugins so that PUC would need the fewest possible API requests to figure out if there is an update available for a plugin. Are there any setups where PUC would only check for the main plugin file and a changelog.md? Or do you have any recommendations on this?
If not, I can imagine this would be a great enhancement.
As fas as I can see, there's no way to do that within a plugin. That sort of optimization would require making some changes to PUC itself.
I suppose it would be useful to have a way to turn off the "look for the stable tag header" and "look for tags" steps of the update checking process. I'm not sure what the best approach would be. Utility methods like disableTags/toggleTags? More constructor arguments? Something else?
Yes, utility functions, a fifth argument or even a filter for each request would be great!
Hey there! Just going through my open issues - has there been any progress on this?
I suppose it would be useful to have a way to turn off the "look for the stable tag header" and "look for tags" steps of the update checking process. I'm not sure what the best approach would be. Utility methods like disableTags/toggleTags? More constructor arguments? Something else?
Either utilitity methods or filters would be great. Something like this:
add_filter('puc/look_for_stable_tag', function(bool $look, string $slug): bool {
if( $slug === $my_plugin_slug ) return false;
return $look;
});
Thank you for the reminder. I finally got around to this today.
I've committed a change that lets you do something like this:
$bitbucketPluginChecker->addFilter('vcs_update_detection_strategies', function($strategies) {
//Don't look for a "Stable tag" header.
unset($strategies['stable_tag']);
//Don't scan the latest tags either.
unset($strategies['latest_tag']);
return $strategies;
});
The addFilter utility method automatically handles things like adding the $slug suffix to the filter name.
Available strategy keys currently include:
latest_releaselatest_tagstable_tagbranch(uses the configured branch; defaults tomaster)
These are also available as constants on the Puc_v4p13_Vcs_Api class, but I would not recommend using those right now because the class name will probably change in the next release. Also, not every API implementation supports every strategy.
Note: Minimum required PHP version has been increased to 5.4 to make this change easier to implement. It's likely that it will be increased further to at least 5.6 soon.
@YahnisElsts wow, nice!! I will try this out and let you know. Thank you!
Works great! 🍰 Looking forward to the next release, so that I can start using it in production :)
The next release is almost ready. If you have the time, please give it another try - it will help verify that I didn't miss anything while adding namespaces and changing class names. The code is in the ascend-to-5.6 branch.
The update checker initialization code will be slightly different in this version. See the readme in the branch for some more details.
I just tried to test it. I am getting this error:
Fatal error: Uncaught Error: Class "YahnisElsts\PluginUpdateChecker\v5p0\Vcs\PluginUpdateChecker" not found in /[...]/vendor/yahnis-elsts/plugin-update-checker/Puc/v5p0/PucFactory.php:135
This is the (relevant) code I'm using to initialize PUC:
use YahnisElsts\PluginUpdateChecker\v5\PucFactory;
$update_checker = PucFactory::buildUpdateChecker(
"https://bitbucket.org/rhilber/$plugin_slug", // Metadata URL.
$file_path, // Full path to the main plugin file.
$plugin_slug, // Plugin slug. Usually it's the same as the name of the directory.
24*7 // <-- update interval in hours
);
The same basic code seems to work fine for me (to actually run it, I changed $plugin_slug to test-slug and $file_path to __FILE__). If you're using Composer, you may need to regenerate the autoloader since the file names and class names have changed. Alternatively, you could include plugin-update-checker.php directly.
Hmm. Maybe more details will help?
I deactivated composer and installed PUC from the branch ascend-to-5.6 like this:
git clone https://github.com/YahnisElsts/plugin-update-checker --branch ascend-to-5.6
Then I required PUC directly like this:
require_once(__DIR__ . '/plugin-update-checker/plugin-update-checker.php');
use YahnisElsts\PluginUpdateChecker\v5\PucFactory;
$update_checker = PucFactory::buildUpdateChecker(
"https://bitbucket.org/rhilber/$plugin_slug", // Metadata URL.
$file_path, // Full path to the main plugin file.
$plugin_slug, // Plugin slug. Usually it's the same as the name of the directory.
24*7 // <-- update interval in hours
);
The branch of PUC:
❯ git branch
* ascend-to-5.6
The error stays the same as above:
Fatal error: Uncaught Error: Class "YahnisElsts\PluginUpdateChecker\v5p0\Vcs\PluginUpdateChecker" not found in /[...]/Puc/v5p0/PucFactory.php:135
If I search for YahnisElsts\PluginUpdateChecker\v5p0\Vcs\PluginUpdateChecker in the source of PUC, I can only find it in the README.md.
I'm running the test on PHP 8.0.8.
The class is defined in /Puc/v5p0/Vcs/PluginUpdateChecker.php. It is registered to the factory in load-v5p0.php. At a glance, it seems like namespace resolution should work fine. I tested it on PHP 8.1.6.
The PUC autoloader is in /Puc/v5p0/Autoloader.php. Can you see if it's being triggered for this class name?
So, I went through the Autloader class. When do var_dump($path) just after this line, I get this:
/[...]/plugin-update-checker/Puc/v5p0/Vcs\PluginUpdateChecker.php
Note the backslash in the path, directly after Vcs? This will be the issue I guess :)
I'm on a MAC by the way.
...can confirm that this must be the issue. Adding this:
if ( strpos($className, $this->prefix) === 0 ) {
$path = substr($className, strlen($this->prefix));
$path = str_replace('_', '/', $path);
$path = str_replace('\\', '/', $path); // replace backslashes with forward slashes
$path = $this->rootDir . $path . '.php';
if ( file_exists($path) ) {
include $path;
}
}
Makes it work on my machine. Actually, using forward slashes should be fine cross-OS, as described here (PHP will take care of converting forward slashes to the right format).
...and I have only tested this version on Windows so far, so it didn't cause any problems for me.
Please try again with the patch that I committed just now.
Yes, that works now, great! And the new filter also has it's effect. PUC even displays a notice if I unset all $strategies 💯
Sounds good. I think I'll wait until next week to release the new version, just in case there are any unidentified bugs - wouldn't want to spend the weekend on debugging.
The new version has now been released, so I'm going to close this as completed.
Nice @YahnisElsts. The VCS strategies look very good. What I'm wondering the one that I use is the plugin header version. What should I remove then? It's not very clear what is the one with Plugin header:
- STRATEGY_LATEST_RELEASE
- STRATEGY_LATEST_TAG
- STRATEGY_STABLE_TAG
- STRATEGY_BRANCH
I believe that I should remove all of the strategies, but keep this one: STRATEGY_LATEST_TAG. Is it correct?
the code would be like this one:
use YahnisElsts\PluginUpdateChecker\v5p0\Vcs\GitHubApi;
$bitbucketPluginChecker->addFilter('vcs_update_detection_strategies', function( $strategies ) {
//Don't look for a "Stable tag" header in readme.txt.
unset( $strategies[ GitHubApi::STRATEGY_BRANCH ] );
unset( $strategies[ GitHubApi::STRATEGY_STABLE_TAG ] );
unset( $strategies[ GitHubApi::STRATEGY_LATEST_RELEASE ] );
return $strategies;
});