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'));
}
}