Commit bc3a3775 authored by japerry's avatar japerry
Browse files

Issue 3192907 by japerry: Update Memcache Admin to work with mcrouter, other memcache servers.

parent c82f94aa
......@@ -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
......@@ -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;
}
}
<?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;
}
}
<?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;
}
/**