3 namespace Drupal\memcache_admin\Controller;
5 use Drupal\Core\Controller\ControllerBase;
6 use Drupal\Core\Datetime\DateFormatter;
9 use Drupal\Component\Render\HtmlEscapedText;
12 * Memcache Statistics.
14 class MemcacheStatisticsController extends ControllerBase {
17 * Callback for the Memcache Stats page.
25 public function statsTable($bin = 'default') {
29 // Get the statistics.
30 $bin = $this->binMapping($bin);
31 /** @var $memcache \Drupal\memcache\DrupalMemcacheInterface */
32 $memcache = \Drupal::service('memcache.factory')->get($bin, TRUE);
33 $stats = $memcache->stats($bin, 'default', TRUE);
35 if (empty($stats[$bin])) {
37 // Break this out to make drupal_set_message easier to read.
38 $additional_message = $this->t(
39 '@enable the memcache module',
41 '@enable' => Link::fromTextAndUrl(t('enable'), Url::fromUri('base:/admin/modules', ['fragment' => 'edit-modules-performance-and-scalability'])),
44 if (\Drupal::moduleHandler()->moduleExists('memcache')) {
45 $additional_message = $this->t(
46 'visit the Drupal admin @status page',
48 '@status' => Link::fromTextAndUrl(t('status report'), Url::fromUri('base:/admin/reports/status')),
53 // Failed to load statistics. Provide a useful error about where to get
54 // more information and help.
57 'There may be a problem with your Memcache configuration. Please review @readme and :more for more information.',
59 '@readme' => 'README.txt',
60 ':more' => $additional_message,
67 if (count($stats[$bin])) {
68 $stats = $stats[$bin];
69 $aggregate = array_pop($stats);
71 if ($memcache->getMemcache() instanceof \Memcached) {
72 $version = t('Memcached v@version', ['@version' => phpversion('Memcached')]);
74 elseif ($memcache->getMemcache() instanceof \Memcache) {
75 $version = t('Memcache v@version', ['@version' => phpversion('Memcache')]);
78 $version = t('Unknown');
79 drupal_set_message(t('Failed to detect the memcache PECL extension.'), 'error');
82 foreach ($stats as $server => $statistics) {
83 if (empty($statistics['uptime'])) {
84 drupal_set_message(t('Failed to connect to server at :address.', [':address' => $server]), 'error');
89 $data['server_overview'][$server] = t('v@version running @uptime', ['@version' => $statistics['version'], '@uptime' => \Drupal::service('date.formatter')->formatInterval($statistics['uptime'])]);
90 $data['server_pecl'][$server] = t('n/a');
91 $data['server_time'][$server] = \Drupal::service('date.formatter')->format($statistics['time']);
92 $data['server_connections'][$server] = $this->statsConnections($statistics);
93 $data['cache_sets'][$server] = $this->statsSets($statistics);
94 $data['cache_gets'][$server] = $this->statsGets($statistics);
95 $data['cache_counters'][$server] = $this->statsCounters($statistics);
96 $data['cache_transfer'][$server] = $this->statsTransfer($statistics);
97 $data['cache_average'][$server] = $this->statsAverage($statistics);
98 $data['memory_available'][$server] = $this->statsMemory($statistics);
99 $data['memory_evictions'][$server] = number_format($statistics['evictions']);
104 // Build a custom report array.
108 'label' => t('Uptime'),
109 'servers' => $data['server_overview'],
112 'label' => t('PECL extension'),
113 'servers' => [$servers[0] => $version],
116 'label' => t('Time'),
117 'servers' => $data['server_time'],
120 'label' => t('Connections'),
121 'servers' => $data['server_connections'],
127 'label' => t('Available memory'),
128 'servers' => $data['memory_available'],
131 'label' => t('Evictions'),
132 'servers' => $data['memory_evictions'],
137 // Don't display aggregate totals if there's only one server.
138 if (count($servers) > 1) {
139 $report['uptime']['uptime']['total'] = t('n/a');
140 $report['uptime']['extension']['servers'] = $data['server_pecl'];
141 $report['uptime']['extension']['total'] = $version;
142 $report['uptime']['time']['total'] = t('n/a');
143 $report['uptime']['connections']['total'] = $this->statsConnections($aggregate);
144 $report['memory']['memory']['total'] = $this->statsMemory($aggregate);
145 $report['memory']['evictions']['total'] = number_format($aggregate['evictions']);
152 'counters' => t('Counters'),
153 'transfer' => t('Transferred'),
154 'average' => t('Per-connection average'),
157 foreach ($stats as $type => $label) {
158 $report['stats'][$type] = [
160 'servers' => $data["cache_{$type}"],
163 if (count($servers) > 1) {
164 $func = 'stats' . ucfirst($type);
165 $report['stats'][$type]['total'] = $this->{$func}($aggregate);
169 $output = $this->statsTablesOutput($bin, $servers, $report);
176 * Callback for the Memcache Stats page.
178 * @param string $cluster
179 * The Memcache cluster name.
180 * @param string $server
181 * The Memcache server name.
182 * @param string $type
183 * The type of statistics to retrieve when using the Memcache extension.
188 public function statsTableRaw($cluster, $server, $type = 'default') {
189 $cluster = $this->binMapping($cluster);
190 $server = str_replace('!', '/', $server);
192 $slab = \Drupal::routeMatch()->getParameter('slab');
193 $memcache = \Drupal::service('memcache.factory')->get($cluster, TRUE);
194 if ($type == 'slabs' && !empty($slab)) {
195 $stats = $memcache->stats($cluster, $slab, FALSE);
198 $stats = $memcache->stats($cluster, $type, FALSE);
201 // @codingStandardsIgnoreStart
202 // @todo - breadcrumb
204 // l(t('Home'), NULL),
205 // l(t('Administer'), 'admin'),
206 // l(t('Reports'), 'admin/reports'),
207 // l(t('Memcache'), 'admin/reports/memcache'),
208 // l(t($bin), "admin/reports/memcache/$bin"),
210 // if ($type == 'slabs' && arg(6) == 'cachedump' && user_access('access slab cachedump')) {
211 // $breadcrumbs[] = l($server, "admin/reports/memcache/$bin/$server");
212 // $breadcrumbs[] = l(t('slabs'), "admin/reports/memcache/$bin/$server/$type");
214 // drupal_set_breadcrumb($breadcrumbs);
215 // @codingStandardsIgnoreEnd
216 if (isset($stats[$cluster][$server]) && is_array($stats[$cluster][$server]) && count($stats[$cluster][$server])) {
217 $output = $this->statsTablesRawOutput($cluster, $server, $stats[$cluster][$server], $type);
219 elseif ($type == 'slabs' && is_array($stats[$cluster]) && count($stats[$cluster])) {
220 $output = $this->statsTablesRawOutput($cluster, $server, $stats[$cluster], $type);
223 $output = $this->statsTablesRawOutput($cluster, $server, [], $type);
224 drupal_set_message(t('No @type statistics for this bin.', ['@type' => $type]));
231 * Helper function, reverse map the memcache_bins variable.
233 private function binMapping($bin = 'cache') {
234 $memcache = \Drupal::service('memcache.factory')->get(NULL, TRUE);
235 $memcache_bins = $memcache->getBins();
237 $bins = array_flip($memcache_bins);
238 if (isset($bins[$bin])) {
242 return $this->defaultBin($bin);
247 * Helper function. Returns the bin name.
249 private function defaultBin($bin) {
250 if ($bin == 'default') {
258 * Statistics report: format total and open connections.
260 private function statsConnections($stats) {
262 '@current open of @total total',
264 '@current' => number_format($stats['curr_connections']),
265 '@total' => number_format($stats['total_connections']),
271 * Statistics report: calculate # of set cmds and total cmds.
273 private function statsSets($stats) {
274 if (($stats['cmd_set'] + $stats['cmd_get']) == 0) {
278 $sets = $stats['cmd_set'] / ($stats['cmd_set'] + $stats['cmd_get']) * 100;
280 if (empty($stats['uptime'])) {
284 $average = $sets / $stats['uptime'];
287 '@average/s; @set sets (@sets%) of @total commands',
289 '@average' => number_format($average, 2),
290 '@sets' => number_format($sets, 2),
291 '@set' => number_format($stats['cmd_set']),
292 '@total' => number_format($stats['cmd_set'] + $stats['cmd_get']),
298 * Statistics report: calculate # of get cmds, broken down by hits and misses.
300 private function statsGets($stats) {
301 if (($stats['cmd_set'] + $stats['cmd_get']) == 0) {
305 $gets = $stats['cmd_get'] / ($stats['cmd_set'] + $stats['cmd_get']) * 100;
307 if (empty($stats['uptime'])) {
311 $average = $stats['cmd_get'] / $stats['uptime'];
314 '@average/s; @total gets (@gets%); @hit hits (@percent_hit%) @miss misses (@percent_miss%)',
316 '@average' => number_format($average, 2),
317 '@gets' => number_format($gets, 2),
318 '@hit' => number_format($stats['get_hits']),
319 '@percent_hit' => ($stats['cmd_get'] > 0 ? number_format($stats['get_hits'] / $stats['cmd_get'] * 100, 2) : '0.00'),
320 '@miss' => number_format($stats['get_misses']),
321 '@percent_miss' => ($stats['cmd_get'] > 0 ? number_format($stats['get_misses'] / $stats['cmd_get'] * 100, 2) : '0.00'),
322 '@total' => number_format($stats['cmd_get']),
328 * Statistics report: calculate # of increments and decrements.
330 private function statsCounters($stats) {
331 if (!is_array($stats)) {
343 '@incr increments, @decr decrements',
345 '@incr' => number_format($stats['incr_hits'] + $stats['incr_misses']),
346 '@decr' => number_format($stats['decr_hits'] + $stats['decr_misses']),
352 * Statistics report: calculate bytes transferred.
354 private function statsTransfer($stats) {
355 if ($stats['bytes_written'] == 0) {
359 $written = $stats['bytes_read'] / $stats['bytes_written'] * 100;
362 '@to:@from (@written% to cache)',
364 '@to' => format_size((int) $stats['bytes_read']),
365 '@from' => format_size((int) $stats['bytes_written']),
366 '@written' => number_format($written, 2),
372 * Statistics report: calculate per-connection averages.
374 private function statsAverage($stats) {
375 if ($stats['total_connections'] == 0) {
382 $get = $stats['cmd_get'] / $stats['total_connections'];
383 $set = $stats['cmd_set'] / $stats['total_connections'];
384 $read = $stats['bytes_written'] / $stats['total_connections'];
385 $write = $stats['bytes_read'] / $stats['total_connections'];
388 '@read in @get gets; @write in @set sets',
390 '@get' => number_format($get, 2),
391 '@set' => number_format($set, 2),
392 '@read' => format_size(number_format($read, 2)),
393 '@write' => format_size(number_format($write, 2)),
399 * Statistics report: calculate available memory.
401 private function statsMemory($stats) {
402 if ($stats['limit_maxbytes'] == 0) {
406 $percent = 100 - $stats['bytes'] / $stats['limit_maxbytes'] * 100;
409 '@available (@percent%) of @total',
411 '@available' => format_size($stats['limit_maxbytes'] - $stats['bytes']),
412 '@percent' => number_format($percent, 2),
413 '@total' => format_size($stats['limit_maxbytes']),
419 * Generates render array for output.
421 private function statsTablesOutput($bin, $servers, $stats) {
422 $memcache = \Drupal::service('memcache.factory')->get(NULL, TRUE);
423 $memcache_bins = $memcache->getBins();
426 foreach ($servers as $server) {
428 // Convert socket file path so it works with an argument, this should
429 // have no impact on non-socket configurations. Convert / to !.
430 $links[] = Link::fromTextandUrl($server, Url::fromUri('base:/admin/reports/memcache/' . $memcache_bins[$bin] . '/' . str_replace('/', '!', $server)))->toString();
433 if (count($servers) > 1) {
434 $headers = array_merge(['', t('Totals')], $links);
437 $headers = array_merge([''], $links);
441 foreach ($stats as $table => $data) {
443 foreach ($data as $data_row) {
445 $row[] = $data_row['label'];
446 if (isset($data_row['total'])) {
447 $row[] = $data_row['total'];
449 foreach ($data_row['servers'] as $server) {
456 '#header' => $headers,
466 * Generates render array for output.
468 private function statsTablesRawOutput($cluster, $server, $stats, $type) {
469 $user = \Drupal::currentUser();
470 $current_type = isset($type) ? $type : 'default';
471 $memcache = \Drupal::service('memcache.factory')->get(NULL, TRUE);
472 $memcache_bins = $memcache->getBins();
473 $bin = isset($memcache_bins[$cluster]) ? $memcache_bins[$cluster] : 'default';
474 $slab = \Drupal::routeMatch()->getParameter('slab');
476 // Provide navigation for the various memcache stats types.
478 if (count($memcache->statsTypes())) {
479 foreach ($memcache->statsTypes() as $type) {
480 // @todo render array
481 $link = Link::fromTextandUrl($type, Url::fromUri('base:/admin/reports/memcache/' . $bin . '/' . str_replace('/', '!', $server) . '/' . ($type == 'default' ? '' : $type)))->toString();
482 if ($current_type == $type) {
483 $links[] = '<strong>' . $link . '</strong>';
492 '#markup' => !empty($links) ? implode($links, ' | ') : '',
499 $this->t('Property'),
506 // Items are returned as an array within an array within an array. We step
507 // in one level to properly display the contained statistics.
508 if ($current_type == 'items' && isset($stats['items'])) {
509 $stats = $stats['items'];
512 foreach ($stats as $key => $value) {
514 // Add navigation for getting a cachedump of individual slabs.
515 if (($current_type == 'slabs' || $current_type == 'items') && is_int($key) && $user->hasPermission('access slab cachedump')) {
516 $build['table'][$row]['key'] = [
518 '#title' => $this->t('Slab @slab', ['@slab' => $key]),
519 '#url' => Url::fromUri('base:/admin/reports/memcache/' . $bin . '/' . str_replace('/', '!', $server) . '/slabs/cachedump/' . $key),
523 $build['table'][$row]['key'] = ['#plain_text' => $key];
526 if (is_array($value)) {
528 $build['table'][$row]['value'] = ['#type' => 'table'];
529 foreach ($value as $k => $v) {
531 // Format timestamp when viewing cachedump of individual slabs.
532 if ($current_type == 'slabs' && $user->hasPermission('access slab cachedump') && !empty($slab) && $k == 0) {
533 $k = $this->t('Size');
534 $v = format_size($v);
536 elseif ($current_type == 'slabs' && $user->hasPermission('access slab cachedump') && !empty($slab) && $k == 1) {
537 $k = $this->t('Expire');
538 $full_stats = $memcache->stats($cluster);
539 $infinite = $full_stats[$cluster][$server]['time'] - $full_stats[$cluster][$server]['uptime'];
540 if ($v == $infinite) {
541 $v = $this->t('infinite');
544 $v = $this->t('in @time', ['@time' => \Drupal::service('date.formatter')->formatInterval($v - \Drupal::time()->getRequestTime())]);
547 $build['table'][$row]['value'][$subrow] = [
548 'key' => ['#plain_text' => $k],
549 'value' => ['#plain_text' => $v],
555 $build['table'][$row]['value'] = ['#plain_text' => $value];