You are here

DriverBase.php in Memcache API and Integration 8.2

File

src/Driver/DriverBase.php
View source
<?php

namespace Drupal\memcache\Driver;

use Drupal\Component\Utility\Timer;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\memcache\MemcacheSettings;
use Drupal\memcache\DrupalMemcacheInterface;

/**
 * Class DriverBase.
 */
abstract class DriverBase implements DrupalMemcacheInterface {
  use LoggerChannelTrait;

  /**
   * The memcache config object.
   *
   * @var \Drupal\memcache\MemcacheSettings
   */
  protected $settings;

  /**
   * The memcache object.
   *
   * @var \Memcache|\Memcached
   *   E.g. \Memcache|\Memcached
   */
  protected $memcache;

  /**
   * The hash algorithm to pass to hash(). Defaults to 'sha1'.
   *
   * @var string
   */
  protected $hashAlgorithm;

  /**
   * The prefix memcache key for all keys.
   *
   * @var string
   */
  protected $prefix;

  /**
   * Stats for the entire request.
   *
   * @var array
   */
  protected static $stats = [
    'all' => [],
    'ops' => [],
  ];

  /**
   * Constructs a DriverBase object.
   *
   * @param \Drupal\memcache\MemcacheSettings $settings
   *   The memcache config object.
   * @param \Memcached|\Memcache $memcache
   *   An existing memcache connection object.
   * @param string $bin
   *   The class instance specific cache bin to use.
   */
  public function __construct(MemcacheSettings $settings, $memcache, $bin = NULL) {
    $this->settings = $settings;
    $this->memcache = $memcache;
    $this->hashAlgorithm = $this->settings
      ->get('key_hash_algorithm', 'sha1');
    $prefix = $this->settings
      ->get('key_prefix', '');
    if ($prefix) {
      $this->prefix = $prefix . ':';
    }
    if ($bin) {
      $this->prefix .= $bin . ':';
    }
  }

  /**
   * {@inheritdoc}
   */
  public function get($key) {
    $collect_stats = $this
      ->statsInit();
    $full_key = $this
      ->key($key);
    $result = $this->memcache
      ->get($full_key);
    if ($collect_stats) {
      $this
        ->statsWrite('get', 'cache', [
        $full_key => (bool) $result,
      ]);
    }
    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function key($key) {
    $full_key = urlencode($this->prefix . '-' . $key);

    // Memcache only supports key lengths up to 250 bytes.  If we have generated
    // a longer key, we shrink it to an acceptable length with a configurable
    // hashing algorithm. Sha1 was selected as the default as it performs
    // quickly with minimal collisions.
    if (strlen($full_key) > 250) {
      $full_key = urlencode($this->prefix . '-' . hash($this->hashAlgorithm, $key));
      $full_key .= '-' . substr(urlencode($key), 0, 250 - 1 - strlen($full_key) - 1);
    }
    return $full_key;
  }

  /**
   * {@inheritdoc}
   */
  public function delete($key) {
    $collect_stats = $this
      ->statsInit();
    $full_key = $this
      ->key($key);
    $result = $this->memcache
      ->delete($full_key, 0);
    if ($collect_stats) {
      $this
        ->statsWrite('delete', 'cache', [
        $full_key => $result,
      ]);
    }
    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function flush() {
    $collect_stats = $this
      ->statsInit();
    $result = $this->memcache
      ->flush();
    if ($collect_stats) {
      $this
        ->statsWrite('flush', 'cache', [
        '' => $result,
      ]);
    }
  }

  /**
   * Retrieves statistics recorded during memcache operations.
   *
   * @param string $stats_bin
   *   The bin to retrieve statistics for.
   * @param string $stats_type
   *   The type of statistics to retrieve when using the Memcache extension.
   * @param bool $aggregate
   *   Whether to aggregate statistics.
   */
  public function stats($stats_bin = 'cache', $stats_type = 'default', $aggregate = FALSE) {

    // The stats_type can be over-loaded with an integer slab id, if doing a
    // cachedump.  We know we're doing a cachedump if $slab is non-zero.
    $slab = (int) $stats_type;
    $stats = [];
    foreach ($this
      ->getBins() as $bin => $target) {
      if ($stats_bin == $bin) {
        if (isset($this->memcache)) {
          if ($this->memcache instanceof \Memcached) {
            $stats[$bin] = $this->memcache
              ->getStats();
          }
          elseif ($this->memcache instanceof \Memcache) {
            if ($stats_type == 'default' || $stats_type == '') {
              $stats[$bin] = $this->memcache
                ->getExtendedStats();
            }
            elseif (!empty($slab)) {
              $stats[$bin] = $this->memcache
                ->getStats('cachedump', $slab);
            }
            else {
              $stats[$bin] = $this->memcache
                ->getExtendedStats($stats_type);
            }
          }
        }
      }
    }

    // Optionally calculate a sum-total for all servers in the current bin.
    if ($aggregate) {

      // Some variables don't logically aggregate.
      $no_aggregate = [
        'pid',
        'time',
        'version',
        'libevent',
        'pointer_size',
        'accepting_conns',
        'listen_disabled_num',
      ];
      foreach ($stats as $bin => $servers) {
        if (is_array($servers)) {
          foreach ($servers as $server) {
            if (is_array($server)) {
              foreach ($server as $key => $value) {
                if (!in_array($key, $no_aggregate)) {
                  if (isset($stats[$bin]['total'][$key])) {
                    $stats[$bin]['total'][$key] += $value;
                  }
                  else {
                    $stats[$bin]['total'][$key] = $value;
                  }
                }
              }
            }
          }
        }
      }
    }
    return $stats;
  }

  /**
   * Helper function to get the bins.
   */
  public function getBins() {
    $memcache_bins = \Drupal::configFactory()
      ->getEditable('memcache.settings')
      ->get('memcache_bins');
    if (!isset($memcache_bins)) {
      $memcache_bins = [
        'cache' => 'default',
      ];
    }
    return $memcache_bins;
  }

  /**
   * Helper function to get the servers.
   */
  public function getServers() {
    $memcache_servers = \Drupal::configFactory()
      ->getEditable('memcache.settings')
      ->get('memcache_servers');
    if (!isset($memcache_servers)) {
      $memcache_servers = [
        '127.0.0.1:11211' => 'default',
      ];
    }
    return $memcache_servers;
  }

  /**
   * Helper function to get memcache.
   */
  public function getMemcache() {
    return $this->memcache;
  }

  /**
   * Helper function to get request stats.
   */
  public function requestStats() {
    return self::$stats;
  }

  /**
   * Returns an array of available statistics types.
   */
  public function statsTypes() {
    if ($this->memcache 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 [
        'default',
        'slabs',
        'items',
        'sizes',
      ];
    }
    else {

      // The Memcached PECL extension only offers the default statistics.
      return [
        'default',
      ];
    }
  }

  /**
   * Helper function to initialize the stats for a memcache operation.
   */
  protected function statsInit() {
    static $drupal_static_fast;
    if (!isset($drupal_static_fast)) {
      $drupal_static_fast =& drupal_static(__FUNCTION__, [
        'variable_checked' => NULL,
        'user_access_checked' => NULL,
      ]);
    }
    $variable_checked =& $drupal_static_fast['variable_checked'];
    $user_access_checked =& $drupal_static_fast['user_access_checked'];

    // Confirm DRUPAL_BOOTSTRAP_VARIABLES has been reached. We don't use
    // drupal_get_bootstrap_phase() as it's buggy. We can use variable_get()
    // here because _drupal_bootstrap_variables() includes module.inc
    // immediately after it calls variable_initialize().
    // @codingStandardsIgnoreStart
    // if (!isset($variable_checked) && function_exists('module_list')) {
    //   $variable_checked = variable_get('show_memcache_statistics', FALSE);
    // }
    // If statistics are enabled we need to check user access.
    // if (!empty($variable_checked) && !isset($user_access_checked) && !empty($GLOBALS['user']) && function_exists('user_access')) {
    //   // Statistics are enabled and the $user object has been populated, so check
    //   // that the user has access to view them.
    //   $user_access_checked = user_access('access memcache statistics');
    // }
    // @codingStandardsIgnoreEnd
    // Return whether or not statistics are enabled and the user can access
    // them.
    if ((!isset($variable_checked) || $variable_checked) && (!isset($user_access_checked) || $user_access_checked)) {
      Timer::start('dmemcache');
      return TRUE;
    }
    else {
      return FALSE;
    }
  }

  /**
   * Memcache statistics to be displayed at end of page generation.
   *
   * @param string $action
   *   The action being performed (get, set, etc...).
   * @param string $bin
   *   The memcache bin the action is being performed in.
   * @param array $keys
   *   Keyed array in the form (string)$cid => (bool)$success. The keys the
   *   action is being performed on, and whether or not it was a success.
   */
  protected function statsWrite($action, $bin, array $keys) {

    // Determine how much time elapsed to execute this action.
    $time = Timer::read('dmemcache');

    // Build the 'all' and 'ops' arrays displayed by memcache_admin.module.
    foreach ($keys as $key => $success) {
      self::$stats['all'][] = [
        number_format($time, 2),
        $action,
        $bin,
        $key,
        $success ? 'hit' : 'miss',
      ];
      if (!isset(self::$stats['ops'][$action])) {
        self::$stats['ops'][$action] = [
          $action,
          0,
          0,
          0,
        ];
      }
      self::$stats['ops'][$action][1] += $time;
      if ($success) {
        self::$stats['ops'][$action][2]++;
      }
      else {
        self::$stats['ops'][$action][3]++;
      }
    }
  }

}

Classes

Namesort descending Description
DriverBase Class DriverBase.