From b9d7b665326805e6034fd9d4b7f2a609cc245b44 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 5 Mar 2025 11:05:30 +0000 Subject: [PATCH 1/8] Collect memcache stats only when memcache_admin is installed and configure to show stats --- memcache.services.yml | 2 ++ memcache_admin/memcache_admin.services.yml | 1 + .../MemcacheAdminSubscriber.php | 29 ++++++++++++++++++- .../src/MemcacheAdminServiceProvider.php | 24 +++++++++++++++ src/Driver/DriverBase.php | 28 +----------------- 5 files changed, 56 insertions(+), 28 deletions(-) create mode 100644 memcache_admin/src/MemcacheAdminServiceProvider.php diff --git a/memcache.services.yml b/memcache.services.yml index d523f10..c4a434c 100644 --- a/memcache.services.yml +++ b/memcache.services.yml @@ -1,3 +1,5 @@ +parameters: + memcache.stats_collect: false services: memcache.settings: class: Drupal\memcache\MemcacheSettings diff --git a/memcache_admin/memcache_admin.services.yml b/memcache_admin/memcache_admin.services.yml index 2f01435..7b8f81b 100644 --- a/memcache_admin/memcache_admin.services.yml +++ b/memcache_admin/memcache_admin.services.yml @@ -6,6 +6,7 @@ services: - '@config.factory' - '@current_user' - '@renderer' + - '@kernel' tags: - { name: event_subscriber } memcache_stats.memcached: diff --git a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php index 7a01975..5c0266e 100644 --- a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php +++ b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php @@ -3,7 +3,10 @@ namespace Drupal\memcache_admin\EventSubscriber; use Drupal\Component\Render\HtmlEscapedText; +use Drupal\Core\Config\ConfigCrudEvent; +use Drupal\Core\Config\ConfigEvents; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\DrupalKernelInterface; use Drupal\Core\Render\HtmlResponse; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Session\AccountInterface; @@ -47,6 +50,13 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface { */ protected $renderer; + /** + * The kernel. + * + * @var \Drupal\Core\DrupalKernelInterface + */ + protected $kernel; + /** * MemcacheAdminSubscriber constructor. * @@ -58,12 +68,15 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface { * The current user. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer service. + * @param \Drupal\Core\DrupalKernelInterface $kernel + * The kernel. */ - public function __construct(MemcacheDriverFactory $memcache_factory, ConfigFactoryInterface $config_factory, AccountInterface $current_user, RendererInterface $renderer) { + public function __construct(MemcacheDriverFactory $memcache_factory, ConfigFactoryInterface $config_factory, AccountInterface $current_user, RendererInterface $renderer, DrupalKernelInterface $kernel) { $this->memcacheFactory = $memcache_factory; $this->configFactory = $config_factory; $this->currentUser = $current_user; $this->renderer = $renderer; + $this->kernel = $kernel; } /** @@ -72,6 +85,7 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { $events = []; $events[KernelEvents::RESPONSE][] = ['displayStatistics']; + $events[ConfigEvents::SAVE][] = ['onConfigSave', 0]; return $events; } @@ -166,6 +180,19 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface { } } + /** + * Causes the container to be rebuilt on the next request, if necessary. + * + * @param \Drupal\Core\Config\ConfigCrudEvent $event + * The configuration event. + */ + public function onConfigSave(ConfigCrudEvent $event) { + $saved_config = $event->getConfig(); + if ($saved_config->getName() == 'memcache_admin.settings' && $event->isChanged('show_memcache_statistics')) { + $this->kernel->invalidateContainer(); + } + } + /** * Helper function. Calculate a percentage. */ diff --git a/memcache_admin/src/MemcacheAdminServiceProvider.php b/memcache_admin/src/MemcacheAdminServiceProvider.php new file mode 100644 index 0000000..bef80ab --- /dev/null +++ b/memcache_admin/src/MemcacheAdminServiceProvider.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\memcache_admin; + +use Drupal\Core\Config\BootstrapConfigStorageFactory; +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\DependencyInjection\ServiceModifierInterface; + +/** + * Enables memcache stats collection based on configuration. + */ +class MemcacheAdminServiceProvider implements ServiceModifierInterface { + + /** + * {@inheritdoc} + */ + public function alter(ContainerBuilder $container) { + $config_storage = BootstrapConfigStorageFactory::get(); + $config = $config_storage->read('memcache_admin.settings'); + // Determine whether stats should be collected using configuration. + $container->setParameter('memcache.stats_collect', $config['show_memcache_statistics'] ?? FALSE); + } + +} diff --git a/src/Driver/DriverBase.php b/src/Driver/DriverBase.php index 7e9b6d0..de6e540 100644 --- a/src/Driver/DriverBase.php +++ b/src/Driver/DriverBase.php @@ -302,33 +302,7 @@ abstract class DriverBase implements DrupalMemcacheInterface { * Helper function to initialize the stats for a memcache operation. */ protected function statsInit() { - static $drupal_static_fast; - - if (!isset($drupal_static_fast)) { - // @phpcs:ignore - $drupal_static_fast = &drupal_static(__FUNCTION__, ['variable_checked' => NULL, 'user_access_checked' => NULL]); - } - $variable_checked = &$drupal_static_fast['variable_checked']; - $user_access_checked = &$drupal_static_fast['user_access_checked']; - - // Confirm DRUPAL_BOOTSTRAP_VARIABLES has been reached. We don't use - // drupal_get_bootstrap_phase() as it's buggy. We can use variable_get() - // here because _drupal_bootstrap_variables() includes module.inc - // immediately after it calls variable_initialize(). - // @codingStandardsIgnoreStart - // if (!isset($variable_checked) && function_exists('module_list')) { - // $variable_checked = variable_get('show_memcache_statistics', FALSE); - // } - // If statistics are enabled we need to check user access. - // if (!empty($variable_checked) && !isset($user_access_checked) && !empty($GLOBALS['user']) && function_exists('user_access')) { - // // Statistics are enabled and the $user object has been populated, so check - // // that the user has access to view them. - // $user_access_checked = user_access('access memcache statistics'); - // } - // @codingStandardsIgnoreEnd - // Return whether or not statistics are enabled and the user can access - // them. - if ((!isset($variable_checked) || $variable_checked) && (!isset($user_access_checked) || $user_access_checked)) { + if (\Drupal::getContainer()->getParameter('memcache.stats_collect')) { Timer::start('dmemcache'); return TRUE; } -- GitLab From 55b80f92f8093755250ae71a5bf265dba050663f Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 5 Mar 2025 11:57:01 +0000 Subject: [PATCH 2/8] Disable stat collection if user does not have access --- memcache.services.yml | 2 +- memcache_admin/memcache_admin.services.yml | 2 +- .../MemcacheAdminSubscriber.php | 173 ++++++++++-------- src/Driver/DriverBase.php | 29 ++- src/Driver/MemcacheDriverFactory.php | 16 +- 5 files changed, 132 insertions(+), 90 deletions(-) diff --git a/memcache.services.yml b/memcache.services.yml index c4a434c..2cb1ef7 100644 --- a/memcache.services.yml +++ b/memcache.services.yml @@ -6,7 +6,7 @@ services: arguments: ['@settings'] memcache.factory: class: Drupal\memcache\Driver\MemcacheDriverFactory - arguments: ['@memcache.settings'] + arguments: ['@memcache.settings', '%memcache.stats_collect%'] memcache.timestamp.invalidator.bin: class: Drupal\memcache\Invalidator\MemcacheTimestampInvalidator # Override this service and adjust tolerance if necessary. diff --git a/memcache_admin/memcache_admin.services.yml b/memcache_admin/memcache_admin.services.yml index 7b8f81b..ce5f7de 100644 --- a/memcache_admin/memcache_admin.services.yml +++ b/memcache_admin/memcache_admin.services.yml @@ -3,7 +3,7 @@ services: class: Drupal\memcache_admin\EventSubscriber\MemcacheAdminSubscriber arguments: - '@memcache.factory' - - '@config.factory' + - '%memcache.stats_collect%' - '@current_user' - '@renderer' - '@kernel' diff --git a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php index 5c0266e..250f21c 100644 --- a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php +++ b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php @@ -13,6 +13,7 @@ use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\memcache\Driver\MemcacheDriverFactory; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -30,11 +31,11 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface { protected $memcacheFactory; /** - * The config factory service. + * Indicates if memcache is collecting stats. * - * @var \Drupal\Core\Config\ConfigFactoryInterface + * @var bool */ - protected $configFactory; + protected bool $statsEnabled; /** * The current user. @@ -62,8 +63,8 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface { * * @param \Drupal\memcache\Driver\MemcacheDriverFactory $memcache_factory * The memcache factory service. - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The config factory service. + * @param bool $stats_enabled + * The 'memcache.stats_collect' container parameter. * @param \Drupal\Core\Session\AccountInterface $current_user * The current user. * @param \Drupal\Core\Render\RendererInterface $renderer @@ -71,9 +72,9 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface { * @param \Drupal\Core\DrupalKernelInterface $kernel * The kernel. */ - public function __construct(MemcacheDriverFactory $memcache_factory, ConfigFactoryInterface $config_factory, AccountInterface $current_user, RendererInterface $renderer, DrupalKernelInterface $kernel) { + public function __construct(MemcacheDriverFactory $memcache_factory, bool $stats_enabled, AccountInterface $current_user, RendererInterface $renderer, DrupalKernelInterface $kernel) { $this->memcacheFactory = $memcache_factory; - $this->configFactory = $config_factory; + $this->statsEnabled = $stats_enabled; $this->currentUser = $current_user; $this->renderer = $renderer; $this->kernel = $kernel; @@ -84,96 +85,108 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface { */ public static function getSubscribedEvents(): array { $events = []; + // Needs to come after user authentication. + $events[KernelEvents::REQUEST][] = ['disableStatisticsIfNoPermission', 299]; $events[KernelEvents::RESPONSE][] = ['displayStatistics']; $events[ConfigEvents::SAVE][] = ['onConfigSave', 0]; return $events; } + /** + * Disables statistics if user does not have permission. + */ + public function disableStatisticsIfNoPermission(RequestEvent $event) { + if (!$this->statsEnabled) { + return; + } + if (!$this->currentUser->hasPermission('access memcache statistics')) { + $memcache = $this->memcacheFactory->get(NULL, TRUE); + $memcache->disableStats(); + } + } + /** * Display statistics on page. */ public function displayStatistics(ResponseEvent $event) { - $user = $this->currentUser; - if ($user->id() == 0) { - // Suppress for the above criteria. + if (!$this->statsEnabled) { + return; } - else { - $response = $event->getResponse(); - - // Don't call theme() during shutdown if the registry has been rebuilt - // (such as when enabling/disabling modules on admin/build/modules) as - // things break. - // Instead, simply exit without displaying admin statistics for this page - // load. See http://drupal.org/node/616282 for discussion. - // @todo make sure this is not still a requirement. - // @codingStandardsIgnoreStart - // if (!function_exists('theme_get_registry') || !theme_get_registry()) { - // return; - // }. - // @codingStandardsIgnoreEnd - // Try not to break non-HTML pages. - if ($response instanceof HtmlResponse) { - - // This should only apply to page content. - if (stripos((string) $response->headers->get('content-type'), 'text/html') !== FALSE) { - $show_stats = $this->configFactory->get('memcache_admin.settings')->get('show_memcache_statistics'); - if ($show_stats && $user->hasPermission('access memcache statistics')) { - $output = ''; - - $memcache = $this->memcacheFactory->get(NULL, TRUE); - $memcache_stats = $memcache->requestStats(); - if (!empty($memcache_stats['ops'])) { - foreach ($memcache_stats['ops'] as $row => $stats) { - $memcache_stats['ops'][$row][0] = new HtmlEscapedText($stats[0]); - $memcache_stats['ops'][$row][1] = number_format($stats[1], 2); - $hits = number_format($this->statsPercent($stats[2], $stats[3]), 1); - $misses = number_format($this->statsPercent($stats[3], $stats[2]), 1); - $memcache_stats['ops'][$row][2] = number_format($stats[2]) . " ($hits%)"; - $memcache_stats['ops'][$row][3] = number_format($stats[3]) . " ($misses%)"; - } - $build = [ - '#theme' => 'table', - '#header' => [ - $this->t('operation'), - $this->t('total ms'), - $this->t('total hits'), - $this->t('total misses'), - ], - '#rows' => $memcache_stats['ops'], - ]; - $output .= $this->renderer->renderRoot($build); + $response = $event->getResponse(); + + // Don't call theme() during shutdown if the registry has been rebuilt + // (such as when enabling/disabling modules on admin/build/modules) as + // things break. + // Instead, simply exit without displaying admin statistics for this page + // load. See http://drupal.org/node/616282 for discussion. + // @todo make sure this is not still a requirement. + // @codingStandardsIgnoreStart + // if (!function_exists('theme_get_registry') || !theme_get_registry()) { + // return; + // }. + // @codingStandardsIgnoreEnd + // Try not to break non-HTML pages. + if ($response instanceof HtmlResponse) { + + // This should only apply to page content. + if (stripos((string) $response->headers->get('content-type'), 'text/html') !== FALSE) { + if ($this->currentUser->hasPermission('access memcache statistics')) { + $output = ''; + + $memcache = $this->memcacheFactory->get(NULL, TRUE); + $memcache_stats = $memcache->requestStats(); + if (!empty($memcache_stats['ops'])) { + foreach ($memcache_stats['ops'] as $row => $stats) { + $memcache_stats['ops'][$row][0] = new HtmlEscapedText($stats[0]); + $memcache_stats['ops'][$row][1] = number_format($stats[1], 2); + $hits = number_format($this->statsPercent($stats[2], $stats[3]), 1); + $misses = number_format($this->statsPercent($stats[3], $stats[2]), 1); + $memcache_stats['ops'][$row][2] = number_format($stats[2]) . " ($hits%)"; + $memcache_stats['ops'][$row][3] = number_format($stats[3]) . " ($misses%)"; } - if (!empty($memcache_stats['all'])) { - $build = [ - '#type' => 'table', - '#header' => [ - $this->t('ms'), - $this->t('operation'), - $this->t('bin'), - $this->t('key'), - $this->t('status'), - ], + $build = [ + '#theme' => 'table', + '#header' => [ + $this->t('operation'), + $this->t('total ms'), + $this->t('total hits'), + $this->t('total misses'), + ], + '#rows' => $memcache_stats['ops'], + ]; + $output .= $this->renderer->renderRoot($build); + } + + if (!empty($memcache_stats['all'])) { + $build = [ + '#type' => 'table', + '#header' => [ + $this->t('ms'), + $this->t('operation'), + $this->t('bin'), + $this->t('key'), + $this->t('status'), + ], + ]; + foreach ($memcache_stats['all'] as $row => $stats) { + $build[$row]['ms'] = ['#plain_text' => $stats[0]]; + $build[$row]['operation'] = ['#plain_text' => $stats[1]]; + $build[$row]['bin'] = ['#plain_text' => $stats[2]]; + $build[$row]['key'] = [ + '#separator' => ' | ', ]; - foreach ($memcache_stats['all'] as $row => $stats) { - $build[$row]['ms'] = ['#plain_text' => $stats[0]]; - $build[$row]['operation'] = ['#plain_text' => $stats[1]]; - $build[$row]['bin'] = ['#plain_text' => $stats[2]]; - $build[$row]['key'] = [ - '#separator' => ' | ', - ]; - foreach (explode('\n', $stats[3]) as $akey) { - $build[$row]['key']['child'][]['#plain_text'] = $akey; - } - $build[$row]['status'] = ['#plain_text' => $stats[4]]; + foreach (explode('\n', $stats[3]) as $akey) { + $build[$row]['key']['child'][]['#plain_text'] = $akey; } - $output .= $this->renderer->renderRoot($build); + $build[$row]['status'] = ['#plain_text' => $stats[4]]; } + $output .= $this->renderer->renderRoot($build); + } - if (!empty($output)) { - $response->setContent($response->getContent() . '<div id="memcache-devel"><h2>' . $this->t('Memcache statistics') . '</h2>' . $output . '</div>'); - } + if (!empty($output)) { + $response->setContent($response->getContent() . '<div id="memcache-devel"><h2>' . $this->t('Memcache statistics') . '</h2>' . $output . '</div>'); } } } diff --git a/src/Driver/DriverBase.php b/src/Driver/DriverBase.php index de6e540..eb06a04 100644 --- a/src/Driver/DriverBase.php +++ b/src/Driver/DriverBase.php @@ -53,6 +53,13 @@ abstract class DriverBase implements DrupalMemcacheInterface { 'ops' => [], ]; + /** + * Flag to indicate if stats collection has been enabled. + * + * @var bool + */ + private static bool $statsEnabled; + /** * Constructs a DriverBase object. * @@ -62,8 +69,10 @@ abstract class DriverBase implements DrupalMemcacheInterface { * An existing memcache connection object. * @param string $bin * The class instance specific cache bin to use. + * @param bool $statsEnabled + * Flag to indicate if stats collection has been enabled. */ - public function __construct(MemcacheSettings $settings, $memcache, $bin = NULL) { + public function __construct(MemcacheSettings $settings, $memcache, $bin = NULL, bool $statsEnabled = FALSE) { $this->settings = $settings; $this->memcache = $memcache; $this->hashAlgorithm = $this->settings->get('key_hash_algorithm', 'sha1'); @@ -74,6 +83,7 @@ abstract class DriverBase implements DrupalMemcacheInterface { if ($bin) { $this->prefix .= $bin . ':'; } + self::$statsEnabled = $statsEnabled; } /** @@ -242,6 +252,16 @@ abstract class DriverBase implements DrupalMemcacheInterface { return $stats; } + public function disableStats() { + // Clear up. + self::$stats = [ + 'all' => [], + 'ops' => [], + ]; + // Stop recording. + self::$statsEnabled = FALSE; + } + /** * Helper function to get the bins. */ @@ -302,13 +322,12 @@ abstract class DriverBase implements DrupalMemcacheInterface { * Helper function to initialize the stats for a memcache operation. */ protected function statsInit() { - if (\Drupal::getContainer()->getParameter('memcache.stats_collect')) { + if (self::$statsEnabled) { Timer::start('dmemcache'); return TRUE; } - else { - return FALSE; - } + + return FALSE; } /** diff --git a/src/Driver/MemcacheDriverFactory.php b/src/Driver/MemcacheDriverFactory.php index 3730376..d7bcf05 100644 --- a/src/Driver/MemcacheDriverFactory.php +++ b/src/Driver/MemcacheDriverFactory.php @@ -68,14 +68,24 @@ class MemcacheDriverFactory { */ protected $failedConnectionCache = []; + /** + * Flag to indicate if stats collection has been enabled. + * + * @var bool + */ + private bool $statsEnabled; + /** * Constructs a MemcacheDriverFactory object. * * @param \Drupal\memcache\MemcacheSettings $settings * The settings object. - */ - public function __construct(MemcacheSettings $settings) { + * @param bool $statsEnabled + * * Flag to indicate if stats collection has been enabled. + */ + public function __construct(MemcacheSettings $settings, bool $statsEnabled = FALSE) { $this->settings = $settings; + $this->statsEnabled = $statsEnabled; $this->initialize(); } @@ -148,7 +158,7 @@ class MemcacheDriverFactory { } } - return empty($this->connections[$bin]) ? FALSE : new $this->driverClass($this->settings, $this->connections[$bin]->getMemcache(), $bin); + return empty($this->connections[$bin]) ? FALSE : new $this->driverClass($this->settings, $this->connections[$bin]->getMemcache(), $bin, $this->statsEnabled); } /** -- GitLab From a7fa783afbfca6ae22a9fb6b784fb01ec7200c95 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 5 Mar 2025 12:21:52 +0000 Subject: [PATCH 3/8] PHPCS --- memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php | 1 - src/Driver/DriverBase.php | 3 +++ src/Driver/MemcacheDriverFactory.php | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php index 250f21c..8aa05b7 100644 --- a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php +++ b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php @@ -5,7 +5,6 @@ namespace Drupal\memcache_admin\EventSubscriber; use Drupal\Component\Render\HtmlEscapedText; use Drupal\Core\Config\ConfigCrudEvent; use Drupal\Core\Config\ConfigEvents; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\DrupalKernelInterface; use Drupal\Core\Render\HtmlResponse; use Drupal\Core\Render\RendererInterface; diff --git a/src/Driver/DriverBase.php b/src/Driver/DriverBase.php index eb06a04..bac0d34 100644 --- a/src/Driver/DriverBase.php +++ b/src/Driver/DriverBase.php @@ -252,6 +252,9 @@ abstract class DriverBase implements DrupalMemcacheInterface { return $stats; } + /** + * Disables stats collection. + */ public function disableStats() { // Clear up. self::$stats = [ diff --git a/src/Driver/MemcacheDriverFactory.php b/src/Driver/MemcacheDriverFactory.php index d7bcf05..6bd36c5 100644 --- a/src/Driver/MemcacheDriverFactory.php +++ b/src/Driver/MemcacheDriverFactory.php @@ -81,7 +81,7 @@ class MemcacheDriverFactory { * @param \Drupal\memcache\MemcacheSettings $settings * The settings object. * @param bool $statsEnabled - * * Flag to indicate if stats collection has been enabled. + * Flag to indicate if stats collection has been enabled. */ public function __construct(MemcacheSettings $settings, bool $statsEnabled = FALSE) { $this->settings = $settings; -- GitLab From c0cf15e3ca89937a21217b41d1c0404dfd602dc0 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 5 Mar 2025 14:18:15 +0000 Subject: [PATCH 4/8] Add a test --- .../src/Functional/MemcacheAdminStatsTest.php | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php diff --git a/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php new file mode 100644 index 0000000..5bb5ecc --- /dev/null +++ b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php @@ -0,0 +1,98 @@ +<?php + +namespace Drupal\Tests\memcache\Functional; + +use Drupal\Tests\BrowserTestBase; +use Drupal\user\Entity\Role; + +/** + * Tests displaying memcache statistics. + * + * @group memcache + */ +class MemcacheAdminStatsTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * {@inheritdoc} + */ + protected static $modules = ['memcache', 'memcache_admin']; + + /** + * {@inheritdoc} + */ + public function setUp(): void { + parent::setUp(); + $host = getenv('MEMCACHED_HOST') ?: '127.0.0.1:11211'; + $settings['settings']['memcache'] = (object) [ + 'value' => [ + 'servers' => [$host => 'default'], + 'bin' => ['default' => 'default'], + 'key_prefix' => $this->databasePrefix, + ], + 'required' => TRUE, + ]; + $settings['settings']['cache']['default'] = (object) [ + 'value' => 'cache.backend.memcache', + 'required' => TRUE, + ]; + + $settings['settings']['hash_salt'] = (object) [ + 'value' => $this->randomMachineName(), + 'required' => TRUE, + ]; + + $this->writeSettings($settings); + $this->rebuildAll(); + } + + /** + * Tests displaying memcache statistics. + */ + public function testStats(): void { + // By default, stats are not collected. + $this->assertFalse(\Drupal::getContainer()->getParameter('memcache.stats_collect')); + + $this->drupalLogin($this->createUser(['administer site configuration'])); + $this->drupalGet('admin/config/system/memcache'); + $this->assertSession()->fieldExists('show_memcache_statistics')->check(); + $this->assertSession()->buttonExists('Save configuration')->click(); + + // The user does not have the permission so should not see any stats. + $this->drupalGet('/'); + $this->assertSession()->pageTextNotContains('Memcache statistics'); + + // A user with the permission can see stats. + $this->drupalLogin($this->createUser(['access memcache statistics'])); + $this->drupalGet('/'); + $this->assertSession()->pageTextContains('Memcache statistics'); + + // The anonymous user can not see stats unless they have the permission. + $this->drupalLogout(); + $this->drupalGet('/'); + $this->assertSession()->pageTextNotContains('Memcache statistics'); + + // Ensure you can set up anonymous users to see memcache stats so for + // debugging purposes. + Role::load(Role::ANONYMOUS_ID)->grantPermission('access memcache statistics')->save(); + $this->drupalGet('/'); + $this->assertSession()->pageTextContains('Memcache statistics'); + + // Rebuild the container in the test so we can assert against it. The form + // save above will have triggered a rebuild in the site-under-test but we + // wait till now so the test confirms this has worked. + $this->rebuildContainer(); + $this->assertTrue(\Drupal::getContainer()->getParameter('memcache.stats_collect')); + + \Drupal::service('module_installer')->uninstall(['memcache_admin']); + $this->rebuildAll(); + $this->drupalGet('/'); + $this->assertSession()->pageTextNotContains('Memcache statistics'); + $this->assertFalse(\Drupal::getContainer()->getParameter('memcache.stats_collect')); + } + +} -- GitLab From 01706230292ecc40509267faf7d298bca4481a1a Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 5 Mar 2025 14:31:28 +0000 Subject: [PATCH 5/8] Add empty update to indicate container needs a rebuild --- memcache.post_update.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/memcache.post_update.php b/memcache.post_update.php index 77cc0d8..98c4209 100644 --- a/memcache.post_update.php +++ b/memcache.post_update.php @@ -13,3 +13,10 @@ function memcache_post_update_add_datetime() { $kernel = \Drupal::service('kernel'); $kernel->invalidateContainer(); } + +/** + * Rebuild the service container to add the 'memcache.stats_collect' container parameter. + */ +function memcache_post_update_add_container_parameter() { + // Intentionally empty. +} -- GitLab From c94dafaa7578dd6bfb0e403ea7d1604ae42ebd28 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 5 Mar 2025 14:32:45 +0000 Subject: [PATCH 6/8] Fixes --- memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php | 2 +- src/Driver/MemcacheDriverFactory.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php index 5bb5ecc..7f0fe59 100644 --- a/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php +++ b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\Tests\memcache\Functional; +namespace Drupal\Tests\memcache_admin\Functional; use Drupal\Tests\BrowserTestBase; use Drupal\user\Entity\Role; diff --git a/src/Driver/MemcacheDriverFactory.php b/src/Driver/MemcacheDriverFactory.php index 6bd36c5..d395841 100644 --- a/src/Driver/MemcacheDriverFactory.php +++ b/src/Driver/MemcacheDriverFactory.php @@ -82,7 +82,7 @@ class MemcacheDriverFactory { * The settings object. * @param bool $statsEnabled * Flag to indicate if stats collection has been enabled. - */ + */ public function __construct(MemcacheSettings $settings, bool $statsEnabled = FALSE) { $this->settings = $settings; $this->statsEnabled = $statsEnabled; -- GitLab From 401ce781501e3bfead5ec46cc072c585cc14db76 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 5 Mar 2025 14:36:51 +0000 Subject: [PATCH 7/8] Pointless and in this case incorrect PHPCS rule but whatevs --- memcache.post_update.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/memcache.post_update.php b/memcache.post_update.php index 98c4209..59daa08 100644 --- a/memcache.post_update.php +++ b/memcache.post_update.php @@ -15,7 +15,7 @@ function memcache_post_update_add_datetime() { } /** - * Rebuild the service container to add the 'memcache.stats_collect' container parameter. + * Rebuild container to add the 'memcache.stats_collect' container parameter. */ function memcache_post_update_add_container_parameter() { // Intentionally empty. -- GitLab From ec9f95261dd7af6da6d71122c00a93f52b8d5a6e Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 5 Mar 2025 14:40:15 +0000 Subject: [PATCH 8/8] Fix test on gitlab --- .../tests/src/Functional/MemcacheAdminStatsTest.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php index 7f0fe59..84ed87d 100644 --- a/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php +++ b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\memcache_admin\Functional; +use Drupal\Core\Url; use Drupal\Tests\BrowserTestBase; use Drupal\user\Entity\Role; @@ -54,6 +55,7 @@ class MemcacheAdminStatsTest extends BrowserTestBase { * Tests displaying memcache statistics. */ public function testStats(): void { + $front = Url::fromRoute('<front>'); // By default, stats are not collected. $this->assertFalse(\Drupal::getContainer()->getParameter('memcache.stats_collect')); @@ -63,23 +65,23 @@ class MemcacheAdminStatsTest extends BrowserTestBase { $this->assertSession()->buttonExists('Save configuration')->click(); // The user does not have the permission so should not see any stats. - $this->drupalGet('/'); + $this->drupalGet($front); $this->assertSession()->pageTextNotContains('Memcache statistics'); // A user with the permission can see stats. $this->drupalLogin($this->createUser(['access memcache statistics'])); - $this->drupalGet('/'); + $this->drupalGet($front); $this->assertSession()->pageTextContains('Memcache statistics'); // The anonymous user can not see stats unless they have the permission. $this->drupalLogout(); - $this->drupalGet('/'); + $this->drupalGet($front); $this->assertSession()->pageTextNotContains('Memcache statistics'); // Ensure you can set up anonymous users to see memcache stats so for // debugging purposes. Role::load(Role::ANONYMOUS_ID)->grantPermission('access memcache statistics')->save(); - $this->drupalGet('/'); + $this->drupalGet($front); $this->assertSession()->pageTextContains('Memcache statistics'); // Rebuild the container in the test so we can assert against it. The form @@ -90,7 +92,7 @@ class MemcacheAdminStatsTest extends BrowserTestBase { \Drupal::service('module_installer')->uninstall(['memcache_admin']); $this->rebuildAll(); - $this->drupalGet('/'); + $this->drupalGet($front); $this->assertSession()->pageTextNotContains('Memcache statistics'); $this->assertFalse(\Drupal::getContainer()->getParameter('memcache.stats_collect')); } -- GitLab