plugin-update-checker icon indicating copy to clipboard operation
plugin-update-checker copied to clipboard

PUC slows down WP admin with a few plugins hosted on BitBucket

Open hirasso opened this issue 5 years ago • 18 comments

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!

hirasso avatar Jul 14 '20 14:07 hirasso

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.

YahnisElsts avatar Jul 14 '20 15:07 YahnisElsts

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:

image

Just wanted to make sure if there would be optimizations possible inside PUC, before jumping to any premature optimizations on my side ;)

hirasso avatar Jul 15 '20 10:07 hirasso

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.

YahnisElsts avatar Jul 15 '20 15:07 YahnisElsts

OK, I installed Query Monitor and reduced the plugins checking for updates to one. It seems like PUC is making 4 requests:

image

(blacked out parts of the URLs to keep my BitBucket customer keys secret 😉 )

hirasso avatar Jul 16 '20 11:07 hirasso

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
**/

hirasso avatar Jul 16 '20 11:07 hirasso

Just tested one of my plugins with a changelog.md (rh-admin-utils):

image

Here it is 5 requests, the last one to changelog.md.

hirasso avatar Jul 16 '20 11:07 hirasso

...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):

image

hirasso avatar Jul 16 '20 12:07 hirasso

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/tags request.
  • Since it doesn't find any updates with the first two requests, it falls back to using the master branch.
  • It downloads the plugin file to get the new version number. That's the /master/rh-updater.php request.
  • Finally, if the installed version has a readme.txt and/or a changelog.md file, PUC also downloads those files to get plugin information like the last tested WP version, required version, changelog, and so on.

YahnisElsts avatar Jul 16 '20 14:07 YahnisElsts

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`.

hirasso avatar Jul 16 '20 18:07 hirasso

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;
});

hirasso avatar Jul 16 '20 18:07 hirasso

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.

hirasso avatar Jul 17 '20 11:07 hirasso

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?

YahnisElsts avatar Jul 17 '20 14:07 YahnisElsts

Yes, utility functions, a fifth argument or even a filter for each request would be great!

hirasso avatar Jul 17 '20 15:07 hirasso

Hey there! Just going through my open issues - has there been any progress on this?

hirasso avatar Aug 21 '22 18:08 hirasso

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;
});

hirasso avatar Aug 23 '22 12:08 hirasso

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_release
  • latest_tag
  • stable_tag
  • branch (uses the configured branch; defaults to master)

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 avatar Sep 06 '22 12:09 YahnisElsts

@YahnisElsts wow, nice!! I will try this out and let you know. Thank you!

hirasso avatar Sep 09 '22 12:09 hirasso

Works great! 🍰 Looking forward to the next release, so that I can start using it in production :)

hirasso avatar Sep 14 '22 11:09 hirasso

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.

YahnisElsts avatar Oct 16 '22 16:10 YahnisElsts

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
);

hirasso avatar Oct 28 '22 09:10 hirasso

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.

YahnisElsts avatar Oct 28 '22 10:10 YahnisElsts

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.

hirasso avatar Oct 28 '22 10:10 hirasso

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?

YahnisElsts avatar Oct 28 '22 11:10 YahnisElsts

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.

hirasso avatar Oct 28 '22 17:10 hirasso

...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).

hirasso avatar Oct 28 '22 17:10 hirasso

...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.

YahnisElsts avatar Oct 28 '22 17:10 YahnisElsts

Yes, that works now, great! And the new filter also has it's effect. PUC even displays a notice if I unset all $strategies 💯

hirasso avatar Oct 28 '22 17:10 hirasso

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.

YahnisElsts avatar Oct 28 '22 18:10 YahnisElsts

The new version has now been released, so I'm going to close this as completed.

YahnisElsts avatar Nov 02 '22 16:11 YahnisElsts

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;
});

davidperezgar avatar Nov 18 '22 12:11 davidperezgar