diff --git a/memcache_admin/memcache_admin.services.yml b/memcache_admin/memcache_admin.services.yml index 41de36ccfaac5c55327a98cc897e26222e28cfaa..c41f4d3dd7c44effa564fba96f5bb0c7c3c5e96a 100644 --- a/memcache_admin/memcache_admin.services.yml +++ b/memcache_admin/memcache_admin.services.yml @@ -3,3 +3,11 @@ services: class: Drupal\memcache_admin\EventSubscriber\MemcacheAdminSubscriber tags: - { name: event_subscriber } + memcache_stats.memcached: + class: Drupal\memcache_admin\EventSubscriber\MemcacheServerStatsSubscriber + tags: + - { name: event_subscriber } + memcache_stats.mcrouter: + class: Drupal\memcache_admin\EventSubscriber\McrouterStatsSubscriber + tags: + - { name: event_subscriber } \ No newline at end of file diff --git a/memcache_admin/src/Controller/MemcacheStatisticsController.php b/memcache_admin/src/Controller/MemcacheStatisticsController.php index 7e119da5f836f1c4c04e98a6a11f1433ac976c89..2be90ba870430911c1d91b097933c7a43bbd6eed 100644 --- a/memcache_admin/src/Controller/MemcacheStatisticsController.php +++ b/memcache_admin/src/Controller/MemcacheStatisticsController.php @@ -3,12 +3,11 @@ namespace Drupal\memcache_admin\Controller; use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\Datetime\DateFormatter; use Drupal\Core\Link; use Drupal\Core\Messenger\MessengerTrait; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; -use Drupal\Component\Render\HtmlEscapedText; +use Drupal\memcache_admin\Event\MemcacheStatsEvent; /** * Memcache Statistics. @@ -24,136 +23,35 @@ class MemcacheStatisticsController extends ControllerBase { * @param string $bin * The bin name. * - * @return string + * @return array * The page output. */ public function statsTable($bin = 'default') { - $output = []; - $servers = []; - - // Get the statistics. - $bin = $this->binMapping($bin); + $bin = $this->getBinMapping($bin); /** @var $memcache \Drupal\memcache\DrupalMemcacheInterface */ $memcache = \Drupal::service('memcache.factory')->get($bin, TRUE); - $stats = $memcache->stats($bin, 'default', TRUE); - - if (empty($stats[$bin])) { - // Failed to load statistics. Provide a useful error about where to get - // more information and help. - $this->messenger()->addError( - $this->t( - 'There may be a problem with your Memcache configuration. Please review @readme for more information.', - [ - '@readme' => 'README.txt', - ] - ) - ); - } - else { - if (count($stats[$bin])) { - $stats = $stats[$bin]; - $aggregate = array_pop($stats); - - if ($memcache->getMemcache() instanceof \Memcached) { - $version = $this->t('Memcached v@version', ['@version' => phpversion('Memcached')]); - } - elseif ($memcache->getMemcache() instanceof \Memcache) { - $version = $this->t('Memcache v@version', ['@version' => phpversion('Memcache')]); - } - else { - $version = $this->t('Unknown'); - $this->messenger()->addError($this->t('Failed to detect the memcache PECL extension.')); - } - foreach ($stats as $server => $statistics) { - if (empty($statistics['uptime'])) { - $this->messenger()->addError($this->t('Failed to connect to server at :address.', [':address' => $server])); - } - else { - $servers[] = $server; - - $data['server_overview'][$server] = $this->t('v@version running @uptime', ['@version' => $statistics['version'], '@uptime' => \Drupal::service('date.formatter')->formatInterval($statistics['uptime'])]); - $data['server_pecl'][$server] = $this->t('n/a'); - $data['server_time'][$server] = \Drupal::service('date.formatter')->format($statistics['time']); - $data['server_connections'][$server] = $this->statsConnections($statistics); - $data['cache_sets'][$server] = $this->statsSets($statistics); - $data['cache_gets'][$server] = $this->statsGets($statistics); - $data['cache_counters'][$server] = $this->statsCounters($statistics); - $data['cache_transfer'][$server] = $this->statsTransfer($statistics); - $data['cache_average'][$server] = $this->statsAverage($statistics); - $data['memory_available'][$server] = $this->statsMemory($statistics); - $data['memory_evictions'][$server] = number_format($statistics['evictions']); - } - } - } + // Instantiate our event. + $event = new MemcacheStatsEvent($memcache, $bin); - // Build a custom report array. - $report = [ - 'uptime' => [ - 'uptime' => [ - 'label' => $this->t('Uptime'), - 'servers' => $data['server_overview'], - ], - 'extension' => [ - 'label' => $this->t('PECL extension'), - 'servers' => [$servers[0] => $version], - ], - 'time' => [ - 'label' => $this->t('Time'), - 'servers' => $data['server_time'], - ], - 'connections' => [ - 'label' => $this->t('Connections'), - 'servers' => $data['server_connections'], - ], - ], - 'stats' => [], - 'memory' => [ - 'memory' => [ - 'label' => $this->t('Available memory'), - 'servers' => $data['memory_available'], - ], - 'evictions' => [ - 'label' => $this->t('Evictions'), - 'servers' => $data['memory_evictions'], - ], - ], - ]; + // Get the event_dispatcher service and dispatch the event. + $event_dispatcher = \Drupal::service('event_dispatcher'); + $event_dispatcher->dispatch(MemcacheStatsEvent::BUILD_MEMCACHE_STATS, $event); - // Don't display aggregate totals if there's only one server. - if (count($servers) > 1) { - $report['uptime']['uptime']['total'] = $this->t('n/a'); - $report['uptime']['extension']['servers'] = $data['server_pecl']; - $report['uptime']['extension']['total'] = $version; - $report['uptime']['time']['total'] = $this->t('n/a'); - $report['uptime']['connections']['total'] = $this->statsConnections($aggregate); - $report['memory']['memory']['total'] = $this->statsMemory($aggregate); - $report['memory']['evictions']['total'] = number_format($aggregate['evictions']); - } - - // Report on stats. - $stats = [ - 'sets' => $this->t('Sets'), - 'gets' => $this->t('Gets'), - 'counters' => $this->t('Counters'), - 'transfer' => $this->t('Transferred'), - 'average' => $this->t('Per-connection average'), - ]; + // Report the PHP Memcache(d) driver version. + if ($memcache->getMemcache() instanceof \Memcached) { + $raw_stats['driver_version'] = $this->t('PECL Driver in Use: Memcached v@version', ['@version' => phpversion('Memcached')]); + } + elseif ($memcache->getMemcache() instanceof \Memcache) { + $raw_stats['driver_version'] = $this->t('PECL Driver in Use: Memcache v@version', ['@version' => phpversion('Memcache')]); + } - foreach ($stats as $type => $label) { - $report['stats'][$type] = [ - 'label' => $label, - 'servers' => $data["cache_{$type}"], - ]; + // Get the event_dispatcher service and dispatch the event. + $event_dispatcher = \Drupal::service('event_dispatcher'); + $event_dispatcher->dispatch(MemcacheStatsEvent::REPORT_MEMCACHE_STATS, $event); - if (count($servers) > 1) { - $func = 'stats' . ucfirst($type); - $report['stats'][$type]['total'] = $this->{$func}($aggregate); - } - } - - $output = $this->statsTablesOutput($bin, $servers, $report); - } + $output = ['#markup' => '<p>' . $raw_stats['driver_version']]; + $output[] = $this->statsTablesOutput($bin, $event->getServers(), $event->getReport()); return $output; } @@ -229,17 +127,6 @@ class MemcacheStatisticsController extends ControllerBase { } } - /** - * Helper function. Returns the bin name. - */ - private function defaultBin($bin) { - if ($bin == 'default') { - return 'cache'; - } - - return $bin; - } - /** * Statistics report: format total and open connections. */ @@ -253,63 +140,6 @@ class MemcacheStatisticsController extends ControllerBase { ); } - /** - * Statistics report: calculate # of set cmds and total cmds. - */ - private function statsSets($stats) { - if (($stats['cmd_set'] + $stats['cmd_get']) == 0) { - $sets = 0; - } - else { - $sets = $stats['cmd_set'] / ($stats['cmd_set'] + $stats['cmd_get']) * 100; - } - if (empty($stats['uptime'])) { - $average = 0; - } - else { - $average = $sets / $stats['uptime']; - } - return $this->t( - '@average/s; @set sets (@sets%) of @total commands', - [ - '@average' => number_format($average, 2), - '@sets' => number_format($sets, 2), - '@set' => number_format($stats['cmd_set']), - '@total' => number_format($stats['cmd_set'] + $stats['cmd_get']), - ] - ); - } - - /** - * Statistics report: calculate # of get cmds, broken down by hits and misses. - */ - private function statsGets($stats) { - if (($stats['cmd_set'] + $stats['cmd_get']) == 0) { - $gets = 0; - } - else { - $gets = $stats['cmd_get'] / ($stats['cmd_set'] + $stats['cmd_get']) * 100; - } - if (empty($stats['uptime'])) { - $average = 0; - } - else { - $average = $stats['cmd_get'] / $stats['uptime']; - } - return $this->t( - '@average/s; @total gets (@gets%); @hit hits (@percent_hit%) @miss misses (@percent_miss%)', - [ - '@average' => number_format($average, 2), - '@gets' => number_format($gets, 2), - '@hit' => number_format($stats['get_hits']), - '@percent_hit' => ($stats['cmd_get'] > 0 ? number_format($stats['get_hits'] / $stats['cmd_get'] * 100, 2) : '0.00'), - '@miss' => number_format($stats['get_misses']), - '@percent_miss' => ($stats['cmd_get'] > 0 ? number_format($stats['get_misses'] / $stats['cmd_get'] * 100, 2) : '0.00'), - '@total' => number_format($stats['cmd_get']), - ] - ); - } - /** * Statistics report: calculate # of increments and decrements. */ @@ -334,73 +164,6 @@ class MemcacheStatisticsController extends ControllerBase { ); } - /** - * Statistics report: calculate bytes transferred. - */ - private function statsTransfer($stats) { - if ($stats['bytes_written'] == 0) { - $written = 0; - } - else { - $written = $stats['bytes_read'] / $stats['bytes_written'] * 100; - } - return $this->t( - '@to:@from (@written% to cache)', - [ - '@to' => format_size((int) $stats['bytes_read']), - '@from' => format_size((int) $stats['bytes_written']), - '@written' => number_format($written, 2), - ] - ); - } - - /** - * Statistics report: calculate per-connection averages. - */ - private function statsAverage($stats) { - if ($stats['total_connections'] == 0) { - $get = 0; - $set = 0; - $read = 0; - $write = 0; - } - else { - $get = $stats['cmd_get'] / $stats['total_connections']; - $set = $stats['cmd_set'] / $stats['total_connections']; - $read = $stats['bytes_written'] / $stats['total_connections']; - $write = $stats['bytes_read'] / $stats['total_connections']; - } - return $this->t( - '@read in @get gets; @write in @set sets', - [ - '@get' => number_format($get, 2), - '@set' => number_format($set, 2), - '@read' => format_size(number_format($read, 2)), - '@write' => format_size(number_format($write, 2)), - ] - ); - } - - /** - * Statistics report: calculate available memory. - */ - private function statsMemory($stats) { - if ($stats['limit_maxbytes'] == 0) { - $percent = 0; - } - else { - $percent = 100 - $stats['bytes'] / $stats['limit_maxbytes'] * 100; - } - return $this->t( - '@available (@percent%) of @total', - [ - '@available' => format_size($stats['limit_maxbytes'] - $stats['bytes']), - '@percent' => number_format($percent, 2), - '@total' => format_size($stats['limit_maxbytes']), - ] - ); - } - /** * Generates render array for output. */ @@ -409,6 +172,9 @@ class MemcacheStatisticsController extends ControllerBase { $memcache_bins = $memcache->getBins(); $links = []; + if (!is_array($servers)) { + return; + } foreach ($servers as $server) { // Convert socket file path so it works with an argument, this should @@ -546,4 +312,31 @@ class MemcacheStatisticsController extends ControllerBase { return $build; } + /** + * Helper function, reverse map the memcache_bins variable. + */ + protected function getBinMapping($bin = 'cache') { + $memcache = \Drupal::service('memcache.factory')->get(NULL, TRUE); + $memcache_bins = $memcache->getBins(); + + $bins = array_flip($memcache_bins); + if (isset($bins[$bin])) { + return $bins[$bin]; + } + else { + return $this->defaultBin($bin); + } + } + + /** + * Helper function. Returns the bin name. + */ + protected function defaultBin($bin) { + if ($bin == 'default') { + return 'cache'; + } + + return $bin; + } + } diff --git a/memcache_admin/src/Event/MemcacheStatsEvent.php b/memcache_admin/src/Event/MemcacheStatsEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..4b8fc07c59b9c34d995a30dda98e03ab2cea501a --- /dev/null +++ b/memcache_admin/src/Event/MemcacheStatsEvent.php @@ -0,0 +1,190 @@ +<?php + +namespace Drupal\memcache_admin\Event; + +use Drupal\memcache\DrupalMemcacheInterface; +use Symfony\Component\EventDispatcher\Event; + +/** + * Memcache Stats Event + * + * The memcache stats event stores all the attributes generated by the different + * types of memcache servers. Currently memcache_admin supports memcache and + * mcrouter. + */ +class MemcacheStatsEvent extends Event { + + /** + * Event used to build the memcache stats array. + * + * When the stats array is created, this event allows modules to inject extra + * data to be contained within the array. + */ + const BUILD_MEMCACHE_STATS = 'memcache_build_memcache_stats'; + + /** + * Event used to report out the memcache stats array. + * + * When the stats array is created, this event allows modules to inject extra + * data to be contained within the array. + */ + const REPORT_MEMCACHE_STATS = 'memcache_report_memcache_stats'; + + /** + * The Stats Array for which to create attributes. + * + * @var array + */ + protected $rawStats; + + /** + * The Stats Array for which to create attributes. + * + * @var array + */ + protected $formattedStats = []; + + /** + * The Stats Array for which to create attributes. + * + * @var array + */ + protected $totals; + + /** + * The Stats Array for which to create attributes. + * + * @var array + */ + protected $report; + + /** + * The Stats Array for which to create attributes. + * + * @var array + */ + protected $servers; + + /** + * Cache Bin To Retrieve + * @var string $bin + */ + protected $bin; + + protected $memcache; + + /** + * MemcacheStatsEvent constructor. + * + * @param array $raw_stats + * The Stats Data. + * @param string $bin + * The cache bin. + * + */ + public function __construct(DrupalMemcacheInterface $memcache, string $bin = 'default') { + $this->memcache = $memcache; + $this->rawStats = $memcache->stats($bin, 'default', TRUE); + $this->formattedStats = []; + $this->bin = $bin; + } + + /** + * Get the Stats Array being created. + * + * @return array + * The Stats Object. + */ + public function getRawStats() { + return $this->rawStats; + } + + /** + * Gets the stats formatted from a MemcacheStatsInterface. + * @param $server_type + * + * @return array|mixed + */ + public function getFormattedStats($server_type) { + if (isset($this->formattedStats[$server_type])) { + return $this->formattedStats[$server_type]; + } + return []; + } + + /** + * Returns the memcache connection. + */ + public function getMemcache() { + return $this->memcache; + } + + /** + * Returns the cache bin from this event. + */ + public function getCacheBin() { + return $this->bin; + } + + /** + * Sets the formatted stats array with relevant data. + * + * @param string $format + * @param string $bin + * @param string $server + * @param \Drupal\memcache_admin\Stats\MemcacheStatsInterface $data + */ + public function updateFormattedStats($format, $bin, $server, $data) { + $this->formattedStats[$format][$bin][$server] = $data; + } + + /** + * Update the total column when multiple memcache servers exist. + * @param $total + */ + public function updateTotals($total) { + $this->totals = $total; + } + + /** + * Return the total values from all memcache servers. + * @return array + */ + public function getTotals() { + return $this->totals; + } + + /** + * Add a new server to the servers array. + * @param string $server + */ + public function updateServers($server) { + $this->servers[] = $server; + } + + /** + * Retrieve all servers from the servers array. + * + * @return array + */ + public function getServers() { + return $this->servers; + } + + /** + * Update the full report from the event. + * @param $report + */ + public function updateReport($report) { + $this->report = $report; + } + + /** + * Returns the stats report. + * @return array + */ + public function getReport() { + return $this->report; + } + +} diff --git a/memcache_admin/src/EventSubscriber/McrouterStatsSubscriber.php b/memcache_admin/src/EventSubscriber/McrouterStatsSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..6432ba94b65ee32b8bc53becffa71bf866d65bb1 --- /dev/null +++ b/memcache_admin/src/EventSubscriber/McrouterStatsSubscriber.php @@ -0,0 +1,68 @@ +<?php + +namespace Drupal\memcache_admin\EventSubscriber; + +use Drupal\Core\Messenger\MessengerTrait; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\memcache_admin\Event\MemcacheStatsEvent; +use Drupal\memcache_admin\Stats\McrouterStatsObject; +use Drupal\memcache_admin\Stats\MemcacheStatsObject; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Adds memcache server specific details to the stats array. + */ +class McrouterStatsSubscriber implements EventSubscriberInterface { + + use StringTranslationTrait; + use MessengerTrait; + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[MemcacheStatsEvent::BUILD_MEMCACHE_STATS][] = [ + 'onPopulateStats', + 100 + ]; + return $events; + } + + /** + * Adds stats to the memcache event. + * + * @param \Drupal\memcache_admin\Event\MemcacheStatsEvent $event + * The event being dispatched. + * + * @throws \Exception + */ + public function onPopulateStats(MemcacheStatsEvent $event) { + $memcache = $event->getMemcache(); + if (!$memcache->getMemcache()->get('__mcrouter__.version')) { + return; + } + + $raw_stats = $event->getRawStats(); + $bin = $event->getCacheBin(); + + // No cache bin data, return. + if (!isset($raw_stats[$bin])) { + return; + } + // No servers found, return. + if (!is_array($raw_stats[$bin])) { + return; + } + $servers = array_keys($raw_stats[$bin]); + foreach ($servers as $server) { + if ($server =='total') { + continue; + } + // McRouter reports num_servers use that for detecting stats. + if (isset($raw_stats[$bin][$server]['num_servers'])) { + $event->updateFormattedStats('memcache', $bin, $server, new McrouterStatsObject($raw_stats[$bin][$server])); + $event->updateServers($server); + } + } + } +} diff --git a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php index e0a967627203a3c3357228d17964b1259b21342e..03ae73981360e2acb9715dc073fd5cd4f1e95697 100644 --- a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php +++ b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php @@ -32,16 +32,6 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface { public function displayStatistics(FilterResponseEvent $event) { $user = \Drupal::currentUser(); - // Removed exclusion criteria, untested. Will likely need to add some of - // these back in. - // @codingStandardsIgnoreStart - // strstr($_SERVER['PHP_SELF'], '/update.php') - // substr($_GET['q'], 0, strlen('batch')) == 'batch' - // strstr($_GET['q'], 'autocomplete') - // substr($_GET['q'], 0, strlen('system/files')) == 'system/files' - // in_array($_GET['q'], ['upload/js', 'admin/content/node-settings/rebuild']) - // @codingStandardsIgnoreEnd - // @todo validate these checks if ($user->id() == 0) { // Suppress for the above criteria. } diff --git a/memcache_admin/src/EventSubscriber/MemcacheServerStatsSubscriber.php b/memcache_admin/src/EventSubscriber/MemcacheServerStatsSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..758221bd49c43c0e4be225dd2b08250f1c6fe9f8 --- /dev/null +++ b/memcache_admin/src/EventSubscriber/MemcacheServerStatsSubscriber.php @@ -0,0 +1,190 @@ +<?php + +namespace Drupal\memcache_admin\EventSubscriber; + +use Drupal\Core\Messenger\MessengerTrait; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\memcache_admin\Event\MemcacheStatsEvent; +use Drupal\memcache_admin\Stats\MemcacheStatsObject; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Adds memcache server specific details to the stats array. + */ +class MemcacheServerStatsSubscriber implements EventSubscriberInterface { + + use StringTranslationTrait; + use MessengerTrait; + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[MemcacheStatsEvent::BUILD_MEMCACHE_STATS][] = [ + 'onPopulateStats', + 100 + ]; + $events[MemcacheStatsEvent::REPORT_MEMCACHE_STATS][] = [ + 'onReportStats', + 100 + ]; + return $events; + } + + /** + * Populates the Memcache Server Stats + * + * @param \Drupal\memcache_admin\Event\MemcacheStatsEvent $event + * The event being dispatched. + * + * @throws \Exception + */ + public function onPopulateStats(MemcacheStatsEvent $event) { + $raw_stats = $event->getRawStats(); + $bin = $event->getCacheBin(); + + // No cache bin data, return. + if (!isset($raw_stats[$bin])) { + return; + } + // No servers found, return. + if (!is_array($raw_stats[$bin])) { + return; + } + $servers = array_keys($raw_stats[$bin]); + $memcache_servers = []; + foreach ($servers as $server) { + // Memcache servers report libevent version, use that for detecting stats. + if (isset($raw_stats[$bin][$server]['libevent'])) { + $event->updateFormattedStats('memcache', $bin, $server, new MemcacheStatsObject($raw_stats[$bin][$server])); + $event->updateServers($server); + } + } + if (isset($raw_stats[$bin]['total'])) { + $event->updateTotals([$bin => new MemcacheStatsObject($raw_stats[$bin]['total'])]); + } + } + + /** + * Populates the reporting of a stored set of stats. + * + * @param \Drupal\memcache_admin\Event\MemcacheStatsEvent $event + */ + public function onReportStats(MemcacheStatsEvent $event) { + $stats = $event->getFormattedStats('memcache'); + $bin = $event->getCacheBin(); + + // No cache bin data, return. + if (empty($stats[$bin])) { + // Failed to load statistics. Provide a useful error about where to get + // more information and help. + $this->messenger()->addError( + $this->t( + 'There may be a problem with your Memcache configuration. Please review @readme for more information.', + [ + '@readme' => 'README.txt', + ] + ) + ); + return; + } + // No servers found, return. + if (!is_array($stats[$bin])) { + return; + } + + /** + * @var string $server + * @var MemcacheStatsObject $statistics + */ + foreach ($stats[$bin] as $server => $statistics) { + if (empty($statistics->getUptime())) { + $this->messenger() + ->addError($this->t('Failed to connect to server at :address.', [':address' => $server])); + } + else { + $data['server_overview'][$server] = $this->t('v@version running @uptime', [ + '@version' => $statistics->getVersion(), + '@uptime' => $statistics->getUptime() + ]); + $data['server_time'][$server] = $statistics->getServerTime(); + $data['server_connections'][$server] = $statistics->getConnections(); + $data['cache_sets'][$server] = $statistics->getSets(); + $data['cache_gets'][$server] = $statistics->getGets(); + $data['cache_counters'][$server] = $statistics->getCounters(); + $data['cache_transfer'][$server] = $statistics->getTransferred(); + $data['cache_average'][$server] = $statistics->getConnectionAvg(); + $data['memory_available'][$server] = $statistics->getMemory(); + $data['memory_evictions'][$server] = $statistics->getEvictions(); + } + } + + // Build a custom report array. + $report = [ + 'uptime' => [ + 'uptime' => [ + 'label' => $this->t('Uptime'), + 'servers' => $data['server_overview'], + ], + 'time' => [ + 'label' => $this->t('Time'), + 'servers' => $data['server_time'], + ], + 'connections' => [ + 'label' => $this->t('Connections'), + 'servers' => $data['server_connections'], + ], + ], + 'stats' => [ + 'sets' => [ + 'label' => $this->t('Sets'), + 'servers' => $data["cache_sets"], + ], + 'gets' => [ + 'label' => $this->t('Gets'), + 'servers' => $data["cache_gets"], + ], + 'counters' => [ + 'label' => $this->t('Counters'), + 'servers' => $data["cache_counters"], + ], + 'transfer' => [ + 'label' => $this->t('Transferred'), + 'servers' => $data["cache_transfer"], + ], + 'average' => [ + 'label' => $this->t('Per-connection average'), + 'servers' => $data["cache_average"], + ], + ], + 'memory' => [ + 'memory' => [ + 'label' => $this->t('Available memory'), + 'servers' => $data['memory_available'], + ], + 'evictions' => [ + 'label' => $this->t('Evictions'), + 'servers' => $data['memory_evictions'], + ], + ], + ]; + + // Don't display aggregate totals if there's only one server. + if (count($stats[$bin]) > 1) { + /** @var MemcacheStatsObject $totals */ + $totals = $event->getTotals(); + $report['uptime']['uptime']['total'] = $this->t('n/a'); + $report['uptime']['time']['total'] = $this->t('n/a'); + $report['uptime']['connections']['total'] = $totals[$bin]->getConnections(); + $report['stats']['sets']['total'] = $totals[$bin]->getSets(); + $report['stats']['gets']['total'] = $totals[$bin]->getGets(); + $report['stats']['counters']['total'] = $totals[$bin]->getCounters(); + $report['stats']['transfer']['total'] = $totals[$bin]->getTransferred(); + $report['stats']['average']['total'] = $totals[$bin]->getConnectionAvg(); + $report['memory']['memory']['total'] = $totals[$bin]->getMemory(); + $report['memory']['evictions']['total'] = $totals[$bin]->getEvictions(); + } + + $event->updateReport($report); + } +} diff --git a/memcache_admin/src/Stats/McrouterStatsObject.php b/memcache_admin/src/Stats/McrouterStatsObject.php new file mode 100644 index 0000000000000000000000000000000000000000..4651bdf23578786294c791f667edfeb08be29f40 --- /dev/null +++ b/memcache_admin/src/Stats/McrouterStatsObject.php @@ -0,0 +1,123 @@ +<?php + +namespace Drupal\memcache_admin\Stats; + +use Drupal\Core\StringTranslation\StringTranslationTrait; + +/** + * Class MemcacheStats. + * + * @package Drupal\memcache_admin\Stats + */ +class McrouterStatsObject extends MemcacheStatsObject implements MemcacheStatsInterface { + + use StringTranslationTrait; + + /** + * @var array $stats + */ + protected $stats; + + public function __construct(array $raw_stats) { + $this->stats = $raw_stats; + } + + /** + * @inheritDoc + */ + public function getExtension(): string { + return isset($this->stats['version']) ?? self::NA; + } + + /** + * @inheritDoc + */ + public function getServerTime(): string { + return isset($this->stats['time']) ? \Drupal::service('date.formatter')->format($this->stats['time']) : self::NA; + } + + /** + * Statistics report: format total and open connections. + */ + public function getConnections() { + return self::NA; + } + + /** + * @inheritDoc + */ + public function getCurrentConnections(): string { + return self::NA; + } + + /** + * @inheritDoc + */ + public function getTotalConnections(): string { + return self::NA; + } + + /** + * Statistics report: calculate # of get cmds, broken down by hits and misses. + */ + public function getGets(): string { + return self::NA; + } + + /** + * @inheritDoc + */ + public function getCounters(): string { + return self::NA; + } + + /** + * @inheritDoc + */ + public function getTransferred(): string { + return self::NA; + } + + /** + * @inheritDoc + */ + public function getConnectionAvg(): string { + return self::NA; + } + + /** + * @inheritDoc + */ + public function getMemory(): string { + return self::NA; + } + + /** + * @inheritDoc + */ + public function getEvictions(): string { + return isset($this->stats['evictions']) ? number_format($this->stats['evictions']) : self::NA; + } + + /** + * @inheritDoc + */ + public function setRaw(array $raw_data) { + $this->stats = $raw_data; + } + + /** + * @inheritDoc + */ + public function getRaw(): array { + return $this->stats; + } + + /** + * @inheritDoc + */ + public function getVersion(): string { + return isset($this->stats['version']) ? (string)$this->stats['version'] : self::NA; + } + +} \ No newline at end of file diff --git a/memcache_admin/src/Stats/MemcacheStatsInterface.php b/memcache_admin/src/Stats/MemcacheStatsInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..7659558974552d2563e4ed0928deb2620fda9717 --- /dev/null +++ b/memcache_admin/src/Stats/MemcacheStatsInterface.php @@ -0,0 +1,103 @@ +<?php + +namespace Drupal\memcache_admin\Stats; + +/** + * Defines the Memcache connection interface. + */ +interface MemcacheStatsInterface { + + /** + * Sets an array of raw data for the memcache server. + * + * @param array $raw_data + * + * @return void + */ + public function setRaw(array $raw_data); + + /** + * Returns raw data from the memcache server. + * + * @return array + */ + public function getRaw(): array; + + /** + * Returns the memcache server version. + * + * @return string + */ + public function getVersion(): string; + + /** + * Returns the uptime for the memcache server. + * + * @return string + */ + public function getUptime(): string; + + /** + * Returns the PECL extension for the memcache server. + * + * @return string + */ + public function getExtension(): string; + + /** + * Returns the total connections for the memcache server. + * + * @return string + */ + public function getTotalConnections(): string; + + /** + * Returns the cache sets for the memcache server. + * + * @return string + */ + public function getSets(): string; + + /** + * Returns the cache gets for the memcache server. + * + * @return string + */ + public function getGets(): string; + + /** + * Returns the counters for the memcache server. + * + * @return string + */ + public function getCounters(): string; + + /** + * Returns the data transferred for the memcache server. + * + * @return string + */ + public function getTransferred(): string; + + /** + * Returns the connection averages for the memcache server. + * + * @return string + */ + public function getConnectionAvg(): string; + + /** + * Returns the memory available for the memcache server. + * + * @return string + */ + public function getMemory(): string; + + /** + * Returns the evictions for the memcache server. + * + * @return string + */ + public function getEvictions(): string; + +} \ No newline at end of file diff --git a/memcache_admin/src/Stats/MemcacheStatsObject.php b/memcache_admin/src/Stats/MemcacheStatsObject.php new file mode 100644 index 0000000000000000000000000000000000000000..df3a4119c8ad492488267b64cbdd36aa2e61c531 --- /dev/null +++ b/memcache_admin/src/Stats/MemcacheStatsObject.php @@ -0,0 +1,285 @@ +<?php + +namespace Drupal\memcache_admin\Stats; + +use Drupal\Core\StringTranslation\StringTranslationTrait; + +/** + * Class MemcacheStats. + * + * @package Drupal\memcache_admin\Stats + */ +class MemcacheStatsObject implements MemcacheStatsInterface { + + use StringTranslationTrait; + + /** + * Stat Not Available + */ + CONST NA = 'n/a'; + + /** + * @var array $stats + */ + protected $stats; + + public function __construct(array $raw_stats) { + $this->stats = $raw_stats; + } + + /** + * @inheritDoc + */ + public function getUptime(): string { + return isset($this->stats['uptime']) ? \Drupal::service('date.formatter')->formatInterval($this->stats['uptime']) : self::NA; + } + + /** + * @inheritDoc + */ + public function getExtension(): string { + return isset($this->stats['extension']) ?? self::NA; + } + + /** + * @inheritDoc + */ + public function getServerTime(): string { + return isset($this->stats['time']) ? \Drupal::service('date.formatter')->format($this->stats['time']) : self::NA; + } + + /** + * Statistics report: format total and open connections. + */ + public function getConnections() { + if (!isset($this->stats['curr_connections']) || + !isset($this->stats['total_connections']) + ) { + return self::NA; + } + + return $this->t( + '@current open of @total total', + [ + '@current' => number_format($this->stats['curr_connections']), + '@total' => number_format($this->stats['total_connections']), + ] + ); + } + + /** + * @inheritDoc + */ + public function getCurrentConnections(): string { + return isset($this->stats['curr_connections']) ?? '0'; + } + + /** + * @inheritDoc + */ + public function getTotalConnections(): string { + return isset($this->stats['total_connections']) ?? '0'; + } + + /** + * Statistics report: calculate # of set cmds and total cmds. + */ + public function getSets(): string { + if (!isset($this->stats['cmd_set'])) { + return self::NA; + } + + if (($this->stats['cmd_set'] + $this->stats['cmd_get']) == 0) { + $sets = 0; + } + else { + $sets = $this->stats['cmd_set'] / ($this->stats['cmd_set'] + $this->stats['cmd_get']) * 100; + } + if (!isset($this->stats['uptime'])) { + $average = 0; + } + else { + $average = $sets / $this->stats['uptime']; + } + return $this->t( + '@average/s; @set sets (@sets%) of @total commands', + [ + '@average' => number_format($average, 2), + '@sets' => number_format($sets, 2), + '@set' => number_format($this->stats['cmd_set']), + '@total' => number_format($this->stats['cmd_set'] + $this->stats['cmd_get']), + ] + ); + } + + /** + * Statistics report: calculate # of get cmds, broken down by hits and misses. + */ + public function getGets(): string { + if (!isset($this->stats['cmd_set']) || !isset($this->stats['cmd_get'])) { + return self::NA; + } + else { + $get = $this->stats['cmd_get']; + $set = $this->stats['cmd_set']; + $hits = isset($this->stats['get_hits']) ?? 0; + $misses = isset($this->stats['get_misses']) ?? 0; + } + + if (($set + $get) == 0) { + $gets = 0; + } + else { + $gets = $get / ($set + $get) * 100; + } + if (empty($stats['uptime'])) { + $average = 0; + } + else { + $average = $get / $stats['uptime']; + } + return $this->t( + '@average/s; @total gets (@gets%); @hit hits (@percent_hit%) @miss misses (@percent_miss%)', + [ + '@average' => number_format($average, 2), + '@gets' => number_format($gets, 2), + '@hit' => number_format($hits), + '@percent_hit' => ($get > 0 ? number_format($hits / $get * 100, 2) : '0.00'), + '@miss' => number_format($misses), + '@percent_miss' => ($get > 0 ? number_format($misses / $get * 100, 2) : '0.00'), + '@total' => number_format($get), + ] + ); + } + + /** + * @inheritDoc + */ + public function getCounters(): string { + $incr_hits = isset($this->stats['incr_hits']) ?? 0; + $incr_misses = isset($this->stats['incr_misses']) ?? 0; + $decr_hits = isset($this->stats['decr_hits']) ?? 0; + $decr_misses = isset($this->stats['decr_misses']) ?? 0; + + return $this->t( + '@incr increments, @decr decrements', + [ + '@incr' => number_format($incr_hits + $incr_misses), + '@decr' => number_format($decr_hits + $decr_misses), + ] + ); + } + + /** + * @inheritDoc + */ + public function getTransferred(): string { + $read = isset($this->stats['bytes_read']) ?? 0; + $write = isset($this->stats['bytes_written']) ?? 0; + + if ($write == 0) { + $written = 0; + } + else { + $written = $read / $write * 100; + } + return $this->t( + '@to:@from (@written% to cache)', + [ + '@to' => format_size((int) $read), + '@from' => format_size((int) $write), + '@written' => number_format($written, 2), + ] + ); + } + + /** + * @inheritDoc + */ + public function getConnectionAvg(): string { + if (!isset($this->stats['total_connections']) || + !isset($this->stats['cmd_get']) || + !isset($this->stats['cmd_set']) || + !isset($this->stats['bytes_written']) || + !isset($this->stats['bytes_read']) + ) { + return self::NA; + } + if ($this->stats['total_connections'] == 0) { + $get = 0; + $set = 0; + $read = 0; + $write = 0; + } + else { + $get = $this->stats['cmd_get'] / $this->stats['total_connections']; + $set = $this->stats['cmd_set'] / $this->stats['total_connections']; + $read = $this->stats['bytes_written'] / $this->stats['total_connections']; + $write = $this->stats['bytes_read'] / $this->stats['total_connections']; + } + return $this->t( + '@read in @get gets; @write in @set sets', + [ + '@get' => number_format($get, 2), + '@set' => number_format($set, 2), + '@read' => format_size(number_format($read, 2)), + '@write' => format_size(number_format($write, 2)), + ] + ); + } + + /** + * @inheritDoc + */ + public function getMemory(): string { + if (!isset($this->stats['limit_maxbytes']) || + !isset($this->stats['bytes']) + ) { + return self::NA; + } + + if ($this->stats['limit_maxbytes'] == 0) { + $percent = 0; + } + else { + $percent = 100 - $this->stats['bytes'] / $this->stats['limit_maxbytes'] * 100; + } + return $this->t( + '@available (@percent%) of @total', + [ + '@available' => format_size($this->stats['limit_maxbytes'] - $this->stats['bytes']), + '@percent' => number_format($percent, 2), + '@total' => format_size($this->stats['limit_maxbytes']), + ] + ); + } + + /** + * @inheritDoc + */ + public function getEvictions(): string { + return isset($this->stats['evictions']) ? number_format($this->stats['evictions']) : self::NA; + } + + /** + * @inheritDoc + */ + public function setRaw(array $raw_data) { + $this->stats = $raw_data; + } + + /** + * @inheritDoc + */ + public function getRaw(): array { + return $this->stats; + } + + /** + * @inheritDoc + */ + public function getVersion(): string { + return isset($this->stats['version']) ? (string)$this->stats['version'] : self::NA; + } + +} \ No newline at end of file