diff --git a/README.md b/README.md index 4248412..244c07e 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,47 @@ Depending on the backend, using a wrong auth will behave differently: Drupal will behave as if it was running with a null cache backend (no cache at all). +Multiple Servers +---------------- + +It is possible to add multiple Redis servers for different services. For +example to use a different host for cache bin config and _flood using different +clients. +```php +$settings['redis_client_bins'] = [ + 'bins' => [ + // These bins use these extra Redis servers listed below. + 'config' => 'redis_config', + '_flood' => 'redis_flood' + ], + // Connection details for these Redis servers. + 'servers' => [ + 'redis_config' => [ + 'host' => 'redis_config', + 'port' => 6380, + 'persistent' => TRUE, + 'interface' => 'Predis', + ], + 'redis_flood' => [ + 'host' => 'redis_flood', + 'port' => 6381, + 'persistent' => TRUE, + 'interface' => 'PhpRedis', + ], + ], +]; +``` + +These are the array keys for additional provided services + +* Flood: '_flood' +* Lock: '_lock' +* Persistent Lock: '_persistent_lock' +* Queue: '_queue' + +The _default_ connection, as defined in ```$settings['redis.connection']``` has the displayed connection name of +_\_default__ + Prefixing site cache entries (avoiding sites name collision) ------------------------------------------------------------ diff --git a/src/Cache/CacheBackendFactory.php b/src/Cache/CacheBackendFactory.php index 4efa1c6..2ab51cd 100644 --- a/src/Cache/CacheBackendFactory.php +++ b/src/Cache/CacheBackendFactory.php @@ -61,8 +61,8 @@ class CacheBackendFactory implements CacheFactoryInterface { */ public function get($bin) { if (!isset($this->bins[$bin])) { - $class_name = $this->clientFactory->getClass(ClientFactory::REDIS_IMPL_CACHE); - $this->bins[$bin] = new $class_name($bin, $this->clientFactory->getClient(), $this->checksumProvider, $this->serializer); + $class_name = $this->clientFactory->getClass(ClientFactory::REDIS_IMPL_CACHE, NULL, $bin); + $this->bins[$bin] = new $class_name($bin, $this->clientFactory->getClient($bin), $this->checksumProvider, $this->serializer); } return $this->bins[$bin]; } diff --git a/src/Cache/RedisCacheTagsChecksum.php b/src/Cache/RedisCacheTagsChecksum.php index 88d7103..2fc54aa 100644 --- a/src/Cache/RedisCacheTagsChecksum.php +++ b/src/Cache/RedisCacheTagsChecksum.php @@ -46,7 +46,7 @@ class RedisCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInv * Creates a Redis cache backend. */ public function __construct(ClientFactory $factory) { - $this->client = $factory->getClient(); + $this->client = $factory->getClient('cachetags'); $this->clientType = $factory->getClientName(); } diff --git a/src/ClientFactory.php b/src/ClientFactory.php index b9b1ea0..9ab983a 100644 --- a/src/ClientFactory.php +++ b/src/ClientFactory.php @@ -63,25 +63,30 @@ class ClientFactory { */ const REDIS_IMPL_RELIABLE_QUEUE = '\\Drupal\\redis\\Queue\\Reliable'; + /** + * Default Connection name. + */ + const REDIS_DEFAULT_CONNECTION_NAME = '_default_'; + /** * @var \Drupal\redis\ClientInterface */ - protected static $_clientInterface; + protected static array $_clientInterface; /** * @var mixed */ - protected static $_client; + protected static array $_clients; public static function hasClient() { - return isset(self::$_client); + return !empty(self::$_clients); } /** * Set client proxy. */ public static function setClient(ClientInterface $interface) { - if (isset(self::$_client)) { + if (empty(self::$_clients)) { throw new \Exception("Once Redis client is connected, you cannot change client proxy instance."); } @@ -95,29 +100,38 @@ class ClientFactory { * implementations, this will be overridden at early bootstrap phase and * configuration will be ignored. * + * @param string $bin + * Bin, if fixed. + * * @return ClientInterface */ - public static function getClientInterface() + public static function getClientInterface($bin = self::REDIS_DEFAULT_CONNECTION_NAME) { - if (!isset(self::$_clientInterface)) + if (!isset(self::$_clientInterface[$bin])) { - $settings = Settings::get('redis.connection', []); + $bin_settings = Settings::get('redis_client_bins', []); + $settings = (isset($bin_settings['bins'][$bin]) + && isset($bin_settings['servers'][$bin_settings['bins'][$bin]]) + && isset($bin_settings['servers'][$bin_settings['bins'][$bin]]['interface']) + ) + ? $bin_settings['servers'][$bin_settings['bins'][$bin]] + : Settings::get('redis.connection', []); if (!empty($settings['interface'])) { $className = self::getClass(self::REDIS_IMPL_CLIENT, $settings['interface']); - self::$_clientInterface = new $className(); + self::$_clientInterface[$bin] = new $className(); } elseif (class_exists('Predis\Client')) { // Transparent and arbitrary preference for Predis library. $className = self::getClass(self::REDIS_IMPL_CLIENT, 'Predis'); - self::$_clientInterface = new $className(); + self::$_clientInterface[$bin] = new $className(); } elseif (class_exists('Redis')) { // Fallback on PhpRedis if available. $className = self::getClass(self::REDIS_IMPL_CLIENT, 'PhpRedis'); - self::$_clientInterface = new $className(); + self::$_clientInterface[$bin] = new $className(); } elseif (class_exists('Relay\Relay')) { @@ -127,31 +141,41 @@ class ClientFactory { } else { - if (!isset(self::$_clientInterface)) + if (!isset(self::$_clientInterface[$bin])) { throw new \Exception("No client interface set."); } } } - return self::$_clientInterface; + return self::$_clientInterface[$bin]; } /** * Get underlying library name. * + * @param string $bin + * Bin, if fixed. + * * @return string */ - public static function getClientName() { - return self::getClientInterface()->getName(); + public static function getClientName($bin = self::REDIS_DEFAULT_CONNECTION_NAME) { + return self::getClientInterface($bin)->getName(); } /** * Get client singleton. + * + * @param string $bin + * Bin, if fixed. */ - public static function getClient() { - if (!isset(self::$_client)) { - $settings = Settings::get('redis.connection', []); + public static function getClient($bin = self::REDIS_DEFAULT_CONNECTION_NAME) { + if (!isset(self::$_clients[$bin])) { + $bin_settings = Settings::get('redis_client_bins', []); + $settings = (isset($bin_settings['bins'][$bin]) && isset($bin_settings['servers'][$bin_settings['bins'][$bin]])) + ? $bin_settings['servers'][$bin_settings['bins'][$bin]] + : Settings::get('redis.connection', []); + $settings += [ 'host' => self::REDIS_DEFAULT_HOST, 'port' => self::REDIS_DEFAULT_PORT, @@ -168,7 +192,7 @@ class ClientFactory { } } - self::$_client = self::getClientInterface()->getClient( + self::$_clients[$bin] = self::getClientInterface($bin)->getClient( $settings['host'], $settings['port'], $settings['base'], @@ -177,7 +201,7 @@ class ClientFactory { $settings['persistent']); } else { - self::$_client = self::getClientInterface()->getClient( + self::$_clients[$bin] = self::getClientInterface($bin)->getClient( $settings['host'], $settings['port'], $settings['base'], @@ -187,7 +211,7 @@ class ClientFactory { } } - return self::$_client; + return self::$_clients[$bin]; } /** @@ -198,6 +222,8 @@ class ClientFactory { * One of the ClientFactory::IMPL_* constant. * @param string $clientName * Client name, if fixed. + * @param string $bin + * Bin, if fixed. * * @return string * Class name, if found. @@ -205,8 +231,8 @@ class ClientFactory { * @throws \Exception * If not found. */ - public static function getClass($system, $clientName = NULL) { - $className = $system . ($clientName ?: self::getClientName()); + public static function getClass($system, $clientName = NULL, $bin = self::REDIS_DEFAULT_CONNECTION_NAME) { + $className = $system . ($clientName ?: self::getClientName($bin)); if (!class_exists($className)) { throw new \Exception($className . " does not exists"); @@ -220,7 +246,7 @@ class ClientFactory { */ static public function reset() { self::$_clientInterface = null; - self::$_client = null; + self::$_clients = null; } } diff --git a/src/Controller/ReportController.php b/src/Controller/ReportController.php index edd3904..a52b004 100644 --- a/src/Controller/ReportController.php +++ b/src/Controller/ReportController.php @@ -5,11 +5,15 @@ namespace Drupal\redis\Controller; use Drupal\Component\Utility\Unicode; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\Logger\RfcLogLevel; +use Drupal\Core\Site\Settings; use Drupal\Core\Url; use Drupal\redis\ClientFactory; use Drupal\redis\RedisPrefixTrait; +use Exception; use Predis\Client; use Predis\Collection\Iterator\Keyspace; +use Relay\Relay; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -21,6 +25,13 @@ class ReportController extends ControllerBase { use RedisPrefixTrait; + /** + * The redis client factory. + * + * @var \Drupal\redis\ClientFactory + */ + protected $clientFactory; + /** * The redis client. * @@ -44,6 +55,7 @@ class ReportController extends ControllerBase { * The date formatter. */ public function __construct(ClientFactory $client_factory, DateFormatterInterface $date_formatter) { + $this->clientFactory = $client_factory; if (ClientFactory::hasClient()) { $this->redis = $client_factory->getClient(); } @@ -78,9 +90,9 @@ class ReportController extends ControllerBase { $build['report']['#requirements'] = [ 'client' => [ 'title' => 'Redis', - 'value' => t('Not connected.'), - 'severity_status' => 'error', - 'description' => t('No Redis client connected. Verify cache settings.'), + 'value' => $this->t('Not connected.'), + 'severity' => REQUIREMENT_ERROR, + 'description' => $this->t('No Redis client connected. Verify cache settings.'), ], ]; @@ -89,223 +101,386 @@ class ReportController extends ControllerBase { $start = microtime(TRUE); - $info = $this->info(); - $prefix_length = strlen($this->getPrefix()) + 1; - $entries_per_bin = array_fill_keys(\Drupal::getContainer()->getParameter('cache_bins'), 0); + $bin_settings = Settings::get('redis_client_bins', []); + $bins = \Drupal::getContainer()->getParameter('cache_bins') + + [ + 'cachetags' => 'cachetags', + '_flood' => '_flood', + '_lock' => '_lock', + '_persistent_lock' => '_persistent_lock', + '_queue' => '_queue', + ]; + + // Check for additional bins. + if (isset($bin_settings['bins'])) { + foreach ($bin_settings['bins'] as $key => $settings) { + if (!in_array($key, $bins)) { + $bins[] = $key; + } + } + } + + $connectionNames = []; + foreach ($bins as $bin) { + if (isset($bin_settings['bins'][$bin])) { + $connectionName = $bin_settings['bins'][$bin]; + } + else { + $connectionName = ClientFactory::REDIS_DEFAULT_CONNECTION_NAME; + } + $connectionNames[$connectionName][] = $bin; + } $required_cached_contexts = \Drupal::getContainer()->getParameter('renderer.config')['required_cache_contexts']; - $render_cache_totals = []; - $render_cache_contexts = []; - $cache_tags = []; - $i = 0; - $cache_tags_max = FALSE; - foreach ($this->scan($this->getPrefix() . '*') as $key) { - $i++; - $second_colon_pos = mb_strpos($key, ':', $prefix_length); - if ($second_colon_pos !== FALSE) { - $bin = mb_substr($key, $prefix_length, $second_colon_pos - $prefix_length); - if (isset($entries_per_bin[$bin])) { - $entries_per_bin[$bin]++; + foreach ($connectionNames as $connectionName => $connections_bins) { + // Use the new connection. + $this->redis = $this->clientFactory->getClient($connections_bins[0]); + + // Fetch the information. + $info = $this->info(); + + // Reset the counters. + $entries_per_bin = array_fill_keys($bins, 0); + $render_cache_totals = []; + $render_cache_contexts = []; + $cache_tags = []; + $i = 0; + $cache_tags_max = FALSE; + + // Fetch the Slow log length + if ($length = $this->redis->slowLog('LEN')) { + $slowlogs = $this->redis->slowLog('GET', 10); + } + + foreach ($this->scan($this->getPrefix() . '*') as $key) { + if ($length) { + // Reset the SLOW LOG as it has entries after the scan. + $this->redis->slowLog('RESET'); } - if ($bin == 'render') { - $cache_key = mb_substr($key, $second_colon_pos + 1); + $i++; + $second_colon_pos = mb_strpos($key, ':', $prefix_length); + if ($second_colon_pos !== FALSE) { + $bin = mb_substr($key, $prefix_length, $second_colon_pos - $prefix_length); + if (isset($entries_per_bin[$bin])) { + $entries_per_bin[$bin]++; + } - $first_context = mb_strpos($cache_key, '['); - if ($first_context) { - $cache_key_only = mb_substr($cache_key, 0, $first_context - 1); - if (!isset($render_cache_totals[$cache_key_only])) { - $render_cache_totals[$cache_key_only] = 1; - } - else { - $render_cache_totals[$cache_key_only]++; - } + if ($bin == 'render') { + $cache_key = mb_substr($key, $second_colon_pos + 1); - if (preg_match_all('/\[([a-z0-9:_.]+)\]=([^:]*)/', $cache_key, $matches)) { - foreach ($matches[1] as $index => $context) { - $render_cache_contexts[$cache_key_only][$context][$matches[2][$index]] = $matches[2][$index]; + $first_context = mb_strpos($cache_key, '['); + if ($first_context) { + $cache_key_only = mb_substr($cache_key, 0, $first_context - 1); + if (!isset($render_cache_totals[$cache_key_only])) { + $render_cache_totals[$cache_key_only] = 1; + } + else { + $render_cache_totals[$cache_key_only]++; + } + + if (preg_match_all('/\[([a-z0-9:_.]+)\]=([^:]*)/', $cache_key, $matches)) { + foreach ($matches[1] as $index => $context) { + $render_cache_contexts[$cache_key_only][$context][$matches[2][$index]] = $matches[2][$index]; + } } } } - } - elseif ($bin == 'cachetags') { - $cache_tag = mb_substr($key, $second_colon_pos + 1); - // @todo: Make the max configurable or allow ot override it through - // a query parameter. - if (count($cache_tags) < 50000) { - $cache_tags[$cache_tag] = $this->redis->get($key); - } - else { - $cache_tags_max = TRUE; + elseif ($bin == 'cachetags') { + $cache_tag = mb_substr($key, $second_colon_pos + 1); + // @todo: Make the max configurable or allow ot override it through + // a query parameter. + if (count($cache_tags) < 50000) { + $cache_tags[$cache_tag] = $this->redis->get($key); + } + else { + $cache_tags_max = TRUE; + } } } - } - - // Do not process more than 100k cache keys. - // @todo Adjust this after more testing or move to a separate page. - } - arsort($entries_per_bin); - arsort($render_cache_totals); - arsort($cache_tags); - - $per_bin_string = ''; - foreach ($entries_per_bin as $bin => $entries) { - $per_bin_string .= "$bin: $entries
"; - } + // Do not process more than 100k cache keys. + // @todo Adjust this after more testing or move to a separate page. + } - $render_cache_string = ''; - foreach (array_slice($render_cache_totals, 0, 50) as $cache_key => $total) { - $contexts = implode(', ', array_diff(array_keys($render_cache_contexts[$cache_key]), $required_cached_contexts)); - $render_cache_string .= $contexts ? "$cache_key: $total ($contexts)
" : "$cache_key: $total
"; - } + arsort($entries_per_bin); + arsort($render_cache_totals); + arsort($cache_tags); - $cache_tags_string = ''; - foreach (array_slice($cache_tags, 0, 50) as $cache_tag => $invalidations) { - $cache_tags_string .= "$cache_tag: $invalidations
"; - } + $per_bin_string = ''; + foreach ($entries_per_bin as $bin => $entries) { + $per_bin_string .= "$bin: $entries
"; + } - $end = microtime(TRUE); + $render_cache_string = ''; + foreach (array_slice($render_cache_totals, 0, 50) as $cache_key => $total) { + $contexts = implode(', ', array_diff(array_keys($render_cache_contexts[$cache_key]), $required_cached_contexts)); + $render_cache_string .= $contexts ? "$cache_key: $total ($contexts)
" : "$cache_key: $total
"; + } - if ($info['maxmemory']) { - $memory_value = $this->t('@used_memory / @max_memory (@used_percentage%), maxmemory policy: @policy', [ - '@used_memory' => $info['used_memory_human'] ?? $info['Memory']['used_memory_human'], - '@max_memory' => format_size($info['maxmemory']), - '@used_percentage' => (int) ($info['used_memory'] / $info['maxmemory'] * 100), - '@policy' => $info['maxmemory_policy'], - ]); - } - else { - $memory_value = $this->t('@used_memory / unlimited, maxmemory policy: @policy', [ - '@used_memory' => $info['used_memory_human'], - '@policy' => $info['maxmemory_policy'], - ]); - } + $cache_tags_string = ''; + foreach (array_slice($cache_tags, 0, 50) as $cache_tag => $invalidations) { + $cache_tags_string .= "$cache_tag: $invalidations
"; + } - $requirements = [ - 'client' => [ - 'title' => $this->t('Client'), - 'value' => t("Connected, using the @name client.", ['@name' => ClientFactory::getClientName()]), - ], - 'version' => [ - 'title' => $this->t('Version'), - 'value' => $info['redis_version'], - ], - 'mode' => [ - 'title' => $this->t('Mode'), - 'value' => Unicode::ucfirst($info['redis_mode']), + $end = microtime(TRUE); + + try { + if ($memory_config = $this->redis->config('get', 'maxmemory*')) { + if ($memory_config['maxmemory']) { + $memory_value = $this->t('@used_memory / @max_memory (@used_percentage%), maxmemory policy: @policy', [ + '@used_memory' => $info['used_memory_human'] ?? $info['Memory']['used_memory_human'], + '@max_memory' => format_size($memory_config['maxmemory']), + '@used_percentage' => (int) ($info['used_memory'] / $memory_config['maxmemory'] * 100), + '@policy' => $memory_config['maxmemory_policy'] ?? 'No policy returned', + ]); + } + else { + $memory_value = $this->t('@used_memory / unlimited, maxmemory policy: @policy', [ + '@used_memory' => $info['used_memory_human'], + '@policy' => $memory_config['maxmemory_policy'] ?? 'No policy returned', + ]); + } + } + else { + $memory_value = $info['used_memory_human'] ?? $info['Memory']['used_memory_human']; + } + } + // We can't be sure what sort of Exception will be thrown, depends on the + // integration used. + catch (Exception $e) { + $memory_value = $info['used_memory_human'] ?? $info['Memory']['used_memory_human']; + + watchdog_exception( + 'redis', + $e, + 'Exception caught while trying to retrieve memory configuration from Redis. This may just be a security measure from your Redis provider. %type: @message in %function (line %line of %file).', + [], + RfcLogLevel::INFO); + } - ], - 'clients' => [ - 'title' => $this->t('Connected clients'), - 'value' => $info['connected_clients'], - ], - 'dbsize' => [ - 'title' => $this->t('Keys'), - 'value' => $info['db_size'], - ], - 'memory' => [ - 'title' => $this->t('Memory'), - 'value' => $memory_value, - ], - 'uptime' => [ - 'title' => $this->t('Uptime'), - 'value' => $this->dateFormatter->formatInterval($info['uptime_in_seconds']), - ], - 'read_write' => [ - 'title' => $this->t('Read/Write'), - 'value' => $this->t('@read read (@percent_read%), @write written (@percent_write%), @commands commands in @connections connections.', [ - '@read' => format_size($info['total_net_output_bytes']), - '@percent_read' => round(100 / ($info['total_net_output_bytes'] + $info['total_net_input_bytes']) * ($info['total_net_output_bytes'])), - '@write' => format_size($info['total_net_input_bytes']), - '@percent_write' => round(100 / ($info['total_net_output_bytes'] + $info['total_net_input_bytes']) * ($info['total_net_input_bytes'])), - '@commands' => $info['total_commands_processed'], - '@connections' => $info['total_connections_received'], - ]), - ], - 'per_bin' => [ - 'title' => $this->t('Keys per cache bin'), - 'value' => ['#markup' => $per_bin_string], - ], - 'render_cache' => [ - 'title' => $this->t('Render cache entries with most variations'), - 'value' => ['#markup' => $render_cache_string], - ], - 'cache_tags' => [ - 'title' => $this->t('Most invalidated cache tags'), - 'value' => ['#markup' => $cache_tags_string], - ], - 'cache_tag_totals' => [ - 'title' => $this->t('Total cache tag invalidations'), - 'value' => [ - '#markup' => $this->t('@count tags with @invalidations invalidations.', [ - '@count' => count($cache_tags), - '@invalidations' => array_sum($cache_tags), - ]), + $requirements = [ + 'connection' => [ + 'title' => $this->t('Connection Name'), + 'value' => $connectionName, ], - ], - 'time_spent' => [ - 'title' => $this->t('Time spent'), - 'value' => [ - '#markup' => $this->t('@count keys in @time seconds.', [ - '@count' => $i, - '@time' => round(($end - $start), 4), + 'bins' => [ + 'title' => $this->t('Serving'), + 'value' => implode(', ', $connections_bins), + ], + 'client' => [ + 'title' => $this->t('Client'), + 'value' => t("Connected, using the @name client.", ['@name' => ClientFactory::getClientName($connections_bins[0])]), + ], + 'version' => [ + 'title' => $this->t('Version'), + 'value' => $info['redis_version'], + ], + 'mode' => [ + 'title' => $this->t('Mode'), + 'value' => Unicode::ucfirst($info['redis_mode']), + ], + 'clients' => [ + 'title' => $this->t('Connected clients'), + 'value' => $info['connected_clients'], + ], + 'dbsize' => [ + 'title' => $this->t('Keys'), + 'value' => $info['db_size'], + ], + 'memory' => [ + 'title' => $this->t('Memory'), + 'value' => $memory_value, + ], + 'uptime' => [ + 'title' => $this->t('Uptime'), + 'value' => $this->dateFormatter->formatInterval($info['uptime_in_seconds']), + ], + 'read_write' => [ + 'title' => $this->t('Read/Write'), + 'value' => $this->t('@read read (@percent_read%), @write written (@percent_write%), @commands commands in @connections connections.', [ + '@read' => format_size($info['total_net_output_bytes']), + '@percent_read' => $info['total_net_output_bytes_percentage'], + '@write' => format_size($info['total_net_input_bytes']), + '@percent_write' => $info['total_net_input_bytes_percentage'], + '@commands' => $info['total_commands_processed'], + '@connections' => $info['total_connections_received'], ]), ], + 'time_spent' => [ + 'title' => $this->t('Time spent'), + 'value' => [ + '#markup' => $this->t('@count keys in @time seconds.', [ + '@count' => $i, + '@time' => round(($end - $start), 4), + ]), + ], + ], + 'slow_log' => [ + 'title' => $this->t('Slow Log'), + 'value' => [ + '#markup' => $this->t('@count Entries.', [ + '@count' => $length, + ]), + ], + ], + 'per_bin' => [ + 'title' => $this->t('Keys per cache bin'), + 'value' => ['#markup' => $per_bin_string], + ], + 'render_cache' => [ + 'title' => $this->t('Render cache entries with most variations'), + 'value' => ['#markup' => $render_cache_string], + ], + ]; - ], - ]; + // Warnings/hints. + if (isset($memory_config['maxmemory-policy']) && $memory_config['maxmemory-policy'] == 'noeviction') { + $redis_url = Url::fromUri('https://redis.io/topics/lru-cache', [ + 'fragment' => 'eviction-policies', + 'attributes' => [ + 'target' => '_blank', + ], + ]); + $requirements['memory']['severity'] = REQUIREMENT_WARNING; + $requirements['memory']['description'] = $this->t('It is recommended to configure the maxmemory policy to e.g. volatile-lru, see Redis documentation.', [ + ':documentation_url' => $redis_url->toString(), + ]); + } - // Warnings/hints. - if ($info['maxmemory_policy'] == 'noeviction') { - $redis_url = Url::fromUri('https://redis.io/topics/lru-cache', [ - 'fragment' => 'eviction-policies', - 'attributes' => [ - 'target' => '_blank', - ], - ]); - $requirements['memory']['severity_status'] = 'warning'; - $requirements['memory']['description'] = $this->t('It is recommended to configure the maxmemory policy to e.g. volatile-lru, see Redis documentation.', [ - ':documentation_url' => $redis_url->toString(), - ]); - } - if (count($cache_tags) == 0) { - $requirements['cache_tag_totals']['severity'] = REQUIREMENT_WARNING; - $requirements['cache_tag_totals']['description'] = $this->t('No cache tags found, make sure that the redis cache tag checksum service is used. See example.services.yml on root of this module.'); - unset($requirements['cache_tags']); - } + // If there was log entries. + if ($length) { + foreach ($slowlogs as &$slowlog) { + $slowlog[3] = str_replace( + $this->getPrefix(), + '_prefix_', + implode ("\n", $slowlog[3]) + ); + } + $requirements['slow_log']['value'] = [ + '#type' => 'table', + '#caption' => $this->t('@count Entries. Slow log cleared.', [ + '@count' => $length, + ]), + '#header' => [ + $this + ->t('Id'), + $this + ->t('Timestamp'), + $this + ->t('Duration'), + $this + ->t('Command'), + $this + ->t('Client Host'), + $this + ->t('Client Name'), + ], + '#rows' => $slowlogs, + ]; + } - if ($this->redis instanceof \Relay\Relay) { - $stats = $this->redis->stats(); + if (in_array('cachetags', $connectionNames[$connectionName])) { + $requirements += [ + 'cache_tags' => [ + 'title' => $this->t('Most invalidated cache tags'), + 'value' => ['#markup' => $cache_tags_string], + ], + 'cache_tag_totals' => [ + 'title' => $this->t('Total cache tag invalidations'), + 'value' => [ + '#markup' => $this->t('@count tags with @invalidations invalidations.', [ + '@count' => count($cache_tags), + '@invalidations' => array_sum($cache_tags), + ]), + ], + ], + ]; + if (count($cache_tags) == 0) { + $requirements['cache_tag_totals']['severity'] = REQUIREMENT_WARNING; + $requirements['cache_tag_totals']['description'] = $this->t('No cache tags found, make sure that the redis cache tag checksum service is used. See example.services.yml on root of this module.'); + unset($requirements['cache_tags']); + } + } - $requirements['relay'] = [ - 'title' => $this->t('Relay'), - 'value' => t("@used / @total memory usage, eviction policy: @policy", [ - '@used' => format_size($stats['memory']['active'] ?? 0), - '@total' => format_size($stats['memory']['total'] ?? 0), - '@policy' => ini_get('relay.eviction_policy') - ]), - ]; + if ($cache_tags_max) { + $requirements['max_cache_tags'] = [ + 'severity_status' => 'warning', + 'title' => $this->t('Cache tags limit reached'), + 'value' => ['#markup' => $this->t('Cache tag count incomplete, only counted @count cache tags.', ['@count' => count($cache_tags)])], + ]; + } + + if ($this->redis instanceof Relay) { + $stats = $this->redis->stats(); - if (ini_get('relay.eviction_policy') != 'lru') { - $requirements['relay']['severity'] = REQUIREMENT_WARNING; - $requirements['relay']['description'] = $this->t('It is recommended to set the relay eviction plicy to lru'); + $requirements['relay'] = [ + 'title' => $this->t('Relay'), + 'value' => $this->t("@used / @total memory usage, eviction policy: @policy", [ + '@used' => format_size($stats['memory']['active'] ?? 0), + '@total' => format_size($stats['memory']['total'] ?? 0), + '@policy' => ini_get('relay.eviction_policy') + ]), + ]; + + if (ini_get('relay.eviction_policy') != 'lru') { + $requirements['relay']['severity'] = REQUIREMENT_WARNING; + $requirements['relay']['description'] = $this->t('It is recommended to set the relay eviction plicy to lru'); + } } + if (1 == count($connectionNames)) { + unset($requirements['connection']); + unset($requirements['bins']); + $build['report']['#requirements'] = $requirements; + } + else { + $build['report']['#requirements'][$connectionName] = [ + 'title' => $connectionName, + 'value' => [ + '#type' => 'status_report', + '#requirements' => $requirements, + ], + ]; + } } - if ($cache_tags_max) { - $requirements['max_cache_tags'] = [ - 'severity' => REQUIREMENT_WARNING, - 'title' => $this->t('Cache tags limit reached'), - 'value' => ['#markup' => $this->t('Cache tag count incomplete, only counted @count cache tags.', ['@count' => count($cache_tags)])], + if ($compression_length = Settings::get('redis_compress_length', FALSE)) { + $size = $compression_length; + $ratio = Settings::get('redis_compress_level', NULL) ?? $this->t('not set'); + $compression_build = [ + 'title' => 'Client Side Compression', + 'value' => [ + '#markup' => $this->t( + 'Status: enabled, Size Threshold: @size, Ratio: @ratio.', + [ + '@size' => $size, + '@ratio' => $ratio, + ] + ), + ], + ]; + } + else { + $compression_build = [ + 'title' => 'Client Side Compression', + 'value' => [ + '#markup' => $this->t('Not configured.'), + ], ]; } - $build['report']['#requirements'] = $requirements; + $build['report']['#requirements'][] = $compression_build; + + $build['report']['#requirements'][] = [ + 'title' => 'Prefix', + 'value' => [ + '#markup' => $this->getPrefix(), + ], + ]; return $build; } @@ -322,7 +497,7 @@ class ReportController extends ControllerBase { */ protected function scan($match, $count = 10000) { $it = NULL; - if ($this->redis instanceof \Redis || $this->redis instanceof \Relay\Relay) { + if ($this->redis instanceof \Redis || $this->redis instanceof Relay) { while ($keys = $this->redis->scan($it, $this->getPrefix() . '*', $count)) { yield from $keys; } @@ -342,21 +517,32 @@ class ReportController extends ControllerBase { * Wrapper to get various statistical information from Redis. * * @return array - * Redis info. */ protected function info() { $normalized_info = []; - if ($this->redis instanceof \RedisCluster) { - $master = current($this->redis->_masters()); - $info = $this->redis->info($master); + try { + if ($this->redis instanceof \RedisCluster) { + $master = current($this->redis->_masters()); + $info = $this->redis->info($master); + } + else { + $info = $this->redis->info(); + } } - else { - $info = $this->redis->info(); + catch(Exception $e) { + $info = []; + + watchdog_exception( + 'redis', + $e, + 'Exception caught while trying to retrieve information configuration from Redis. This may just be a security measure from your Redis provider. %type: @message in %function (line %line of %file).', + [], + RfcLogLevel::INFO); } - $normalized_info['redis_version'] = $info['redis_version'] ?? $info['Server']['redis_version']; - $normalized_info['redis_mode'] = $info['redis_mode'] ?? $info['Server']['redis_mode']; - $normalized_info['connected_clients'] = $info['connected_clients'] ?? $info['Clients']['connected_clients']; + $normalized_info['redis_version'] = $info['redis_version'] ?? $info['Server']['redis_version'] ?? ''; + $normalized_info['redis_mode'] = $info['redis_mode'] ?? $info['Server']['redis_mode'] ?? ''; + $normalized_info['connected_clients'] = $info['connected_clients'] ?? $info['Clients']['connected_clients'] ?? ''; if ($this->redis instanceof \RedisCluster) { $master = current($this->redis->_masters()); $normalized_info['db_size'] = $this->redis->dbSize($master); @@ -364,24 +550,40 @@ class ReportController extends ControllerBase { else { $normalized_info['db_size'] = $this->redis->dbSize(); } - $normalized_info['used_memory'] = $info['used_memory'] ?? $info['Memory']['used_memory']; - $normalized_info['used_memory_human'] = $info['used_memory_human'] ?? $info['Memory']['used_memory_human']; + $normalized_info['used_memory'] = $info['used_memory'] ?? $info['Memory']['used_memory'] ?? ''; + $normalized_info['used_memory_human'] = $info['used_memory_human'] ?? $info['Memory']['used_memory_human'] ?? ''; if (empty($info['maxmemory_policy'])) { - $memory_config = $this->redis->config('get', 'maxmemory*'); + try { + $memory_config = $this->redis->config('get', 'maxmemory*'); + } + catch (Exception $e) { + // The next call to config() will provide the watchdog entry. + $normalized_info['maxmemory_policy'] = $info['maxmemory_policy'] ?? ''; + $normalized_info['maxmemory'] = $info['maxmemory'] ?? ''; + } $normalized_info['maxmemory_policy'] = $memory_config['maxmemory-policy']; $normalized_info['maxmemory'] = $memory_config['maxmemory']; } else { - $normalized_info['maxmemory_policy'] = $info['maxmemory_policy']; - $normalized_info['maxmemory'] = $info['maxmemory']; + $normalized_info['maxmemory_policy'] = $info['maxmemory_policy'] ?? ''; + $normalized_info['maxmemory'] = $info['maxmemory'] ?? ''; } - $normalized_info['uptime_in_seconds'] = $info['uptime_in_seconds'] ?? $info['Server']['uptime_in_seconds']; - $normalized_info['total_net_output_bytes'] = $info['total_net_output_bytes'] ?? $info['Stats']['total_net_output_bytes']; - $normalized_info['total_net_input_bytes'] = $info['total_net_input_bytes'] ?? $info['Stats']['total_net_input_bytes']; - $normalized_info['total_commands_processed'] = $info['total_commands_processed'] ?? $info['Stats']['total_commands_processed']; - $normalized_info['total_connections_received'] = $info['total_connections_received'] ?? $info['Stats']['total_connections_received']; + $normalized_info['uptime_in_seconds'] = $info['uptime_in_seconds'] ?? $info['Server']['uptime_in_seconds'] ?? ''; + $normalized_info['total_net_output_bytes'] = $info['total_net_output_bytes'] ?? $info['Stats']['total_net_output_bytes'] ?? 0; + $normalized_info['total_net_input_bytes'] = $info['total_net_input_bytes'] ?? $info['Stats']['total_net_input_bytes'] ?? 0; + $normalized_info['total_commands_processed'] = $info['total_commands_processed'] ?? $info['Stats']['total_commands_processed'] ?? ''; + $normalized_info['total_connections_received'] = $info['total_connections_received'] ?? $info['Stats']['total_connections_received'] ?? ''; + + if ($normalized_info['total_net_output_bytes'] + $normalized_info['total_net_input_bytes']) { + $normalized_info['total_net_output_bytes_percentage'] = round(100 / ($normalized_info['total_net_output_bytes'] + $normalized_info['total_net_input_bytes']) * ($normalized_info['total_net_output_bytes'])); + $normalized_info['total_net_input_bytes_percentage'] = round(100 / ($normalized_info['total_net_output_bytes'] + $normalized_info['total_net_input_bytes']) * ($normalized_info['total_net_input_bytes'])); + } + else { + $normalized_info['total_net_output_bytes_percentage'] = ''; + $normalized_info['total_net_input_bytes_percentage'] = ''; + } return $normalized_info; } diff --git a/src/Flood/PhpRedis.php b/src/Flood/PhpRedis.php index 06a3604..dcfe8b2 100644 --- a/src/Flood/PhpRedis.php +++ b/src/Flood/PhpRedis.php @@ -36,7 +36,7 @@ class PhpRedis implements FloodInterface { * The request stack used to retrieve the current request. */ public function __construct(ClientFactory $client_factory, RequestStack $request_stack) { - $this->client = $client_factory->getClient(); + $this->client = $client_factory->getClient('_flood'); $this->requestStack = $request_stack; } diff --git a/src/Flood/Predis.php b/src/Flood/Predis.php index de28b04..a2c1913 100644 --- a/src/Flood/Predis.php +++ b/src/Flood/Predis.php @@ -36,7 +36,7 @@ class Predis implements FloodInterface { * The request stack used to retrieve the current request. */ public function __construct(ClientFactory $client_factory, RequestStack $request_stack) { - $this->client = $client_factory->getClient(); + $this->client = $client_factory->getClient('_flood'); $this->requestStack = $request_stack; } diff --git a/src/Lock/PhpRedis.php b/src/Lock/PhpRedis.php index 7ae84bd..d6545aa 100644 --- a/src/Lock/PhpRedis.php +++ b/src/Lock/PhpRedis.php @@ -22,7 +22,7 @@ class PhpRedis extends LockBackendAbstract { * Creates a PhpRedis cache backend. */ public function __construct(ClientFactory $factory) { - $this->client = $factory->getClient(); + $this->client = $factory->getClient('_lock'); // __destruct() is causing problems with garbage collections, register a // shutdown function instead. drupal_register_shutdown_function([$this, 'releaseAll']); diff --git a/src/Lock/Predis.php b/src/Lock/Predis.php index 27cfcf8..1472021 100644 --- a/src/Lock/Predis.php +++ b/src/Lock/Predis.php @@ -22,7 +22,7 @@ class Predis extends LockBackendAbstract { * Creates a Predis cache backend. */ public function __construct(ClientFactory $factory) { - $this->client = $factory->getClient(); + $this->client = $factory->getClient('_lock'); // __destruct() is causing problems with garbage collections, register a // shutdown function instead. drupal_register_shutdown_function([$this, 'releaseAll']); diff --git a/src/PersistentLock/PhpRedis.php b/src/PersistentLock/PhpRedis.php index 1e21055..2cd4f25 100644 --- a/src/PersistentLock/PhpRedis.php +++ b/src/PersistentLock/PhpRedis.php @@ -15,7 +15,7 @@ class PhpRedis extends \Drupal\redis\Lock\PhpRedis { public function __construct(ClientFactory $factory) { // Do not call the parent constructor to avoid registering a shutdown // function that releases all the locks at the end of a request. - $this->client = $factory->getClient(); + $this->client = $factory->getClient('_persistent_lock'); // Set the lockId to a fixed string to make the lock ID the same across // multiple requests. The lock ID is used as a page token to relate all the // locks set during a request to each other. diff --git a/src/PersistentLock/Predis.php b/src/PersistentLock/Predis.php index 79252e7..e53b489 100644 --- a/src/PersistentLock/Predis.php +++ b/src/PersistentLock/Predis.php @@ -15,7 +15,7 @@ class Predis extends \Drupal\redis\Lock\Predis { public function __construct(ClientFactory $factory) { // Do not call the parent constructor to avoid registering a shutdown // function that releases all the locks at the end of a request. - $this->client = $factory->getClient(); + $this->client = $factory->getClient('_persistent_lock'); // Set the lockId to a fixed string to make the lock ID the same across // multiple requests. The lock ID is used as a page token to relate all the // locks set during a request to each other. diff --git a/src/Queue/QueueRedisFactory.php b/src/Queue/QueueRedisFactory.php index aabc1b7..68925bc 100644 --- a/src/Queue/QueueRedisFactory.php +++ b/src/Queue/QueueRedisFactory.php @@ -50,7 +50,7 @@ class QueueRedisFactory { public function get($name) { $settings = $this->settings->get('redis_queue_' . $name, ['reserve_timeout' => NULL]); $class_name = $this->clientFactory->getClass(static::CLASS_NAMESPACE); - return new $class_name($name, $settings, $this->clientFactory->getClient()); + return new $class_name($name, $settings, $this->clientFactory->getClient('_queue')); } }