Skip to content
Snippets Groups Projects

Collect memcache stats only when memcache_admin is installed and configure to show stats

Open Alex Pott requested to merge issue/memcache-3503083:3503083-memory-leak into 8.x-2.x
Files
8
@@ -3,13 +3,16 @@
@@ -3,13 +3,16 @@
namespace Drupal\memcache_admin\EventSubscriber;
namespace Drupal\memcache_admin\EventSubscriber;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ConfigCrudEvent;
 
use Drupal\Core\Config\ConfigEvents;
 
use Drupal\Core\DrupalKernelInterface;
use Drupal\Core\Render\HtmlResponse;
use Drupal\Core\Render\HtmlResponse;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\memcache\Driver\MemcacheDriverFactory;
use Drupal\memcache\Driver\MemcacheDriverFactory;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\KernelEvents;
@@ -27,11 +30,11 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
@@ -27,11 +30,11 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
protected $memcacheFactory;
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.
* The current user.
@@ -47,23 +50,33 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
@@ -47,23 +50,33 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
*/
*/
protected $renderer;
protected $renderer;
 
/**
 
* The kernel.
 
*
 
* @var \Drupal\Core\DrupalKernelInterface
 
*/
 
protected $kernel;
 
/**
/**
* MemcacheAdminSubscriber constructor.
* MemcacheAdminSubscriber constructor.
*
*
* @param \Drupal\memcache\Driver\MemcacheDriverFactory $memcache_factory
* @param \Drupal\memcache\Driver\MemcacheDriverFactory $memcache_factory
* The memcache factory service.
* The memcache factory service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* @param bool $stats_enabled
* The config factory service.
* The 'memcache.stats_collect' container parameter.
* @param \Drupal\Core\Session\AccountInterface $current_user
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* The current user.
* @param \Drupal\Core\Render\RendererInterface $renderer
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
* 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, bool $stats_enabled, AccountInterface $current_user, RendererInterface $renderer, DrupalKernelInterface $kernel) {
$this->memcacheFactory = $memcache_factory;
$this->memcacheFactory = $memcache_factory;
$this->configFactory = $config_factory;
$this->statsEnabled = $stats_enabled;
$this->currentUser = $current_user;
$this->currentUser = $current_user;
$this->renderer = $renderer;
$this->renderer = $renderer;
 
$this->kernel = $kernel;
}
}
/**
/**
@@ -71,101 +84,127 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
@@ -71,101 +84,127 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
*/
*/
public static function getSubscribedEvents(): array {
public static function getSubscribedEvents(): array {
$events = [];
$events = [];
 
// Needs to come after user authentication.
 
$events[KernelEvents::REQUEST][] = ['disableStatisticsIfNoPermission', 299];
$events[KernelEvents::RESPONSE][] = ['displayStatistics'];
$events[KernelEvents::RESPONSE][] = ['displayStatistics'];
 
$events[ConfigEvents::SAVE][] = ['onConfigSave', 0];
return $events;
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.
* Display statistics on page.
*/
*/
public function displayStatistics(ResponseEvent $event) {
public function displayStatistics(ResponseEvent $event) {
$user = $this->currentUser;
if (!$this->statsEnabled) {
if ($user->id() == 0) {
return;
// Suppress for the above criteria.
}
}
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 = [
$response = $event->getResponse();
'#theme' => 'table',
'#header' => [
// Don't call theme() during shutdown if the registry has been rebuilt
$this->t('operation'),
// (such as when enabling/disabling modules on admin/build/modules) as
$this->t('total ms'),
// things break.
$this->t('total hits'),
// Instead, simply exit without displaying admin statistics for this page
$this->t('total misses'),
// load. See http://drupal.org/node/616282 for discussion.
],
// @todo make sure this is not still a requirement.
'#rows' => $memcache_stats['ops'],
// @codingStandardsIgnoreStart
];
// if (!function_exists('theme_get_registry') || !theme_get_registry()) {
$output .= $this->renderer->renderRoot($build);
// 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 = [
$build = [
'#theme' => 'table',
'#type' => 'table',
'#header' => [
'#header' => [
$this->t('operation'),
$this->t('ms'),
$this->t('total ms'),
$this->t('operation'),
$this->t('total hits'),
$this->t('bin'),
$this->t('total misses'),
$this->t('key'),
],
$this->t('status'),
'#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) {
foreach (explode('\n', $stats[3]) as $akey) {
$build[$row]['ms'] = ['#plain_text' => $stats[0]];
$build[$row]['key']['child'][]['#plain_text'] = $akey;
$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]];
}
}
$output .= $this->renderer->renderRoot($build);
$build[$row]['status'] = ['#plain_text' => $stats[4]];
}
}
 
$output .= $this->renderer->renderRoot($build);
 
}
if (!empty($output)) {
if (!empty($output)) {
$response->setContent($response->getContent() . '<div id="memcache-devel"><h2>' . $this->t('Memcache statistics') . '</h2>' . $output . '</div>');
$response->setContent($response->getContent() . '<div id="memcache-devel"><h2>' . $this->t('Memcache statistics') . '</h2>' . $output . '</div>');
}
}
}
}
}
}
}
}
}
}
}
 
/**
 
* 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.
* Helper function. Calculate a percentage.
*/
*/
Loading