Newer
Older
<?php
/**
* @file
* For the collection and display of memcache stats.
*
* This module adds a small .js file to makes sure that the HTML displaying the
* stats is inside of the <body> part of the HTML document.
*/
/**
* Implements hook_init().
*/
function memcache_admin_init() {
global $user;
if (($user->uid == 0) || strstr($_SERVER['PHP_SELF'], 'update.php') || (isset($_GET['q']) && (in_array($_GET['q'], array('upload/js', 'admin/content/node-settings/rebuild')) || substr($_GET['q'], 0, strlen('system/files')) == 'system/files' || substr($_GET['q'], 0, strlen('batch')) == 'batch' || strstr($_GET['q'], 'autocomplete')))) {
// update.php relies on standard error handler.
}
else {
if ($user->uid) {
drupal_add_js(drupal_get_path('module', 'memcache_admin') . '/memcache.js');
}
register_shutdown_function('memcache_admin_shutdown');
}
}
/**
*/
return array(
'access memcache statistics' => array(
'title' => t('Access memcache statistics'),
),
'access slab cachedump' => array(
'title' => t('Access cachedump of memcache slab'),
'restrict access' => TRUE,
),
}
/**
*/
function memcache_admin_menu() {
$items['admin/config/system/memcache'] = array(
'title' => 'Memcache',
'description' => 'Show or hide memcache statistics at the bottom of each page.',
'page callback' => 'drupal_get_form',
'page arguments' => array('memcache_admin_admin_settings'),
'access arguments' => array('administer site configuration'),
);
$items['admin/reports/memcache'] = array(
'title' => 'Memcache statistics',
'description' => "View statistics for all configured memcache servers.",
'page callback' => 'memcache_admin_stats',
'access arguments' => array('access memcache statistics'),
'weight' => 1,
);
$memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
$clusters = array();
Jeremy Andrews
committed
foreach ($memcache_servers as $server => $bin) {
if ($cluster = dmemcache_object_cluster($bin)) {
$clusters[$cluster]['servers'][] = $server;
$clusters[$cluster]['bin'] = _memcache_admin_get_bin_for_cluster($cluster['cluster']);
}
}
$count = 0;
foreach ($clusters as $cluster => $cluster_info) {
if ($cluster_info['bin']) {
if (empty($current_cluster)) {
$current_cluster = arg(3);
if (empty($current_cluster)) {
$current_cluster = $cluster;
}
}
$items["admin/reports/memcache/$cluster"] = array(
'title' => $cluster,
'type' => $count == 0 ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
'page callback' => 'memcache_admin_stats',
'page arguments' => array($cluster),
'access arguments' => array('access memcache statistics'),
'weight' => $count++,
);
foreach ($cluster_info['servers'] as $server) {
$items["admin/reports/memcache/$cluster/$server"] = array(
'title' => check_plain($server),
'type' => MENU_CALLBACK,
'page callback' => 'memcache_admin_stats_raw',
'page arguments' => array($cluster, $server),
'access arguments' => array('access memcache statistics'),
);
foreach (memcache_admin_stats_types($cluster) as $type) {
$items["admin/reports/memcache/$cluster/$server/$type"] = array(
'type' => MENU_CALLBACK,
'page callback' => 'memcache_admin_stats_raw',
'page arguments' => array($cluster, $server, $type),
'title' => $type,
'access arguments' => array('access memcache statistics'),
);
}
}
}
}
return $items;
}
/**
* Settings form.
*/
function memcache_admin_admin_settings() {
Jeremy Andrews
committed
$form['show_memcache_statistics'] = array(
'#type' => 'checkbox',
'#title' => t('Show memcache statistics at the bottom of each page'),
Jeremy Andrews
committed
'#default_value' => variable_get('show_memcache_statistics', FALSE),
'#description' => t("These statistics will be visible to users with the 'access memcache statistics' permission."),
);
return system_settings_form($form);
}
/**
* Helper function. Returns the bin name.
*/
function _memcache_admin_default_bin($bin) {
if ($bin == 'default') {
return 'cache';
}
return $bin;
}
/**
* Statistics report: format total and open connections.
*/
function _memcache_admin_stats_connections($stats) {
return t('!current open of !total total', array('!current' => number_format($stats['curr_connections']), '!total' => number_format($stats['total_connections'])));
}
/**
* Statistics report: calculate # of set cmds and total cmds.
*/
function _memcache_admin_stats_sets($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 t('!average/s; !set sets (!sets%) of !total commands', array('!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.
*/
function _memcache_admin_stats_gets($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 t('!average/s; !total gets (!gets%); !hit hits (!percent_hit%) !miss misses (!percent_miss%)', array('!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.
*/
function _memcache_admin_stats_counters($stats) {
Jeremy Andrews
committed
if (!is_array($stats)) {
$stats = array();
}
$stats += array(
'incr_hits' => 0,
'incr_misses' => 0,
'decr_hits' => 0,
'decr_misses' => 0,
);
return t('!incr increments, !decr decrements', array('!incr' => number_format($stats['incr_hits'] + $stats['incr_misses']), '!decr' => number_format($stats['decr_hits'] + $stats['decr_misses'])));
}
/**
* Statistics report: calculate bytes transferred.
*/
function _memcache_admin_stats_transfer($stats) {
if ($stats['bytes_written'] == 0) {
$written = 0;
}
else {
$written = $stats['bytes_read'] / $stats['bytes_written'] * 100;
}
return t('!to:!from (!written% to cache)', array('!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.
*/
function _memcache_admin_stats_average($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 t('!read in !get gets; !write in !set sets', array('!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.
*/
function _memcache_admin_stats_memory($stats) {
if ($stats['limit_maxbytes'] == 0) {
$percent = 0;
}
else {
$percent = 100 - $stats['bytes'] / $stats['limit_maxbytes'] * 100;
}
return t('!available (!percent%) of !total', array('!available' => format_size($stats['limit_maxbytes'] - $stats['bytes']), '!percent' => number_format($percent, 2), '!total' => format_size($stats['limit_maxbytes'])));
}
/**
* Helper function, reverse map the memcache_bins variable.
*/
function memcache_admin_bin_mapping($bin = 'cache') {
$bins = array_flip(variable_get('memcache_bins', array('cache' => 'default')));
if (isset($bins[$bin])) {
return $bins[$bin];
}
else {
// The default bin is 'cache'.
return _memcache_admin_default_bin($bin);
}
}
* Callback for the Memcache Stats page.
*
* @return string
* The page output.
function memcache_admin_stats($bin = 'default') {
if (!class_exists('MemCacheDrupal', FALSE)) {
return t('There is a problem with your memcache configuration. Please review !readme for help resolving the following issue: %error.', array('!readme' => l('README.txt', 'http://cgit.drupalcode.org/memcache/tree/README.txt?id=refs/heads;id2=7.x-1.x'), '%error' => t('cache_backends not properly configured in settings.php, failed to load required file memcache.inc')));
}
if ($memcache_debug_log = variable_get('memcache_debug_log', FALSE)) {
if (variable_get('memcache_debug_verbose', FALSE)) {
$verbose = t('verbose');
}
else {
$verbose = '';
}
drupal_set_message(t('You are writing !verbose debug logs to !debug_log.', array('!verbose' => $verbose, '!debug_log' => $memcache_debug_log)), 'warning');
}
$bin = memcache_admin_bin_mapping($bin);
$output = '';
$server = array();
$stats = dmemcache_stats($bin, 'default', TRUE);
Jeremy Andrews
committed
if (empty($stats[$bin])) {
// Failed to load statistics. Provide a useful error about where to get
// more information and help.
Jeremy Andrews
committed
drupal_set_message(t('Failed to retreive statistics. There may be a problem with your Memcache configuration. Please review %readme and !more for more information.', array('%readme' => 'README.txt', 'admin/reports/status', '!more' => module_exists('memcache') ? t('visit the Drupal admin !status page', array('!status' => l(t('status report'), 'admin/reports/status'))) : t('!enable the memcache module', array('!enable' => l(t('enable'), 'admin/modules', array('fragment' => 'edit-modules-performance-and-scalability')))))), 'error');
}
else {
Jeremy Andrews
committed
$stats = $stats[$bin];
$aggregate = array_pop($stats);
$mc = dmemcache_object($bin);
if ($mc instanceof Memcached) {
$version = t('Memcached v!version', array('!version' => phpversion('Memcached')));
}
elseif ($mc instanceof Memcache) {
$version = t('Memcache v!version', array('!version' => phpversion('Memcache')));
}
else {
$version = t('Unknown');
drupal_set_message(t('Failed to detect the memcache PECL extension.'), 'error');
}
$memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
foreach ($stats as $server => $statistics) {
if (empty($statistics['uptime'])) {
drupal_set_message(t('Failed to connect to server at %address.', array('%address' => $server)), 'error');
}
else {
Jeremy Andrews
committed
$servers[] = $server;
$data['server_overview'][$server] = t('v!version running !uptime', array('!version' => check_plain($statistics['version']), '!uptime' => format_interval($statistics['uptime'])));
$data['server_pecl'][$server] = t('n/a');
$data['server_serialize'][$server] = t('n/a');
$data['server_time'][$server] = format_date($statistics['time']);
$data['server_connections'][$server] = _memcache_admin_stats_connections($statistics);
$data['cache_sets'][$server] = _memcache_admin_stats_sets($statistics);
$data['cache_gets'][$server] = _memcache_admin_stats_gets($statistics);
$data['cache_counters'][$server] = _memcache_admin_stats_counters($statistics);
$data['cache_transfer'][$server] = _memcache_admin_stats_transfer($statistics);
$data['cache_average'][$server] = _memcache_admin_stats_average($statistics);
$data['memory_available'][$server] = _memcache_admin_stats_memory($statistics);
$data['memory_evictions'][$server] = number_format($statistics['evictions']);
// Don't display aggregate totals if there's only one server.
if (count($servers) == 1) {
$aggregate = array();
}
// Build a custom report array.
$report = array();
// Report server uptime.
$item = array(
'label' => t('Uptime'),
'servers' => $data['server_overview'],
);
if (count($aggregate)) {
$item['total'] = t('n/a');
$report['uptime'][] = $item;
// Report server PECL extension.
$item = array(
'label' => t('PECL extension'),
);
if (count($aggregate)) {
$item['servers'] = $data['server_pecl'];
$item['total'] = $version;
else {
$item['servers'] = array($servers[0] => $version);
}
$report['uptime'][] = $item;
// Report which serialize function is being used.
$item = array(
'label' => t('Serialize function')
);
$serialize_function = dmemcache_serialize();
if ($serialize_function != 'serialize') {
$serialize_function = t('!function v!version', array('!function' => $serialize_function, '!version' => phpversion(dmemcache_serialize_extension())));
}
if (count($aggregate)) {
$item['servers'] = $data['server_serialize'];
$item['total'] = $serialize_function;
}
else {
$item['servers'] = array($servers[0] => $serialize_function);
}
$report['uptime'][] = $item;
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
// Report server time.
$item = array(
'label' => t('Time'),
'servers' => $data['server_time'],
);
if (count($aggregate)) {
$item['total'] = t('n/a');
}
$report['uptime'][] = $item;
// Report number of connections.
$item = array(
'label' => t('Connections'),
'servers' => $data['server_connections'],
);
if (count($aggregate)) {
$item['total'] = _memcache_admin_stats_connections($aggregate);
}
$report['uptime'][] = $item;
$stats = array(
'sets' => t('Sets'),
'gets' => t('Gets'),
'counters' => t('Counters'),
'transfer' => t('Transferred'),
'average' => t('Per-connection average'),
);
foreach ($stats as $type => $label) {
$item = array(
'label' => $label,
'servers' => $data["cache_{$type}"],
);
if (count($aggregate)) {
$func = "_memcache_admin_stats_{$type}";
$item['total'] = $func($aggregate);
$report['stats'][] = $item;
// Report on available memory.
$item = array(
'label' => t('Available memory'),
'servers' => $data['memory_available'],
);
if (count($aggregate)) {
$item['total'] = _memcache_admin_stats_memory($aggregate);
}
$report['memory'][] = $item;
// Report on memory evictions.
$item = array(
'label' => t('Evictions'),
'servers' => $data['memory_evictions'],
if (count($aggregate)) {
$item['total'] = number_format($aggregate['evictions']);
}
$report['memory'][] = $item;
$output = theme('memcache_admin_stats_table', array(
'bin' => $bin,
'servers' => $servers,
'report' => $report,
));
return $output;
}
/**
* Callback for the server statistics page.
*/
function memcache_admin_stats_raw($bin, $server, $type = 'default') {
$cluster = memcache_admin_bin_mapping($bin);
$slab = (int) arg(7);
if (arg(6) == 'cachedump' && !empty($slab) && user_access('access slab cachedump')) {
$stats = dmemcache_stats($cluster, arg(7), FALSE);
}
else {
$stats = dmemcache_stats($cluster, $type, FALSE);
}
$breadcrumbs = array(
l(t('Home'), NULL),
l(t('Administer'), 'admin'),
l(t('Reports'), 'admin/reports'),
l(t('Memcache'), 'admin/reports/memcache'),
l(t($bin), "admin/reports/memcache/$bin"),
);
if ($type == 'slabs' && arg(6) == 'cachedump' && user_access('access slab cachedump')) {
$breadcrumbs[] = l($server, "admin/reports/memcache/$bin/$server");
$breadcrumbs[] = l(t('slabs'), "admin/reports/memcache/$bin/$server/$type");
}
drupal_set_breadcrumb($breadcrumbs);
if (isset($stats[$cluster][$server]) && is_array($stats[$cluster][$server]) && count($stats[$cluster][$server])) {
$output = theme('memcache_admin_stats_raw_table', array(
'cluster' => $cluster,
'server' => $server,
'stats' => $stats[$cluster][$server],
'type' => $type,
));
}
elseif ($type == 'slabs' && is_array($stats[$cluster]) && count($stats[$cluster])) {
$output = theme('memcache_admin_stats_raw_table', array(
'cluster' => $cluster,
'server' => $server,
'stats' => $stats[$cluster],
'type' => $type,
));
$output = theme('memcache_admin_stats_raw_table', array(
'cluster' => $cluster,
'server' => $server,
'stats' => array(),
'type' => $type,
));
drupal_set_message(t('No @type statistics for this bin.', array('@type' => $type)));
}
return $output;
}
/**
*/
function memcache_admin_theme() {
return array(
'memcache_admin_stats_table' => array(
'variables' => array(
'bin' => NULL,
'servers' => NULL,
'report' => NULL,
),
),
'memcache_admin_stats_raw_table' => array(
'variables' => array(
'bin' => NULL,
'server' => NULL,
'stats' => NULL,
'type' => NULL,
),
),
);
}
/**
* Theme function for rendering the output from memcache_admin_stats.
*/
function theme_memcache_admin_stats_table($variables) {
$bin = $variables['bin'];
$servers = $variables['servers'];
$stats = $variables['report'];
$output = '';
$links = array();
$memcache_bins = variable_get('memcache_bins', array('cache' => 'default'));
foreach ($servers as $server) {
$link_bin = $memcache_bins[$bin];
$links[] = l($server, check_plain("admin/reports/memcache/$link_bin/$server"));
}
if (count($servers) > 1) {
$headers = array_merge(array('', t('Totals')), $links);
}
else {
$headers = array_merge(array(''), $links);
}
foreach ($stats as $table => $data) {
$rows = array();
foreach ($data as $row) {
$r = array();
$r[] = $row['label'];
if (isset($row['total'])) {
$r[] = $row['total'];
}
foreach ($row['servers'] as $server) {
$r[] = $server;
}
$rows[] = $r;
}
$output .= theme('table', array('header' => $headers, 'rows' => $rows));
}
return $output;
}
/**
* Returns an array of available statistics types.
*/
function memcache_admin_stats_types($bin) {
module_load_include('inc', 'memcache', 'dmemcache');
if ($mc = dmemcache_object($bin)) {
if ($mc instanceof Memcache) {
// TODO: Determine which versions of the PECL memcache extension have
// these other stats types: 'malloc', 'maps', optionally detect this
// version and expose them. These stats are "subject to change without
// warning" unfortunately.
return array('default', 'slabs', 'items', 'sizes');
}
else {
// The Memcached PECL extension only offers the default statistics.
return array('default');
}
}
catch
committed
else {
return array();
}
Jeremy Andrews
committed
/**
* Theme function to produce a table of statistics.
*/
function theme_memcache_admin_stats_raw_table($variables) {
$cluster = $variables['cluster'];
$server = $variables['server'];
$stats = $variables['stats'];
$current_type = isset($variables['type']) ? $variables['type'] : 'default';
$memcache_bins = variable_get('memcache_bins', array());
$bin = isset($memcache_bins[$cluster]) ? $memcache_bins[$cluster] : 'default';
// Provide navigation for the various memcache stats types.
if (count(memcache_admin_stats_types($bin)) > 1) {
foreach (memcache_admin_stats_types($bin) as $type) {
if ($current_type == $type) {
$links[] = '<strong>' . l(t($type), "admin/reports/memcache/$bin/$server/" . ($type == 'default' ? '' : $type)) . '</strong>';
$links[] = l(t($type), "admin/reports/memcache/$bin/$server/" . ($type == 'default' ? '' : $type));
}
}
}
$output = !empty($links) ? implode($links, ' | ') : '';
$headers = array(t('Property'), t('Value'));
$rows = array();
// Items are returned as an array within an array within an array. We step
// in one level to properly display the contained statistics.
if ($current_type == 'items' && isset($stats['items'])) {
$stats = $stats['items'];
}
foreach ($stats as $key => $value) {
// Add navigation for getting a cachedump of individual slabs.
if (($current_type == 'slabs' || $current_type == 'items') && is_int($key) && user_access('access slab cachedump')) {
$key = l($key, "admin/reports/memcache/$bin/$server/slabs/cachedump/$key");
}
if (is_array($value)) {
$rs = array();
foreach ($value as $k => $v) {
// Format timestamp when viewing cachedump of individual slabs.
if ($current_type == 'slabs' && user_access('access slab cachedump') && arg(6) == 'cachedump' && $k == 0) {
$k = t('Size');
$v = format_size($v);
}
elseif ($current_type == 'slabs' && user_access('access slab cachedump') && arg(6) == 'cachedump' && $k == 1) {
$k = t('Expire');
$full_stats = dmemcache_stats($cluster, 'default');
$infinite = $full_stats[$cluster][$server]['time'] - $full_stats[$cluster][$server]['uptime'];
if ($v == $infinite) {
$v = t('infinite');
}
else {
$v = t('in @time', array('@time' => format_interval($v - time())));
}
}
$rs[] = array(check_plain($k), check_plain($v));
$rows[] = array($key, theme('table', array('rows' => $rs)));
}
else {
$rows[] = array(check_plain($key), check_plain($value));
}
}
$output .= theme('table', array('header' => $headers, 'rows' => $rows));
return $output;
}
/**
* Retrieve the bin for any given cluster.
*
* @param string $cluster
* Cluster ID
*
* @return string
* The name of the bin.
*/
function _memcache_admin_get_bin_for_cluster($cluster) {
static $cluster_map = array();
if (!isset($cluster_map[$cluster])) {
$memcache_bins = variable_get('memcache_bins', array());
if ($mapping = array_search($cluster, $memcache_bins)) {
$cluster_map[$cluster] = $mapping;
}
else {
$cluster_map[$cluster] = 'default';
}
}
return $cluster_map[$cluster];
}
/**
* Helper function. Calculate a percentage.
*/
Jeremy Andrews
committed
function _memcache_admin_stats_percent($a, $b) {
if ($a == 0) {
return 0;
}
elseif ($b == 0) {
Jeremy Andrews
committed
return 100;
}
else {
return $a / ($a + $b) * 100;
}
}
/**
* Displays memcache stats in the footer. This is run as a shutdown function.
*
* @see memcache_admin_init()
*/
function memcache_admin_shutdown() {
Jeremy Andrews
committed
global $_dmemcache_stats;
catch
committed
// 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.
if (!function_exists('theme_get_registry') || !theme_get_registry()) {
return;
}
// Try not to break non-HTML pages.
Jeremy Andrews
committed
if (function_exists('drupal_get_http_header')) {
$header = drupal_get_http_header('content-type');
if ($header) {
$formats = array(
'xml',
'javascript',
'json',
'plain',
'image',
'application',
'csv',
'x-comma-separated-values',
);
Jeremy Andrews
committed
foreach ($formats as $format) {
if (strstr($header, $format)) {
return;
}
}
}
Jeremy Andrews
committed
else {
// Workaround for CKEditor, see https://www.drupal.org/node/2556999
return;
}
}
Jeremy Andrews
committed
if (variable_get('show_memcache_statistics', FALSE) && function_exists('user_access') && user_access('access memcache statistics')) {
Jeremy Andrews
committed
$output = '';
if (!empty($_dmemcache_stats['ops'])) {
foreach ($_dmemcache_stats['ops'] as $row => $stats) {
$_dmemcache_stats['ops'][$row][0] = check_plain($stats[0]);
$_dmemcache_stats['ops'][$row][1] = number_format($stats[1], 2);
$hits = number_format(_memcache_admin_stats_percent($stats[2], $stats[3]), 1);
$misses = number_format(_memcache_admin_stats_percent($stats[3], $stats[2]), 1);
$_dmemcache_stats['ops'][$row][2] = number_format($stats[2]) . " ($hits%)";
$_dmemcache_stats['ops'][$row][3] = number_format($stats[3]) . " ($misses%)";
}
$variables = array(
'header' => array(
t('operation'),
t('total ms'),
t('total hits'),
t('total misses'),
),
'rows' => $_dmemcache_stats['ops'],
);
Jeremy Andrews
committed
$output .= theme('table', $variables);
}
if (!empty($_dmemcache_stats['all'])) {
foreach ($_dmemcache_stats['all'] as $row => $stats) {
$_dmemcache_stats['all'][$row][1] = check_plain($stats[1]);
$_dmemcache_stats['all'][$row][2] = check_plain($stats[2]);
$_dmemcache_stats['all'][$row][3] = check_plain($stats[3]);
$variables = array(
'header' => array(
t('ms'),
t('operation'),
t('bin'),
t('key'),
t('status'),
),
'rows' => $_dmemcache_stats['all'],
);
Jeremy Andrews
committed
$output .= theme('table', $variables);
Jeremy Andrews
committed
}
if (!empty($output)) {
// This makes sure all of the HTML is within the <body> even though this
// <script> is outside it.
print '<div id="memcache-devel"><h2>' . t('Memcache statistics') . '</h2>' . $output . '</div>';