Skip to content

Commit f9007c6

Browse files
committed
Redis Cluster fixes
1 parent 8dfc47d commit f9007c6

File tree

9 files changed

+226
-56
lines changed

9 files changed

+226
-56
lines changed

phpstan.neon

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
parameters:
2-
level: 6
3-
treatPhpDocTypesAsCertain: false
4-
paths:
5-
- src
6-
- tests
7-
excludePaths:
8-
- 'src/Dashboards/Redis/Compatibility/Cluster/RedisCluster.php' # phpstan doesn't have updated stubs for RedisCluster
2+
level: 6
3+
treatPhpDocTypesAsCertain: false
4+
paths:
5+
- src
6+
- tests
7+
ignoreErrors:
8+
-
9+
message: '~^Method RedisCluster::(info|scan|rawcommand)\(\) invoked with \d+ parameters, .* required\.$~'
10+
path: src/Dashboards/Redis/Compatibility/Cluster/RedisCluster.php
11+
-
12+
message: '~^Parameter #3 \$pattern of method RedisCluster::scan\(\) expects int, string given\.$~'
13+
path: src/Dashboards/Redis/Compatibility/Cluster/RedisCluster.php

src/Admin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use RobiNN\Pca\Dashboards\DashboardInterface;
1212

1313
class Admin {
14-
public const VERSION = '2.2.0';
14+
public const VERSION = '2.2.1';
1515

1616
/**
1717
* @var array<string, DashboardInterface>

src/Dashboards/Redis/Compatibility/Cluster/RedisCluster.php

Lines changed: 125 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace RobiNN\Pca\Dashboards\Redis\Compatibility\Cluster;
1010

11+
use InvalidArgumentException;
1112
use Redis;
1213
use RedisClusterException;
1314
use RobiNN\Pca\Dashboards\DashboardException;
@@ -19,6 +20,11 @@ class RedisCluster extends \RedisCluster implements RedisCompatibilityInterface
1920
use RedisJson;
2021
use RedisModules;
2122

23+
/**
24+
* @var array<int, mixed>
25+
*/
26+
private array $nodes;
27+
2228
/**
2329
* @var array<int|string, string>
2430
*/
@@ -50,6 +56,8 @@ public function __construct(array $server) {
5056
} catch (RedisClusterException $e) {
5157
throw new DashboardException($e->getMessage().' ['.implode(',', $server['nodes']).']');
5258
}
59+
60+
$this->nodes = $this->_masters();
5361
}
5462

5563
public function getType(string|int $type): string {
@@ -71,30 +79,44 @@ public function getKeyType(string $key): string {
7179
}
7280

7381
/**
74-
* @return array<int|string, mixed>
82+
* @param list<string>|null $combine
83+
*
84+
* @return array<string, array<string, mixed>>
7585
*
7686
* @throws RedisClusterException
7787
*/
78-
public function getInfo(?string $option = null): array {
88+
public function getInfo(?string $option = null, ?array $combine = null): array {
7989
static $info = [];
8090

8191
$options = ['SERVER', 'CLIENTS', 'MEMORY', 'PERSISTENCE', 'STATS', 'REPLICATION', 'CPU', 'CLUSTER', 'KEYSPACE'];
82-
$nodes = $this->_masters();
8392

8493
foreach ($options as $option_name) {
94+
/** @var array<string, array<int, mixed>|array<string, array<int, mixed>>> $combined */
8595
$combined = [];
8696

87-
foreach ($nodes as $node) {
97+
foreach ($this->nodes as $node) {
98+
/** @var array<string, mixed> $node_info */
8899
$node_info = $this->info($node, $option_name);
89100

90101
foreach ($node_info as $key => $value) {
91-
$combined[$key][] = $value;
102+
if (is_array($value)) {
103+
foreach ($value as $sub_key => $sub_val) {
104+
$combined[$key][$sub_key][] = $sub_val;
105+
}
106+
} else {
107+
$combined[$key][] = $value;
108+
}
92109
}
93110
}
94111

95112
foreach ($combined as $key => $values) {
96-
$unique = array_unique($values);
97-
$combined[$key] = count($unique) === 1 ? $unique[0] : $unique;
113+
if (is_array(reset($values))) {
114+
foreach ($values as $sub_key => $sub_values) {
115+
$combined[$key][$sub_key] = $this->combineValues($sub_key, $sub_values, $combine);
116+
}
117+
} else {
118+
$combined[$key] = $this->combineValues($key, $values, $combine);
119+
}
98120
}
99121

100122
$info[strtolower($option_name)] = $combined;
@@ -103,15 +125,42 @@ public function getInfo(?string $option = null): array {
103125
return $option !== null ? ($info[$option] ?? []) : $info;
104126
}
105127

128+
/**
129+
* @param list<mixed> $values
130+
* @param list<string>|null $combine
131+
*/
132+
private function combineValues(string $key, array $values, ?array $combine): mixed {
133+
$unique = array_unique($values);
134+
135+
if (count($unique) === 1) {
136+
return $unique[0];
137+
}
138+
139+
$numeric = array_filter($values, 'is_numeric');
140+
141+
if ($combine && in_array($key, $combine, true) && count($numeric) === count($values)) {
142+
return array_sum($values);
143+
}
144+
145+
if ($key === 'mem_fragmentation_ratio' && count($numeric) === count($values)) {
146+
return round(array_sum($values) / count($values), 2);
147+
}
148+
149+
if ($key === 'used_memory_peak' && count($numeric) === count($values)) {
150+
return max($values);
151+
}
152+
153+
return $values;
154+
}
155+
106156
/**
107157
* @return array<int, string>
108158
* @throws RedisClusterException
109159
*/
110160
public function scanKeys(string $pattern, int $count): array {
111161
$keys = [];
112-
$nodes = $this->_masters();
113162

114-
foreach ($nodes as $node) {
163+
foreach ($this->nodes as $node) {
115164
$iterator = null;
116165

117166
while (false !== ($scan = $this->scan($iterator, $node, $pattern, $count))) {
@@ -196,9 +245,7 @@ public function size(string $key): int {
196245
* @throws RedisClusterException
197246
*/
198247
public function flushDatabase(): bool {
199-
$nodes = $this->_masters();
200-
201-
foreach ($nodes as $node) {
248+
foreach ($this->nodes as $node) {
202249
$this->flushDB($node);
203250
}
204251

@@ -218,4 +265,70 @@ public function databaseSize(): int {
218265

219266
return $total;
220267
}
268+
269+
/**
270+
* @throws RedisClusterException|InvalidArgumentException
271+
*/
272+
public function execConfig(string $operation, mixed ...$args): mixed {
273+
switch (strtoupper($operation)) {
274+
case 'GET':
275+
if (empty($args)) {
276+
throw new InvalidArgumentException('CONFIG GET requires a parameter name.');
277+
}
278+
279+
$result = $this->rawcommand($this->nodes[0], 'CONFIG', 'GET', $args[0]);
280+
281+
return isset($result[0], $result[1]) ? [$result[0] => $result[1]] : [];
282+
case 'SET':
283+
if (count($args) < 2) {
284+
throw new InvalidArgumentException('CONFIG SET requires a parameter name and a value.');
285+
}
286+
287+
foreach ($this->nodes as $node) {
288+
$this->rawcommand($node, 'CONFIG', 'SET', $args[0], $args[1]);
289+
}
290+
291+
return true;
292+
case 'REWRITE':
293+
case 'RESETSTAT':
294+
foreach ($this->nodes as $node) {
295+
$this->rawcommand($node, 'CONFIG', strtoupper($operation));
296+
}
297+
298+
return true;
299+
default:
300+
throw new InvalidArgumentException('Unsupported CONFIG operation: '.$operation);
301+
}
302+
}
303+
304+
/**
305+
* @return null|array<int, mixed>
306+
*
307+
* @throws RedisClusterException
308+
*/
309+
public function getSlowlog(int $count): ?array {
310+
$all_logs = [];
311+
312+
foreach ($this->nodes as $node) {
313+
$logs = $this->rawcommand($node, 'SLOWLOG', 'GET', $count);
314+
315+
if (is_array($logs) && !empty($logs)) {
316+
array_push($all_logs, ...$logs);
317+
}
318+
}
319+
320+
usort($all_logs, static function ($a, $b) {
321+
return $b[1] <=> $a[1];
322+
});
323+
324+
return $all_logs;
325+
}
326+
327+
public function resetSlowlog(): bool {
328+
foreach ($this->nodes as $node) {
329+
$this->rawcommand($node, 'SLOWLOG', 'RESET');
330+
}
331+
332+
return true;
333+
}
221334
}

src/Dashboards/Redis/Compatibility/Predis.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,19 @@ public function flushDatabase(): bool {
181181
public function databaseSize(): int {
182182
return $this->dbsize();
183183
}
184+
185+
public function execConfig(string $operation, mixed ...$args): mixed {
186+
return $this->config($operation, ...$args);
187+
}
188+
189+
/**
190+
* @return null|array<int, mixed>
191+
*/
192+
public function getSlowlog(int $count): ?array {
193+
return $this->rawcommand('SLOWLOG', 'GET', $count);
194+
}
195+
196+
public function resetSlowlog(): bool {
197+
return $this->rawcommand('SLOWLOG', 'RESET');
198+
}
184199
}

src/Dashboards/Redis/Compatibility/Redis.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,19 @@ public function flushDatabase(): bool {
199199
public function databaseSize(): int {
200200
return $this->dbSize();
201201
}
202+
203+
public function execConfig(string $operation, mixed ...$args): mixed {
204+
return $this->config($operation, ...$args);
205+
}
206+
207+
/**
208+
* @return null|array<int, mixed>
209+
*/
210+
public function getSlowlog(int $count): ?array {
211+
return $this->rawcommand('SLOWLOG', 'GET', $count);
212+
}
213+
214+
public function resetSlowlog(): bool {
215+
return $this->rawcommand('SLOWLOG', 'RESET');
216+
}
202217
}

src/Dashboards/Redis/Compatibility/RedisCompatibilityInterface.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,21 @@ public function flushDatabase(): bool;
7474
* Alias to a dbSize().
7575
*/
7676
public function databaseSize(): int;
77+
78+
/**
79+
* Alias to a config().
80+
*/
81+
public function execConfig(string $operation, mixed ...$args): mixed;
82+
83+
/**
84+
* Get Slowlog entries.
85+
*
86+
* @return null|array<int, mixed>
87+
*/
88+
public function getSlowlog(int $count): ?array;
89+
90+
/**
91+
* Reset Slowlog.
92+
*/
93+
public function resetSlowlog(): bool;
7794
}

src/Dashboards/Redis/Compatibility/RedisModules.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
namespace RobiNN\Pca\Dashboards\Redis\Compatibility;
1010

1111
use Exception;
12-
use RedisException;
1312

1413
trait RedisModules {
1514
/**
@@ -22,7 +21,7 @@ public function getModules(): array {
2221

2322
try {
2423
$list = $this->rawCommand('MODULE', 'LIST'); // require Redis >= 4.0
25-
} catch (RedisException) {
24+
} catch (Exception) {
2625
return [];
2726
}
2827

src/Dashboards/Redis/RedisTrait.php

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,15 @@ private function panels(): string {
3030
}
3131

3232
try {
33-
$info = $this->redis->getInfo();
33+
$info = $this->redis->getInfo(null, [
34+
'redis_version',
35+
'used_memory',
36+
'maxmemory',
37+
'keyspace_hits',
38+
'keyspace_misses',
39+
'total_connections_received',
40+
'total_commands_processed',
41+
]);
3442

3543
$count_of_all_keys = 0;
3644

@@ -502,31 +510,26 @@ private function dbSelect(): string {
502510
* @throws Exception
503511
*/
504512
private function slowlog(): string {
505-
if ($this->is_cluster) {
506-
return $this->template->render('components/tabs', ['links' => ['keys' => 'Keys', 'slowlog' => 'Slow Log',],]).
507-
'Unsupported in cluster.';
508-
}
509-
510513
if (isset($_GET['resetlog'])) {
511-
$this->redis->rawCommand('SLOWLOG', 'RESET');
514+
$this->redis->resetSlowlog();
512515
Http::redirect(['tab']);
513516
}
514517

515518
if (isset($_POST['save'])) {
516-
$this->redis->config('SET', 'slowlog-max-len', Http::post('slowlog_max_items', '50'));
517-
$this->redis->config('SET', 'slowlog-log-slower-than', Http::post('slowlog_slower_than', '1000'));
519+
$this->redis->execConfig('SET', 'slowlog-max-len', Http::post('slowlog_max_items', 50));
520+
$this->redis->execConfig('SET', 'slowlog-log-slower-than', Http::post('slowlog_slower_than', 1000));
518521
Http::redirect(['tab']);
519522
}
520523

521-
$slowlog_max_items = $this->redis->config('GET', 'slowlog-max-len')['slowlog-max-len'];
522-
$slowlog_items = $this->redis->rawCommand('SLOWLOG', 'GET', $slowlog_max_items);
523-
$slowlog_slower_than = $this->redis->config('GET', 'slowlog-log-slower-than')['slowlog-log-slower-than'];
524+
$slowlog_max_items = (int) $this->redis->execConfig('GET', 'slowlog-max-len')['slowlog-max-len'];
525+
$slowlog_items = $this->redis->getSlowlog($slowlog_max_items);
526+
$slowlog_slower_than = $this->redis->execConfig('GET', 'slowlog-log-slower-than')['slowlog-log-slower-than'];
524527

525528
return $this->template->render('dashboards/redis/redis', [
526529
'slowlog' => [
527530
'items' => $slowlog_items ?? [],
528-
'max_items' => $slowlog_max_items ?? '',
529-
'slower_than' => $slowlog_slower_than ?? '',
531+
'max_items' => $slowlog_max_items,
532+
'slower_than' => $slowlog_slower_than ?? 1000,
530533
],
531534
]);
532535
}

0 commit comments

Comments
 (0)