Skip to content

Commit 9ac9ee6

Browse files
authored
Adding hotkeys commands support. (#3924)
* Adding hotkeys commands support. * Applying latest changes to the command spec * Fixing test keys pattern * Removing special case handling for determine slots for hotkeys commands * Adding detailed logging for Setup Test environment step in the github test actions * Applying review comments * Applying review comments
1 parent 65623b7 commit 9ac9ee6

9 files changed

Lines changed: 856 additions & 4 deletions

File tree

.github/actions/run-tests/action.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,22 @@ runs:
7878
echo "::endgroup::"
7979
8080
echo "::group::Starting Redis servers"
81-
redis_major_version=$(echo "$REDIS_VERSION" | grep -oP '^\d+')
81+
# Check if REDIS_VERSION is in the custom map
82+
mapped_version=""
83+
if [[ -n "${REDIS_VERSION_CUSTOM_MAP:-}" ]]; then
84+
for mapping in $REDIS_VERSION_CUSTOM_MAP; do
85+
tag="${mapping%%:*}"
86+
version="${mapping##*:}"
87+
if [[ "$REDIS_VERSION" == "$tag" ]]; then
88+
mapped_version="$version"
89+
echo "Found custom mapping: $REDIS_VERSION -> $mapped_version"
90+
break
91+
fi
92+
done
93+
fi
94+
# Use mapped version if found, otherwise use REDIS_VERSION
95+
version_to_parse="${mapped_version:-$REDIS_VERSION}"
96+
redis_major_version=$(echo "$version_to_parse" | grep -oP '^\d+')
8297
echo "REDIS_MAJOR_VERSION=${redis_major_version}" >> $GITHUB_ENV
8398
8499
if (( redis_major_version < 8 )); then

.github/workflows/integration.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ env:
3131
# for example after 8.2.1 is published, 8.2 image contains 8.2.1 content
3232
CURRENT_CLIENT_LIBS_TEST_STACK_IMAGE_TAG: '8.4.0'
3333
CURRENT_REDIS_VERSION: '8.4.0'
34+
REDIS_VERSION_CUSTOM_MAP: 'custom-21651605017-debian-amd64:8.6'
3435

3536
jobs:
3637
dependency-audit:
@@ -76,7 +77,7 @@ jobs:
7677
max-parallel: 15
7778
fail-fast: false
7879
matrix:
79-
redis-version: ['8.6-rc1-21356658603-debian-amd64', '${{ needs.redis_version.outputs.CURRENT }}', '8.2', '8.0.2' ,'7.4.4', '7.2.9']
80+
redis-version: ['custom-21651605017-debian-amd64', '${{ needs.redis_version.outputs.CURRENT }}', '8.2', '8.0.2' ,'7.4.4', '7.2.9']
8081
python-version: ['3.10', '3.14']
8182
parser-backend: ['plain']
8283
event-loop: ['asyncio']

redis/_parsers/helpers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,7 @@ def string_keys_to_dict(key_string, callback):
880880
map(lambda ll: (float(ll[0]), float(ll[1])) if ll is not None else None, r)
881881
),
882882
"HGETALL": lambda r: r and pairs_to_dict(r) or {},
883+
"HOTKEYS GET": lambda r: [pairs_to_dict(m) for m in r],
883884
"MEMORY STATS": parse_memory_stats,
884885
"MODULE LIST": lambda r: [pairs_to_dict(m) for m in r],
885886
"RESET": str_if_bytes,

redis/commands/cluster.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
TYPE_CHECKING,
44
Any,
55
AsyncIterator,
6+
Awaitable,
67
Dict,
78
Iterable,
89
Iterator,
@@ -38,6 +39,7 @@
3839
AsyncScriptCommands,
3940
DataAccessCommands,
4041
FunctionCommands,
42+
HotkeysMetricsTypes,
4143
ManagementCommands,
4244
ModuleCommands,
4345
PubSubCommands,
@@ -827,6 +829,54 @@ def client_tracking_off(
827829
target_nodes=target_nodes,
828830
)
829831

832+
def hotkeys_start(
833+
self,
834+
metrics: List[HotkeysMetricsTypes],
835+
count: Optional[int] = None,
836+
duration: Optional[int] = None,
837+
sample_ratio: Optional[int] = None,
838+
slots: Optional[List[int]] = None,
839+
**kwargs,
840+
) -> Union[str, bytes]:
841+
"""
842+
Cluster client does not support hotkeys command. Please use the non-cluster client.
843+
844+
For more information see https://redis.io/commands/hotkeys-start
845+
"""
846+
raise NotImplementedError(
847+
"HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
848+
)
849+
850+
def hotkeys_stop(self, **kwargs) -> Union[str, bytes]:
851+
"""
852+
Cluster client does not support hotkeys command. Please use the non-cluster client.
853+
854+
For more information see https://redis.io/commands/hotkeys-stop
855+
"""
856+
raise NotImplementedError(
857+
"HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
858+
)
859+
860+
def hotkeys_reset(self, **kwargs) -> Union[str, bytes]:
861+
"""
862+
Cluster client does not support hotkeys command. Please use the non-cluster client.
863+
864+
For more information see https://redis.io/commands/hotkeys-reset
865+
"""
866+
raise NotImplementedError(
867+
"HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
868+
)
869+
870+
def hotkeys_get(self, **kwargs) -> list[dict[Union[str, bytes], Any]]:
871+
"""
872+
Cluster client does not support hotkeys command. Please use the non-cluster client.
873+
874+
For more information see https://redis.io/commands/hotkeys-get
875+
"""
876+
raise NotImplementedError(
877+
"HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
878+
)
879+
830880

831881
class AsyncClusterManagementCommands(
832882
ClusterManagementCommands, AsyncManagementCommands
@@ -924,6 +974,56 @@ async def client_tracking_off(
924974
target_nodes=target_nodes,
925975
)
926976

977+
async def hotkeys_start(
978+
self,
979+
metrics: List[HotkeysMetricsTypes],
980+
count: Optional[int] = None,
981+
duration: Optional[int] = None,
982+
sample_ratio: Optional[int] = None,
983+
slots: Optional[List[int]] = None,
984+
**kwargs,
985+
) -> Awaitable[Union[str, bytes]]:
986+
"""
987+
Cluster client does not support hotkeys command. Please use the non-cluster client.
988+
989+
For more information see https://redis.io/commands/hotkeys-start
990+
"""
991+
raise NotImplementedError(
992+
"HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
993+
)
994+
995+
async def hotkeys_stop(self, **kwargs) -> Awaitable[Union[str, bytes]]:
996+
"""
997+
Cluster client does not support hotkeys command. Please use the non-cluster client.
998+
999+
For more information see https://redis.io/commands/hotkeys-stop
1000+
"""
1001+
raise NotImplementedError(
1002+
"HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1003+
)
1004+
1005+
async def hotkeys_reset(self, **kwargs) -> Awaitable[Union[str, bytes]]:
1006+
"""
1007+
Cluster client does not support hotkeys command. Please use the non-cluster client.
1008+
1009+
For more information see https://redis.io/commands/hotkeys-reset
1010+
"""
1011+
raise NotImplementedError(
1012+
"HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1013+
)
1014+
1015+
async def hotkeys_get(
1016+
self, **kwargs
1017+
) -> Awaitable[list[dict[Union[str, bytes], Any]]]:
1018+
"""
1019+
Cluster client does not support hotkeys command. Please use the non-cluster client.
1020+
1021+
For more information see https://redis.io/commands/hotkeys-get
1022+
"""
1023+
raise NotImplementedError(
1024+
"HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1025+
)
1026+
9271027

9281028
class ClusterDataAccessCommands(DataAccessCommands):
9291029
"""

redis/commands/core.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,11 @@ def acl_whoami(self, **kwargs) -> ResponseT:
405405
AsyncACLCommands = ACLCommands
406406

407407

408+
class HotkeysMetricsTypes(Enum):
409+
CPU = "CPU"
410+
NET = "NET"
411+
412+
408413
class ManagementCommands(CommandsProtocol):
409414
"""
410415
Redis management commands
@@ -1406,6 +1411,94 @@ def failover(self):
14061411
"FAILOVER is intentionally not implemented in the client."
14071412
)
14081413

1414+
def hotkeys_start(
1415+
self,
1416+
metrics: List[HotkeysMetricsTypes],
1417+
count: Optional[int] = None,
1418+
duration: Optional[int] = None,
1419+
sample_ratio: Optional[int] = None,
1420+
slots: Optional[List[int]] = None,
1421+
**kwargs,
1422+
) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]:
1423+
"""
1424+
Start collecting hotkeys data.
1425+
Returns an error if there is an ongoing collection session.
1426+
1427+
Args:
1428+
count: The number of keys to collect in each criteria (CPU and network consumption)
1429+
metrics: List of metrics to track. Supported values: [HotkeysMetricsTypes.CPU, HotkeysMetricsTypes.NET]
1430+
duration: Automatically stop the collection after `duration` seconds
1431+
sample_ratio: Commands are sampled with probability 1/ratio (1 means no sampling)
1432+
slots: Only track keys on the specified hash slots
1433+
1434+
For more information, see https://redis.io/commands/hotkeys-start
1435+
"""
1436+
args: List[Union[str, int]] = ["HOTKEYS", "START"]
1437+
1438+
# Add METRICS
1439+
args.extend(["METRICS", len(metrics)])
1440+
args.extend([str(m.value) for m in metrics])
1441+
1442+
# Add COUNT
1443+
if count is not None:
1444+
args.extend(["COUNT", count])
1445+
1446+
# Add optional DURATION
1447+
if duration is not None:
1448+
args.extend(["DURATION", duration])
1449+
1450+
# Add optional SAMPLE ratio
1451+
if sample_ratio is not None:
1452+
args.extend(["SAMPLE", sample_ratio])
1453+
1454+
# Add optional SLOTS
1455+
if slots is not None:
1456+
args.append("SLOTS")
1457+
args.append(len(slots))
1458+
args.extend(slots)
1459+
1460+
return self.execute_command(*args, **kwargs)
1461+
1462+
def hotkeys_stop(
1463+
self, **kwargs
1464+
) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]:
1465+
"""
1466+
Stop the ongoing hotkeys collection session (if any).
1467+
The results of the last collection session are kept for consumption with HOTKEYS GET.
1468+
1469+
For more information, see https://redis.io/commands/hotkeys-stop
1470+
"""
1471+
return self.execute_command("HOTKEYS STOP", **kwargs)
1472+
1473+
def hotkeys_reset(
1474+
self, **kwargs
1475+
) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]:
1476+
"""
1477+
Discard the last hotkeys collection session results (in order to save memory).
1478+
Error if there is an ongoing collection session.
1479+
1480+
For more information, see https://redis.io/commands/hotkeys-reset
1481+
"""
1482+
return self.execute_command("HOTKEYS RESET", **kwargs)
1483+
1484+
def hotkeys_get(
1485+
self, **kwargs
1486+
) -> Union[
1487+
Awaitable[list[dict[Union[str, bytes], Any]]],
1488+
list[dict[Union[str, bytes], Any]],
1489+
]:
1490+
"""
1491+
Retrieve the result of the ongoing collection session (if any),
1492+
or the last collection session (if any).
1493+
1494+
HOTKEYS GET response is wrapped in an array for aggregation support.
1495+
Each node returns a single-element array, allowing multiple node
1496+
responses to be concatenated by DMC or other aggregators.
1497+
1498+
For more information, see https://redis.io/commands/hotkeys-get
1499+
"""
1500+
return self.execute_command("HOTKEYS GET", **kwargs)
1501+
14091502

14101503
class AsyncManagementCommands(ManagementCommands):
14111504
async def command_info(self, **kwargs) -> None:

tests/test_asyncio/test_cluster.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
LoadBalancingStrategy,
2727
get_node_name,
2828
)
29+
from redis.commands.core import HotkeysMetricsTypes
2930
from redis.crc import REDIS_CLUSTER_HASH_SLOTS, key_slot
3031
from redis.exceptions import (
3132
AskError,
@@ -2465,6 +2466,19 @@ async def test_acl_log(
24652466

24662467
await user_client.aclose()
24672468

2469+
@skip_if_server_version_lt("8.5.240")
2470+
async def test_hotkeys_cluster(self, r: RedisCluster) -> None:
2471+
"""Test all HOTKEYS commands in cluster are raising an error"""
2472+
2473+
with pytest.raises(NotImplementedError):
2474+
await r.hotkeys_start(count=10, metrics=[HotkeysMetricsTypes.CPU])
2475+
with pytest.raises(NotImplementedError):
2476+
await r.hotkeys_get()
2477+
with pytest.raises(NotImplementedError):
2478+
await r.hotkeys_reset()
2479+
with pytest.raises(NotImplementedError):
2480+
await r.hotkeys_stop()
2481+
24682482

24692483
class TestNodesManager:
24702484
"""

0 commit comments

Comments
 (0)