You are here

memcache_storage.module in Memcache Storage 7

Same filename and directory in other branches
  1. 8 memcache_storage.module

Provides hook implementation for Memcache Storage module.

File

memcache_storage.module
View source
<?php

/**
 * @file
 * Provides hook implementation for Memcache Storage module.
 */

/**
 * Constant definitions.
 */
define('MEMCACHE_STORAGE_MINIMUM_MEMCACHE_VERSION', '2.2.1');
define('MEMCACHE_STORAGE_MINIMUM_MEMCACHED_VERSION', '2.0.1');

/**
 * Implements hook_menu().
 */
function memcache_storage_menu() {
  $items['admin/config/development/memcache_storage'] = array(
    'title' => 'Memcache storage',
    'description' => t('Manage Memcache Storage settings'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'memcache_storage_settings_form',
    ),
    'access arguments' => array(
      'administer memcache storage',
    ),
    'file' => 'memcache_storage.admin.inc',
  );
  $items['memcache_storage/delete/%/%/%'] = array(
    'page callback' => 'memcache_storage_clear_cache_ajax_callback',
    'page arguments' => array(
      2,
      3,
      4,
      5,
    ),
    'access arguments' => array(
      'view memcache storage debug',
    ),
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
    'file' => 'memcache_storage.pages.inc',
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function memcache_storage_permission() {
  return array(
    'administer memcache storage' => array(
      'title' => t('Administer Memcache storage'),
      'description' => t('Manage Memcache storage module settings.'),
    ),
    'view memcache storage debug' => array(
      'title' => t('View Memcache Storage debug output'),
      'description' => t('Allow user to see memcache I/O operations at the bottom of the page.'),
    ),
  );
}

/**
 * Implements hook_admin_menu_cache_info().
 */
function memcache_storage_admin_menu_cache_info() {
  $caches['memcache_storage'] = array(
    'title' => t('Memcache data'),
    'callback' => 'memcache_storage_flush_all_cache',
  );
  return $caches;
}

/**
 * Flushes all cached bins in memcached pool.
 */
function memcache_storage_flush_all_cache() {
  $cache_bins = _memcache_storage_memcached_cache_bins();
  foreach ($cache_bins as $cache_bin) {
    cache_clear_all('*', $cache_bin, TRUE);
  }
  drupal_set_message(t('Memcached cache bins were successfully flushed.'));
}

/**
 * Implements hook_init().
 */
function memcache_storage_init() {

  // If wildcards were not flushed for a while, then lets register a shutdown
  // function which will do it.
  $last_wildcards_flush = variable_get('memcache_storage_last_wildcards_flush');
  $wildcards_flush_interval = variable_get('memcache_storage_wildcards_flush_interval', 3600);
  if (REQUEST_TIME >= $last_wildcards_flush + $wildcards_flush_interval) {
    drupal_register_shutdown_function('memcache_storage_wildcard_flush');
  }

  // Do nothing if debug mode is disabled.
  if (!variable_get('memcache_storage_debug', FALSE)) {
    return;
  }

  // Do nothing if user has no access to debug info.
  if (!user_access('view memcache storage debug')) {
    return;
  }

  // Trying not break normal site operating.
  if (!strstr($_SERVER['PHP_SELF'], 'index.php') || strstr($_GET['q'], 'upload/js') || strstr($_GET['q'], 'admin/content/node-settings/rebuild') || strstr($_GET['q'], 'system') || strstr($_GET['q'], 'batch') || strstr($_GET['q'], 'ckeditor/xss') || strstr($_GET['q'], 'autocomplete')) {
    return;
  }
  else {
    drupal_add_library('system', 'drupal.ajax');
    drupal_add_css(drupal_get_path('module', 'memcache_storage') . '/css/memcache_storage.css');
    drupal_register_shutdown_function('memcache_storage_debug_shutdown');

    // Avoid caching of pages with debug info in Drupal cache.
    drupal_page_is_cacheable(FALSE);

    // Avoid caching of pages with debug info in external cache.
    drupal_add_http_header('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
  }
}

/**
 * Invalidates all cache which should be invalid because of
 * wildcard flushes.
 */
function memcache_storage_wildcard_flush() {

  // Get list of cache bins which are stored in memcached.
  $cache_bins = _memcache_storage_memcached_cache_bins();
  foreach ($cache_bins as $bin) {

    // Get instance of MemcacheStorage from static cache.
    $object = _cache_get_object($bin);
    if (!$object instanceof MemcacheStorage) {
      continue;
    }

    // Check if the current cache bin has not yet invalidated wildcards.
    $wildcards = $object
      ->getWildcards();
    if (empty($wildcards)) {
      continue;
    }

    // Load a list of all cache ids stored in the cache bin.
    $cache_ids = $object
      ->getCacheIDs();

    // Check if each cache may match any wildcard, and mark it as "delete
    // candidate" if it is true.
    $delete_ids = array();
    foreach ($wildcards as $wildcard => $flush_timestamp) {
      foreach ($cache_ids as $cache_id => $cache_created) {
        if ($cache_created <= $flush_timestamp) {
          if (strpos($cache_id, $wildcard) === 0) {
            $delete_ids[] = $cache_id;
          }
        }
      }
    }

    // Clear all cache items which are now invalid.
    if (!empty($delete_ids)) {
      $object
        ->clear($delete_ids);
    }

    // No need to store the information about wildcards for this cache bin
    // anymore - all invalid items have been deleted.
    $object
      ->clear('memcache_storage_wildcards');
  }

  // Set a timestamp of the latest wildcards invalidation.
  variable_set('memcache_storage_last_wildcards_flush', REQUEST_TIME);
}

/**
 * Print debug output of memcache statistics.
 */
function memcache_storage_debug_shutdown() {

  // 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.
  if (function_exists('drupal_get_http_header') && !strstr(drupal_get_http_header('Content-Type'), 'html')) {
    return;
  }
  $debug_output = drupal_static('memcache_storage_debug_output');

  // Don't output any debug if it is empty.
  if (empty($debug_output)) {
    return;
  }
  $common_stats = array();
  foreach ($debug_output as $key => &$row) {
    $action = $row['action'];
    if (empty($common_stats[$action])) {
      $common_stats[$action]['action'] = $action;
      $common_stats[$action]['time'] = 0;
      $common_stats[$action]['mem'] = 0;
      $common_stats[$action]['HIT'] = 0;
      $common_stats[$action]['MISS'] = 0;
    }
    $common_stats[$action][$row['result']]++;

    // Add to the link attributes that was unable to add during
    // statistics collection, because some functions may be not loaded.
    if (!empty($row['clear_link'])) {
      $row['clear_link']['#text'] = t('Delete');
      $row['clear_link']['#options']['query'] = array(
        'token' => drupal_get_token($row['token']),
      );
    }

    // Remove token because we don't want to show it in the debug output.
    unset($row['token']);

    // Render link for clearing cache item.
    $debug_output[$key]['clear_link'] = render($row['clear_link']);

    // Do not collect statistics if previos row is same as current (for getMultiple).
    if (isset($debug_output[$key - 1]) && $debug_output[$key - 1]['timer'] == $row['timer'] && $debug_output[$key - 1]['memory'] == $row['memory']) {
      continue;
    }
    $common_stats[$action]['time'] += $row['timer'];
    $common_stats[$action]['mem'] += $row['memory'];
  }
  foreach ($common_stats as &$stats) {
    $stats['mem'] = number_format($stats['mem'] / 1024, 2);
    $stats['HIT'] = $stats['HIT'] . ' / ' . number_format($stats['HIT'] / ($stats['HIT'] + $stats['MISS']) * 100, 1) . '%';
    $stats['MISS'] = $stats['MISS'] . ' / ' . number_format($stats['MISS'] / ($stats['HIT'] + $stats['MISS']) * 100, 1) . '%';
  }

  // Rebuild detailed debug output.
  $detailed_debug = array();
  foreach ($debug_output as $row) {
    $detailed_debug[] = array(
      'data' => $row,
      'class' => array(
        $row['result'] == 'MISS' ? 'miss' : 'hit',
      ),
    );
  }
  $stats_table = array(
    '#theme' => 'table',
    '#header' => array(
      t('Action'),
      t('Total time, ms'),
      t('Total memory used, MB'),
      t('Total hits / %'),
      t('Total misses / %'),
    ),
    '#rows' => $common_stats,
    '#attributes' => array(
      'class' => array(
        'memcache-storage-common-debug',
      ),
    ),
  );
  $debug_table = array(
    '#theme' => 'table',
    '#header' => array(
      t('Action'),
      t('Time, ms'),
      t('Used memory, KB'),
      t('Result'),
      t('Cache bin'),
      t('Cache ID'),
      t('Memcache key'),
      t('Cache action'),
    ),
    '#rows' => $detailed_debug,
    '#attributes' => array(
      'class' => array(
        'memcache-storage-detailed-debug',
      ),
    ),
  );
  print '<h2>' . t('Memcache Storage debug output') . '</h2>';
  print t('Request URL: !url', array(
    '!url' => '<strong>' . url($_GET['q'], array(
      'absolute' => TRUE,
    )) . '</strong>',
  ));
  print render($stats_table) . render($debug_table);
}

/**
 * Helper function that returns a list of cache bins
 * stored in memcached.
 */
function _memcache_storage_memcached_cache_bins() {

  // Load all cache bins available for flushing.
  $available_bins = array();
  $schema = drupal_get_complete_schema();
  foreach ($schema as $table_name => $table_data) {
    if (strpos($table_name, 'cache') === 0) {

      // Make sure that cache controlled for the current cache bin
      // is Memcache Storage.
      $cache_class = variable_get('cache_class_' . $table_name);
      if (!isset($cache_class)) {
        $cache_class = variable_get('cache_default_class', 'DrupalDatabaseCache');
      }
      if ($cache_class == 'MemcacheStorage') {
        $available_bins[$table_name] = $table_name;
      }
    }
  }
  ksort($available_bins);

  // Don't allow to flush form and updates cache, because it's actually not a
  // "flushable" cache and should be stored in database.
  unset($available_bins['cache_form']);
  unset($available_bins['cache_update']);

  // We can't flush cached pages if enabled external page cache.
  // Otherwise it will change memcache key prefix and external server won't find cached pages.
  $external_page_cache = variable_get('memcache_storage_external_page_cache', FALSE);
  if ($external_page_cache) {
    unset($available_bins['cache_page']);
  }
  return $available_bins;
}

Functions

Namesort descending Description
memcache_storage_admin_menu_cache_info Implements hook_admin_menu_cache_info().
memcache_storage_debug_shutdown Print debug output of memcache statistics.
memcache_storage_flush_all_cache Flushes all cached bins in memcached pool.
memcache_storage_init Implements hook_init().
memcache_storage_menu Implements hook_menu().
memcache_storage_permission Implements hook_permission().
memcache_storage_wildcard_flush Invalidates all cache which should be invalid because of wildcard flushes.
_memcache_storage_memcached_cache_bins Helper function that returns a list of cache bins stored in memcached.

Constants