mirror of
https://github.com/redis/redis.git
synced 2026-02-03 20:39:54 -05:00
Add cmd tips for HOTKEYS. Return err when hotkeys START specifies invalid slots (#14761)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Reply-schemas linter / reply-schemas-linter (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Reply-schemas linter / reply-schemas-linter (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
- When passing slots not within the range of a node to `HOTKEYS START SLOTS ...` the hotkey command now returns error. - Changed the cmd tips for the HOTKEYS subcommands so that they reflect the special nature of the cmd in cluster mode - i.e command should be issued against a single node only. Clients should not care about cluster management and aggregation of results. - Change reply schema to return Array of the maps. For a single node this will return array of 1 element. Getting results from multiple nodes will make it easy to concatenate the elements into one array.
This commit is contained in:
parent
02700f11cd
commit
b5a37c0e42
8 changed files with 374 additions and 164 deletions
|
|
@ -7320,7 +7320,11 @@ struct COMMAND_ARG FLUSHDB_Args[] = {
|
||||||
|
|
||||||
#ifndef SKIP_CMD_TIPS_TABLE
|
#ifndef SKIP_CMD_TIPS_TABLE
|
||||||
/* HOTKEYS GET tips */
|
/* HOTKEYS GET tips */
|
||||||
#define HOTKEYS_GET_Tips NULL
|
const char *HOTKEYS_GET_Tips[] = {
|
||||||
|
"nondeterministic_output",
|
||||||
|
"request_policy:special",
|
||||||
|
"response_policy:special",
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||||
|
|
@ -7337,7 +7341,9 @@ struct COMMAND_ARG FLUSHDB_Args[] = {
|
||||||
|
|
||||||
#ifndef SKIP_CMD_TIPS_TABLE
|
#ifndef SKIP_CMD_TIPS_TABLE
|
||||||
/* HOTKEYS RESET tips */
|
/* HOTKEYS RESET tips */
|
||||||
#define HOTKEYS_RESET_Tips NULL
|
const char *HOTKEYS_RESET_Tips[] = {
|
||||||
|
"request_policy:special",
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||||
|
|
@ -7354,7 +7360,9 @@ struct COMMAND_ARG FLUSHDB_Args[] = {
|
||||||
|
|
||||||
#ifndef SKIP_CMD_TIPS_TABLE
|
#ifndef SKIP_CMD_TIPS_TABLE
|
||||||
/* HOTKEYS START tips */
|
/* HOTKEYS START tips */
|
||||||
#define HOTKEYS_START_Tips NULL
|
const char *HOTKEYS_START_Tips[] = {
|
||||||
|
"request_policy:special",
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||||
|
|
@ -7393,7 +7401,9 @@ struct COMMAND_ARG HOTKEYS_START_Args[] = {
|
||||||
|
|
||||||
#ifndef SKIP_CMD_TIPS_TABLE
|
#ifndef SKIP_CMD_TIPS_TABLE
|
||||||
/* HOTKEYS STOP tips */
|
/* HOTKEYS STOP tips */
|
||||||
#define HOTKEYS_STOP_Tips NULL
|
const char *HOTKEYS_STOP_Tips[] = {
|
||||||
|
"request_policy:special",
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||||
|
|
@ -7403,10 +7413,10 @@ struct COMMAND_ARG HOTKEYS_START_Args[] = {
|
||||||
|
|
||||||
/* HOTKEYS command table */
|
/* HOTKEYS command table */
|
||||||
struct COMMAND_STRUCT HOTKEYS_Subcommands[] = {
|
struct COMMAND_STRUCT HOTKEYS_Subcommands[] = {
|
||||||
{MAKE_CMD("get","Returns lists of top K hotkeys depending on metrics chosen in HOTKEYS START command.","O(K) where K is the number of hotkeys returned.","8.6.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,HOTKEYS_GET_History,0,HOTKEYS_GET_Tips,0,hotkeysCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0,HOTKEYS_GET_Keyspecs,0,NULL,0)},
|
{MAKE_CMD("get","Returns lists of top K hotkeys depending on metrics chosen in HOTKEYS START command.","O(K) where K is the number of hotkeys returned.","8.6.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,HOTKEYS_GET_History,0,HOTKEYS_GET_Tips,3,hotkeysCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0,HOTKEYS_GET_Keyspecs,0,NULL,0)},
|
||||||
{MAKE_CMD("reset","Release the resources used for hotkey tracking.","O(1)","8.6.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,HOTKEYS_RESET_History,0,HOTKEYS_RESET_Tips,0,hotkeysCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0,HOTKEYS_RESET_Keyspecs,0,NULL,0)},
|
{MAKE_CMD("reset","Release the resources used for hotkey tracking.","O(1)","8.6.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,HOTKEYS_RESET_History,0,HOTKEYS_RESET_Tips,1,hotkeysCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0,HOTKEYS_RESET_Keyspecs,0,NULL,0)},
|
||||||
{MAKE_CMD("start","Starts hotkeys tracking.","O(1)","8.6.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,HOTKEYS_START_History,0,HOTKEYS_START_Tips,0,hotkeysCommand,-2,CMD_ADMIN|CMD_NOSCRIPT,0,HOTKEYS_START_Keyspecs,0,NULL,5),.args=HOTKEYS_START_Args},
|
{MAKE_CMD("start","Starts hotkeys tracking.","O(1)","8.6.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,HOTKEYS_START_History,0,HOTKEYS_START_Tips,1,hotkeysCommand,-2,CMD_ADMIN|CMD_NOSCRIPT,0,HOTKEYS_START_Keyspecs,0,NULL,5),.args=HOTKEYS_START_Args},
|
||||||
{MAKE_CMD("stop","Stops hotkeys tracking.","O(1)","8.6.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,HOTKEYS_STOP_History,0,HOTKEYS_STOP_Tips,0,hotkeysCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0,HOTKEYS_STOP_Keyspecs,0,NULL,0)},
|
{MAKE_CMD("stop","Stops hotkeys tracking.","O(1)","8.6.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,HOTKEYS_STOP_History,0,HOTKEYS_STOP_Tips,1,hotkeysCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0,HOTKEYS_STOP_Keyspecs,0,NULL,0)},
|
||||||
{0}
|
{0}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,101 +11,114 @@
|
||||||
"ADMIN",
|
"ADMIN",
|
||||||
"NOSCRIPT"
|
"NOSCRIPT"
|
||||||
],
|
],
|
||||||
|
"command_tips": [
|
||||||
|
"NONDETERMINISTIC_OUTPUT",
|
||||||
|
"REQUEST_POLICY:SPECIAL",
|
||||||
|
"RESPONSE_POLICY:SPECIAL"
|
||||||
|
],
|
||||||
"reply_schema": {
|
"reply_schema": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"description": "Map with various metrics (tracking-active, sample-ratio, selected-slots, time/network statistics), collection info (collection-start-time-unix-ms, collection-duration-ms, total-cpu-time-user-ms, total-cpu-time-sys-ms, total-net-bytes), and the requested lists of Top-K hotkeys (available metrics: by-cpu-time-us, by-net-bytes) where at most K hotkeys are returned.",
|
"description": "Array of maps with various metrics (tracking-active, sample-ratio, selected-slots, time/network statistics), collection info (collection-start-time-unix-ms, collection-duration-ms, total-cpu-time-user-ms, total-cpu-time-sys-ms, total-net-bytes), and the requested lists of Top-K hotkeys (available metrics: by-cpu-time-us, by-net-bytes) where at most K hotkeys are returned.",
|
||||||
"type": "object",
|
"type": "array",
|
||||||
"properties": {
|
"items": {
|
||||||
"tracking-active": {
|
"type": "object",
|
||||||
"type": "integer",
|
"properties": {
|
||||||
"description": "Whether hotkey tracking is currently active (1) or stopped (0)."
|
"tracking-active": {
|
||||||
},
|
"type": "integer",
|
||||||
"sample-ratio": {
|
"description": "Whether hotkey tracking is currently active (1) or stopped (0)."
|
||||||
"type": "integer",
|
|
||||||
"description": "The sampling ratio used for tracking."
|
|
||||||
},
|
|
||||||
"selected-slots": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
},
|
||||||
"description": "Array of slot numbers being tracked (empty if tracking all slots)."
|
"sample-ratio": {
|
||||||
},
|
"type": "integer",
|
||||||
"sampled-command-selected-slots-us": {
|
"description": "The sampling ratio used for tracking."
|
||||||
"type": "integer",
|
},
|
||||||
"description": "CPU time in microseconds for sampled commands in selected slots (only present when sampling and slots are configured)."
|
"selected-slots": {
|
||||||
},
|
"type": "array",
|
||||||
"all-commands-selected-slots-us": {
|
"items": {
|
||||||
"type": "integer",
|
"type": "array",
|
||||||
"description": "CPU time in microseconds for all commands in selected slots (only present when slots are configured)."
|
"items": {
|
||||||
},
|
|
||||||
"all-commands-all-slots-us": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "CPU time in microseconds for all commands across all slots."
|
|
||||||
},
|
|
||||||
"net-bytes-sampled-commands-selected-slots": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Network bytes for sampled commands in selected slots (only present when sampling and slots are configured)."
|
|
||||||
},
|
|
||||||
"net-bytes-all-commands-selected-slots": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Network bytes for all commands in selected slots (only present when slots are configured)."
|
|
||||||
},
|
|
||||||
"net-bytes-all-commands-all-slots": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Network bytes for all commands across all slots."
|
|
||||||
},
|
|
||||||
"collection-start-time-unix-ms": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Unix timestamp in milliseconds when collection started."
|
|
||||||
},
|
|
||||||
"collection-duration-ms": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Duration of collection in milliseconds."
|
|
||||||
},
|
|
||||||
"total-cpu-time-user-ms": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Total user CPU time in milliseconds (only present when CPU tracking is enabled)."
|
|
||||||
},
|
|
||||||
"total-cpu-time-sys-ms": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Total system CPU time in milliseconds (only present when CPU tracking is enabled)."
|
|
||||||
},
|
|
||||||
"total-net-bytes": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Total network bytes (only present when NET tracking is enabled)."
|
|
||||||
},
|
|
||||||
"by-cpu-time-us": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"description": "Flat array of key-value pairs (key1, cpu_time1, key2, cpu_time2, ...) for top-K hotkeys by CPU time in microseconds (only present when CPU tracking is enabled)."
|
|
||||||
},
|
|
||||||
"by-net-bytes": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
},
|
||||||
{
|
"minItems": 1,
|
||||||
"type": "integer"
|
"maxItems": 2
|
||||||
}
|
},
|
||||||
]
|
"description": "Array of slot ranges. Each element is an array: single-element [slot] for individual slots, or two-element [start, end] for inclusive ranges."
|
||||||
},
|
},
|
||||||
"description": "Flat array of key-value pairs (key1, bytes1, key2, bytes2, ...) for top-K hotkeys by network bytes (only present when NET tracking is enabled)."
|
"sampled-command-selected-slots-us": {
|
||||||
}
|
"type": "integer",
|
||||||
},
|
"description": "CPU time in microseconds for sampled commands in selected slots (only present when sampling and slots are configured)."
|
||||||
"additionalProperties": false
|
},
|
||||||
|
"all-commands-selected-slots-us": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "CPU time in microseconds for all commands in selected slots (only present when slots are configured)."
|
||||||
|
},
|
||||||
|
"all-commands-all-slots-us": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "CPU time in microseconds for all commands across all slots."
|
||||||
|
},
|
||||||
|
"net-bytes-sampled-commands-selected-slots": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Network bytes for sampled commands in selected slots (only present when sampling and slots are configured)."
|
||||||
|
},
|
||||||
|
"net-bytes-all-commands-selected-slots": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Network bytes for all commands in selected slots (only present when slots are configured)."
|
||||||
|
},
|
||||||
|
"net-bytes-all-commands-all-slots": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Network bytes for all commands across all slots."
|
||||||
|
},
|
||||||
|
"collection-start-time-unix-ms": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Unix timestamp in milliseconds when collection started."
|
||||||
|
},
|
||||||
|
"collection-duration-ms": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Duration of collection in milliseconds."
|
||||||
|
},
|
||||||
|
"total-cpu-time-user-ms": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Total user CPU time in milliseconds (only present when CPU tracking is enabled)."
|
||||||
|
},
|
||||||
|
"total-cpu-time-sys-ms": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Total system CPU time in milliseconds (only present when CPU tracking is enabled)."
|
||||||
|
},
|
||||||
|
"total-net-bytes": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Total network bytes (only present when NET tracking is enabled)."
|
||||||
|
},
|
||||||
|
"by-cpu-time-us": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "Flat array of key-value pairs (key1, cpu_time1, key2, cpu_time2, ...) for top-K hotkeys by CPU time in microseconds (only present when CPU tracking is enabled)."
|
||||||
|
},
|
||||||
|
"by-net-bytes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "Flat array of key-value pairs (key1, bytes1, key2, bytes2, ...) for top-K hotkeys by network bytes (only present when NET tracking is enabled)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "If no tracking is started",
|
"description": "If no tracking is started",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@
|
||||||
"ADMIN",
|
"ADMIN",
|
||||||
"NOSCRIPT"
|
"NOSCRIPT"
|
||||||
],
|
],
|
||||||
|
"command_tips": [
|
||||||
|
"REQUEST_POLICY:SPECIAL"
|
||||||
|
],
|
||||||
"reply_schema": {
|
"reply_schema": {
|
||||||
"const": "OK"
|
"const": "OK"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@
|
||||||
"ADMIN",
|
"ADMIN",
|
||||||
"NOSCRIPT"
|
"NOSCRIPT"
|
||||||
],
|
],
|
||||||
|
"command_tips": [
|
||||||
|
"REQUEST_POLICY:SPECIAL"
|
||||||
|
],
|
||||||
"reply_schema": {
|
"reply_schema": {
|
||||||
"const": "OK"
|
"const": "OK"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@
|
||||||
"ADMIN",
|
"ADMIN",
|
||||||
"NOSCRIPT"
|
"NOSCRIPT"
|
||||||
],
|
],
|
||||||
|
"command_tips": [
|
||||||
|
"REQUEST_POLICY:SPECIAL"
|
||||||
|
],
|
||||||
"reply_schema": {
|
"reply_schema": {
|
||||||
"const": "OK"
|
"const": "OK"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
128
src/hotkeys.c
128
src/hotkeys.c
|
|
@ -18,12 +18,17 @@ static inline int nearestNextPowerOf2(unsigned int count) {
|
||||||
return 1 << (32 - __builtin_clz(count-1));
|
return 1 << (32 - __builtin_clz(count-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Comparison function for qsort to sort slot indices */
|
||||||
|
static inline int slotCompare(const void *a, const void *b) {
|
||||||
|
return (*(const int *)a) - (*(const int *)b);
|
||||||
|
}
|
||||||
|
|
||||||
/* Initialize the hotkeys structure and start tracking. If tracking keys in
|
/* Initialize the hotkeys structure and start tracking. If tracking keys in
|
||||||
* specific slots is desired the user should pass along an already allocated and
|
* specific slots is desired the user should pass along an already allocated and
|
||||||
* populated slots array. The hotkeys structure takes ownership of the array and
|
* populated slotRangeArray. The hotkeys structure takes ownership of the array
|
||||||
* will free it upon release. On failure the slots memory is released. */
|
* and will free it upon release. On failure the slots memory is released. */
|
||||||
hotkeyStats *hotkeyStatsCreate(int count, int duration, int sample_ratio,
|
hotkeyStats *hotkeyStatsCreate(int count, int duration, int sample_ratio,
|
||||||
int *slots, int slots_count, uint64_t tracked_metrics)
|
slotRangeArray *slots, uint64_t tracked_metrics)
|
||||||
{
|
{
|
||||||
serverAssert(tracked_metrics & (HOTKEYS_TRACK_CPU | HOTKEYS_TRACK_NET));
|
serverAssert(tracked_metrics & (HOTKEYS_TRACK_CPU | HOTKEYS_TRACK_NET));
|
||||||
|
|
||||||
|
|
@ -44,7 +49,6 @@ hotkeyStats *hotkeyStatsCreate(int count, int duration, int sample_ratio,
|
||||||
hotkeys->duration = duration;
|
hotkeys->duration = duration;
|
||||||
hotkeys->sample_ratio = sample_ratio;
|
hotkeys->sample_ratio = sample_ratio;
|
||||||
hotkeys->slots = slots;
|
hotkeys->slots = slots;
|
||||||
hotkeys->numslots = slots_count;
|
|
||||||
hotkeys->active = 1;
|
hotkeys->active = 1;
|
||||||
hotkeys->keys_result = (getKeysResult)GETKEYS_RESULT_INIT;
|
hotkeys->keys_result = (getKeysResult)GETKEYS_RESULT_INIT;
|
||||||
hotkeys->start = server.mstime;
|
hotkeys->start = server.mstime;
|
||||||
|
|
@ -62,20 +66,17 @@ void hotkeyStatsRelease(hotkeyStats *hotkeys) {
|
||||||
if (!hotkeys) return;
|
if (!hotkeys) return;
|
||||||
if (hotkeys->cpu) chkTopKRelease(hotkeys->cpu);
|
if (hotkeys->cpu) chkTopKRelease(hotkeys->cpu);
|
||||||
if (hotkeys->net) chkTopKRelease(hotkeys->net);
|
if (hotkeys->net) chkTopKRelease(hotkeys->net);
|
||||||
zfree(hotkeys->slots);
|
slotRangeArrayFree(hotkeys->slots);
|
||||||
getKeysFreeResult(&hotkeys->keys_result);
|
getKeysFreeResult(&hotkeys->keys_result);
|
||||||
|
|
||||||
zfree(hotkeys);
|
zfree(hotkeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper function for hotkey tracking to check if a slot is in the selected
|
/* Helper function for hotkey tracking to check if a slot is in the selected
|
||||||
* slots list. If numslots is 0 then all slots are selected. */
|
* slots list. If slots is NULL then all slots are selected. */
|
||||||
static inline int isSlotSelected(hotkeyStats *hotkeys, int slot) {
|
static inline int isSlotSelected(hotkeyStats *hotkeys, int slot) {
|
||||||
if (hotkeys->numslots == 0) return 1;
|
if (hotkeys->slots == NULL) return 1;
|
||||||
for (int i = 0; i < hotkeys->numslots; i++) {
|
return slotRangeArrayContains(hotkeys->slots, slot);
|
||||||
if (hotkeys->slots[i] == slot) return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Preparation for updates of the hotkeyStats for the current command, f.e
|
/* Preparation for updates of the hotkeyStats for the current command, f.e
|
||||||
|
|
@ -192,9 +193,9 @@ size_t hotkeysGetMemoryUsage(hotkeyStats *hotkeys) {
|
||||||
if (hotkeys->net) {
|
if (hotkeys->net) {
|
||||||
memory_usage += chkTopKGetMemoryUsage(hotkeys->net);
|
memory_usage += chkTopKGetMemoryUsage(hotkeys->net);
|
||||||
}
|
}
|
||||||
/* Add memory for slots array if present */
|
/* Add memory for slotRangeArray if present */
|
||||||
if (hotkeys->slots) {
|
if (hotkeys->slots) {
|
||||||
memory_usage += sizeof(int) * hotkeys->numslots;
|
memory_usage += sizeof(slotRangeArray) + sizeof(slotRange) * hotkeys->slots->num_ranges;
|
||||||
}
|
}
|
||||||
|
|
||||||
return memory_usage;
|
return memory_usage;
|
||||||
|
|
@ -212,8 +213,41 @@ static int64_t time_diff_ms(struct timeval a, struct timeval b) {
|
||||||
return sec * 1000 + usec / 1000;
|
return sec * 1000 + usec / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Helper function to output a slotRangeArray as array of arrays.
|
||||||
|
* Single slots become 1-element arrays, ranges become 2-element arrays. */
|
||||||
|
static void addReplySlotRangeArray(client *c, slotRangeArray *slots) {
|
||||||
|
addReplyArrayLen(c, slots->num_ranges);
|
||||||
|
for (int i = 0; i < slots->num_ranges; i++) {
|
||||||
|
if (slots->ranges[i].start == slots->ranges[i].end) {
|
||||||
|
/* Single slot */
|
||||||
|
addReplyArrayLen(c, 1);
|
||||||
|
addReplyLongLong(c, slots->ranges[i].start);
|
||||||
|
} else {
|
||||||
|
/* Range */
|
||||||
|
addReplyArrayLen(c, 2);
|
||||||
|
addReplyLongLong(c, slots->ranges[i].start);
|
||||||
|
addReplyLongLong(c, slots->ranges[i].end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helper function to output selected-slots as array of arrays.
|
||||||
|
* If slots is NULL, outputs the local node's slot ranges (all slots in non-cluster mode). */
|
||||||
|
static void addReplySelectedSlots(client *c, hotkeyStats *hotkeys) {
|
||||||
|
if (hotkeys->slots == NULL) {
|
||||||
|
/* No specific slots selected - return the local node's slot ranges */
|
||||||
|
slotRangeArray *slots = clusterGetLocalSlotRanges();
|
||||||
|
addReplySlotRangeArray(c, slots);
|
||||||
|
slotRangeArrayFree(slots);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slots are already stored as a sorted/merged slotRangeArray */
|
||||||
|
addReplySlotRangeArray(c, hotkeys->slots);
|
||||||
|
}
|
||||||
|
|
||||||
/* HOTKEYS command implementation
|
/* HOTKEYS command implementation
|
||||||
*
|
*
|
||||||
* HOTKEYS START
|
* HOTKEYS START
|
||||||
* <METRICS count [CPU] [NET]>
|
* <METRICS count [CPU] [NET]>
|
||||||
* [COUNT k]
|
* [COUNT k]
|
||||||
|
|
@ -300,8 +334,7 @@ void hotkeysCommand(client *c) {
|
||||||
int count = 10; /* default */
|
int count = 10; /* default */
|
||||||
long duration = 0; /* default: no auto-stop */
|
long duration = 0; /* default: no auto-stop */
|
||||||
int sample_ratio = 1; /* default: track every key */
|
int sample_ratio = 1; /* default: track every key */
|
||||||
int slots_count = 0;
|
slotRangeArray *slots = NULL;
|
||||||
int *slots = NULL;
|
|
||||||
while (j < c->argc) {
|
while (j < c->argc) {
|
||||||
int moreargs = (c->argc-1) - j;
|
int moreargs = (c->argc-1) - j;
|
||||||
if (moreargs && !strcasecmp(c->argv[j]->ptr, "COUNT")) {
|
if (moreargs && !strcasecmp(c->argv[j]->ptr, "COUNT")) {
|
||||||
|
|
@ -309,7 +342,7 @@ void hotkeysCommand(client *c) {
|
||||||
if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, 64,
|
if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, 64,
|
||||||
&count_val, "COUNT must be between 1 and 64") != C_OK)
|
&count_val, "COUNT must be between 1 and 64") != C_OK)
|
||||||
{
|
{
|
||||||
zfree(slots);
|
slotRangeArrayFree(slots);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
count = (int)count_val;
|
count = (int)count_val;
|
||||||
|
|
@ -320,7 +353,7 @@ void hotkeysCommand(client *c) {
|
||||||
if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, 1000000,
|
if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, 1000000,
|
||||||
&duration, "DURATION must be between 1 and 1000000") != C_OK)
|
&duration, "DURATION must be between 1 and 1000000") != C_OK)
|
||||||
{
|
{
|
||||||
zfree(slots);
|
slotRangeArrayFree(slots);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
duration *= 1000;
|
duration *= 1000;
|
||||||
|
|
@ -330,7 +363,7 @@ void hotkeysCommand(client *c) {
|
||||||
if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, INT_MAX,
|
if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, INT_MAX,
|
||||||
&ratio_val, "SAMPLE ratio must be positive") != C_OK)
|
&ratio_val, "SAMPLE ratio must be positive") != C_OK)
|
||||||
{
|
{
|
||||||
zfree(slots);
|
slotRangeArrayFree(slots);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sample_ratio = (int)ratio_val;
|
sample_ratio = (int)ratio_val;
|
||||||
|
|
@ -343,7 +376,7 @@ void hotkeysCommand(client *c) {
|
||||||
|
|
||||||
if (slots) {
|
if (slots) {
|
||||||
addReplyError(c, "SLOTS parameter already specified");
|
addReplyError(c, "SLOTS parameter already specified");
|
||||||
zfree(slots);
|
slotRangeArrayFree(slots);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long slots_count_val;
|
long slots_count_val;
|
||||||
|
|
@ -355,41 +388,59 @@ void hotkeysCommand(client *c) {
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
slots_count = (int)slots_count_val;
|
int slots_count = (int)slots_count_val;
|
||||||
|
|
||||||
/* Parse slot numbers */
|
/* Parse slot numbers */
|
||||||
if (j + 1 + slots_count >= c->argc) {
|
if (j + 1 + slots_count >= c->argc) {
|
||||||
addReplyError(c, "not enough slot numbers provided");
|
addReplyError(c, "not enough slot numbers provided");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
slots = zmalloc(sizeof(int) * slots_count);
|
|
||||||
|
/* Collect slots into a temporary array for sorting */
|
||||||
|
int *temp_slots = zmalloc(sizeof(int) * slots_count);
|
||||||
for (int i = 0; i < slots_count; i++) {
|
for (int i = 0; i < slots_count; i++) {
|
||||||
long slot_val;
|
long slot_val;
|
||||||
if ((slot_val = getSlotOrReply(c, c->argv[j+2+i])) == -1) {
|
if ((slot_val = getSlotOrReply(c, c->argv[j+2+i])) == -1) {
|
||||||
zfree(slots);
|
zfree(temp_slots);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* Check for duplicate slot indices */
|
if (!clusterNodeCoversSlot(getMyClusterNode(), slot_val)) {
|
||||||
for (int k = 0; k < i; ++k) {
|
addReplyErrorFormat(c, "slot %ld not handled by this node", slot_val);
|
||||||
if (slots[k] == slot_val) {
|
zfree(temp_slots);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for duplicate slot */
|
||||||
|
for (int k = 0; k < i; k++) {
|
||||||
|
if (temp_slots[k] == slot_val) {
|
||||||
addReplyError(c, "duplicate slot number");
|
addReplyError(c, "duplicate slot number");
|
||||||
zfree(slots);
|
zfree(temp_slots);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slots[i] = (int)slot_val;
|
temp_slots[i] = (int)slot_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sort the slots array */
|
||||||
|
qsort(temp_slots, slots_count, sizeof(int), slotCompare);
|
||||||
|
|
||||||
|
/* Build slotRangeArray from sorted slots */
|
||||||
|
for (int i = 0; i < slots_count; i++) {
|
||||||
|
slots = slotRangeArrayAppend(slots, temp_slots[i]);
|
||||||
|
}
|
||||||
|
zfree(temp_slots);
|
||||||
|
|
||||||
j += 2 + slots_count;
|
j += 2 + slots_count;
|
||||||
} else {
|
} else {
|
||||||
addReplyError(c, "syntax error");
|
addReplyError(c, "syntax error");
|
||||||
if (slots) zfree(slots);
|
slotRangeArrayFree(slots);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hotkeyStats *hotkeys = hotkeyStatsCreate(count, duration, sample_ratio,
|
hotkeyStats *hotkeys = hotkeyStatsCreate(count, duration, sample_ratio,
|
||||||
slots, slots_count, tracked_metrics);
|
slots, tracked_metrics);
|
||||||
|
|
||||||
hotkeyStatsRelease(server.hotkeys);
|
hotkeyStatsRelease(server.hotkeys);
|
||||||
server.hotkeys = hotkeys;
|
server.hotkeys = hotkeys;
|
||||||
|
|
@ -472,11 +523,15 @@ void hotkeysCommand(client *c) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int has_selected_slots = (server.hotkeys->numslots > 0);
|
int has_selected_slots = (server.hotkeys->slots != NULL);
|
||||||
int has_sampling = (server.hotkeys->sample_ratio > 1);
|
int has_sampling = (server.hotkeys->sample_ratio > 1);
|
||||||
|
|
||||||
|
/* We return an array of map for easy aggregation of results from
|
||||||
|
* different nodes. */
|
||||||
|
addReplyArrayLen(c, 1);
|
||||||
|
|
||||||
int total_len = 7;
|
int total_len = 7;
|
||||||
void *arraylenptr = addReplyDeferredLen(c);
|
void *maplenptr = addReplyDeferredLen(c);
|
||||||
|
|
||||||
/* tracking-active */
|
/* tracking-active */
|
||||||
addReplyBulkCString(c, "tracking-active");
|
addReplyBulkCString(c, "tracking-active");
|
||||||
|
|
@ -486,12 +541,9 @@ void hotkeysCommand(client *c) {
|
||||||
addReplyBulkCString(c, "sample-ratio");
|
addReplyBulkCString(c, "sample-ratio");
|
||||||
addReplyLongLong(c, server.hotkeys->sample_ratio);
|
addReplyLongLong(c, server.hotkeys->sample_ratio);
|
||||||
|
|
||||||
/* selected-slots */
|
/* selected-slots - array of arrays with merged ranges */
|
||||||
addReplyBulkCString(c, "selected-slots");
|
addReplyBulkCString(c, "selected-slots");
|
||||||
addReplyArrayLen(c, server.hotkeys->numslots);
|
addReplySelectedSlots(c, server.hotkeys);
|
||||||
for (int i = 0; i < server.hotkeys->numslots; i++) {
|
|
||||||
addReplyLongLong(c, server.hotkeys->slots[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* sampled-command-selected-slots-us (conditional) */
|
/* sampled-command-selected-slots-us (conditional) */
|
||||||
if (has_sampling && has_selected_slots) {
|
if (has_sampling && has_selected_slots) {
|
||||||
|
|
@ -592,7 +644,7 @@ void hotkeysCommand(client *c) {
|
||||||
++total_len;
|
++total_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDeferredMapLen(c, arraylenptr, total_len);
|
setDeferredMapLen(c, maplenptr, total_len);
|
||||||
|
|
||||||
} else if (!strcasecmp(sub, "RESET")) {
|
} else if (!strcasecmp(sub, "RESET")) {
|
||||||
/* HOTKEYS RESET */
|
/* HOTKEYS RESET */
|
||||||
|
|
|
||||||
|
|
@ -2538,10 +2538,9 @@ struct hotkeyStats {
|
||||||
struct chkTopK *net;
|
struct chkTopK *net;
|
||||||
mstime_t start; /* Initial time point for wall time tracking */
|
mstime_t start; /* Initial time point for wall time tracking */
|
||||||
|
|
||||||
/* Only keys from selected slots will be tracked. If slots are not
|
/* Only keys from selected slots will be tracked. If slots is NULL,
|
||||||
* initialized - all keys are tracked. */
|
* all keys are tracked. Stored as a sorted slotRangeArray. */
|
||||||
int *slots;
|
struct slotRangeArray *slots;
|
||||||
int numslots;
|
|
||||||
|
|
||||||
/* Statistics counters. */
|
/* Statistics counters. */
|
||||||
uint64_t time_sampled_commands_selected_slots; /* microseconds */
|
uint64_t time_sampled_commands_selected_slots; /* microseconds */
|
||||||
|
|
@ -4132,7 +4131,7 @@ int validateHexDigest(client *c, const sds digest);
|
||||||
|
|
||||||
/* Hotkey tracking */
|
/* Hotkey tracking */
|
||||||
hotkeyStats *hotkeyStatsCreate(int count, int duration, int sample_ratio,
|
hotkeyStats *hotkeyStatsCreate(int count, int duration, int sample_ratio,
|
||||||
int *slots, int slots_count, uint64_t tracked_metrics);
|
struct slotRangeArray *slots, uint64_t tracked_metrics);
|
||||||
void hotkeyStatsRelease(hotkeyStats *hotkeys);
|
void hotkeyStatsRelease(hotkeyStats *hotkeys);
|
||||||
void hotkeyStatsPreCurrentCmd(hotkeyStats *hotkeys, client *c);
|
void hotkeyStatsPreCurrentCmd(hotkeyStats *hotkeys, client *c);
|
||||||
void hotkeyStatsUpdateCurrentCmd(hotkeyStats *hotkeys, hotkeyMetrics metrics);
|
void hotkeyStatsUpdateCurrentCmd(hotkeyStats *hotkeys, hotkeyMetrics metrics);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ proc hotkeys_array_to_dict {arr} {
|
||||||
return $result
|
return $result
|
||||||
}
|
}
|
||||||
|
|
||||||
start_server {tags {"hotkeys"}} {
|
start_server {tags {external:skip "hotkeys"}} {
|
||||||
test {HOTKEYS START - METRICS required} {
|
test {HOTKEYS START - METRICS required} {
|
||||||
catch {r hotkeys start} err
|
catch {r hotkeys start} err
|
||||||
assert_match "*METRICS parameter is required*" $err
|
assert_match "*METRICS parameter is required*" $err
|
||||||
|
|
@ -20,7 +20,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
r set key1 value1
|
r set key1 value1
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
r set key1 value1
|
r set key1 value1
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +58,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
r set key1 value1
|
r set key1 value1
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +126,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
|
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -154,7 +154,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
assert_equal {OK} [r hotkeys start METRICS 1 CPU DURATION 1]
|
assert_equal {OK} [r hotkeys start METRICS 1 CPU DURATION 1]
|
||||||
after 1500
|
after 1500
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] eq "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] eq "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +183,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET]
|
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET]
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +197,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
assert_equal {OK} [r hotkeys reset]
|
assert_equal {OK} [r hotkeys reset]
|
||||||
# After reset, GET should return nil
|
# After reset, GET should return nil
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
assert_equal {} $result
|
assert_equal {} $result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,7 +210,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
}
|
}
|
||||||
|
|
||||||
test {HOTKEYS GET - returns nil when not started} {
|
test {HOTKEYS GET - returns nil when not started} {
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
assert_equal {} $result
|
assert_equal {} $result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,7 +218,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE 5]
|
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE 5]
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -234,7 +234,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
r eval "redis.call('set', 'x', 2)" 1 x
|
r eval "redis.call('set', 'x', 2)" 1 x
|
||||||
r eval "redis.call('set', 'x', 3)" 1 x
|
r eval "redis.call('set', 'x', 3)" 1 x
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
set result [dict get $result "by-net-bytes"]
|
set result [dict get $result "by-net-bytes"]
|
||||||
assert [dict exists $result "x"]
|
assert [dict exists $result "x"]
|
||||||
assert [dict exists $result "y"]
|
assert [dict exists $result "y"]
|
||||||
|
|
@ -260,7 +260,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
r exec
|
r exec
|
||||||
|
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
assert_equal {OK} [r hotkeys reset]
|
assert_equal {OK} [r hotkeys reset]
|
||||||
|
|
||||||
# Check NET metrics
|
# Check NET metrics
|
||||||
|
|
@ -290,7 +290,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
r exec
|
r exec
|
||||||
|
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
assert_equal {OK} [r hotkeys reset]
|
assert_equal {OK} [r hotkeys reset]
|
||||||
|
|
||||||
# Check NET metrics - both keys should be tracked through EVAL commands
|
# Check NET metrics - both keys should be tracked through EVAL commands
|
||||||
|
|
@ -306,7 +306,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
r set key1 value1
|
r set key1 value1
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -354,7 +354,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
|
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
assert_not_equal $result {}
|
assert_not_equal $result {}
|
||||||
|
|
||||||
# Convert to dict if it's a flat array
|
# Convert to dict if it's a flat array
|
||||||
|
|
@ -407,7 +407,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start_server {tags {"hotkeys"}} {
|
start_server {tags {external:skip "hotkeys"}} {
|
||||||
test {HOTKEYS GET - RESP3 returns map with flat array values for hotkeys} {
|
test {HOTKEYS GET - RESP3 returns map with flat array values for hotkeys} {
|
||||||
r hello 3
|
r hello 3
|
||||||
|
|
||||||
|
|
@ -415,7 +415,7 @@ start_server {tags {"hotkeys"}} {
|
||||||
r set testkey testvalue
|
r set testkey testvalue
|
||||||
assert_equal {OK} [r hotkeys stop]
|
assert_equal {OK} [r hotkeys stop]
|
||||||
|
|
||||||
set result [r hotkeys get]
|
set result [lindex [r hotkeys get] 0]
|
||||||
|
|
||||||
# In RESP3, the outer result is a native map (dict)
|
# In RESP3, the outer result is a native map (dict)
|
||||||
assert [dict exists $result "tracking-active"]
|
assert [dict exists $result "tracking-active"]
|
||||||
|
|
@ -446,6 +446,25 @@ start_server {tags {"hotkeys"}} {
|
||||||
|
|
||||||
assert_equal {OK} [r hotkeys reset]
|
assert_equal {OK} [r hotkeys reset]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {HOTKEYS GET - selected-slots returns full range in non-cluster mode} {
|
||||||
|
assert_equal {OK} [r hotkeys start METRICS 1 CPU]
|
||||||
|
assert_equal {OK} [r hotkeys stop]
|
||||||
|
|
||||||
|
set result [lindex [r hotkeys get] 0]
|
||||||
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
|
set result [hotkeys_array_to_dict $result]
|
||||||
|
}
|
||||||
|
set slots [dict get $result "selected-slots"]
|
||||||
|
# Should return single range [[0, 16383]]
|
||||||
|
assert_equal 1 [llength $slots]
|
||||||
|
set range [lindex $slots 0]
|
||||||
|
assert_equal 2 [llength $range]
|
||||||
|
assert_equal 0 [lindex $range 0]
|
||||||
|
assert_equal 16383 [lindex $range 1]
|
||||||
|
|
||||||
|
assert_equal {OK} [r hotkeys reset]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
||||||
|
|
@ -471,18 +490,72 @@ start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
||||||
assert_match "*SLOTS parameter already specified*" $err
|
assert_match "*SLOTS parameter already specified*" $err
|
||||||
}
|
}
|
||||||
|
|
||||||
test {HOTKEYS GET - selected-slots field} {
|
test {HOTKEYS START - Error: invalid slot - negative value} {
|
||||||
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 1 -1} err
|
||||||
|
assert_match "*Invalid or out of range slot*" $err
|
||||||
|
}
|
||||||
|
|
||||||
|
test {HOTKEYS START - Error: invalid slot - out of range} {
|
||||||
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 1 16384} err
|
||||||
|
assert_match "*Invalid or out of range slot*" $err
|
||||||
|
}
|
||||||
|
|
||||||
|
test {HOTKEYS START - Error: invalid slot - non-integer} {
|
||||||
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 1 abc} err
|
||||||
|
assert_match "*Invalid or out of range slot*" $err
|
||||||
|
}
|
||||||
|
|
||||||
|
test {HOTKEYS GET - selected-slots field with individual slots} {
|
||||||
assert_equal {OK} [R 0 hotkeys start METRICS 2 CPU NET SLOTS 2 0 5]
|
assert_equal {OK} [R 0 hotkeys start METRICS 2 CPU NET SLOTS 2 0 5]
|
||||||
assert_equal {OK} [R 0 hotkeys stop]
|
assert_equal {OK} [R 0 hotkeys stop]
|
||||||
|
|
||||||
set result [R 0 hotkeys get]
|
set result [lindex [R 0 hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
set slots [dict get $result "selected-slots"]
|
set slots [dict get $result "selected-slots"]
|
||||||
|
# Two individual slots should return two 1-element arrays
|
||||||
assert_equal 2 [llength $slots]
|
assert_equal 2 [llength $slots]
|
||||||
assert_equal 0 [lindex $slots 0]
|
assert_equal {0} [lindex $slots 0]
|
||||||
assert_equal 5 [lindex $slots 1]
|
assert_equal {5} [lindex $slots 1]
|
||||||
|
|
||||||
|
assert_equal {OK} [R 0 hotkeys reset]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {HOTKEYS GET - selected-slots with unordered input slots are sorted} {
|
||||||
|
# Slots 10,5,1,0,6,2 should become [[0,2], [5,6], [10]]
|
||||||
|
assert_equal {OK} [R 0 hotkeys start METRICS 1 CPU SLOTS 6 10 5 1 0 6 2]
|
||||||
|
assert_equal {OK} [R 0 hotkeys stop]
|
||||||
|
|
||||||
|
set result [lindex [R 0 hotkeys get] 0]
|
||||||
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
|
set result [hotkeys_array_to_dict $result]
|
||||||
|
}
|
||||||
|
set slots [dict get $result "selected-slots"]
|
||||||
|
assert_equal 3 [llength $slots]
|
||||||
|
assert_equal {0 2} [lindex $slots 0]
|
||||||
|
assert_equal {5 6} [lindex $slots 1]
|
||||||
|
assert_equal {10} [lindex $slots 2]
|
||||||
|
|
||||||
|
assert_equal {OK} [R 0 hotkeys reset]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {HOTKEYS GET - selected-slots returns node's slot ranges when no SLOTS specified in cluster mode} {
|
||||||
|
# In a 1-node cluster, the node owns all slots [0-16383]
|
||||||
|
assert_equal {OK} [R 0 hotkeys start METRICS 1 CPU]
|
||||||
|
assert_equal {OK} [R 0 hotkeys stop]
|
||||||
|
|
||||||
|
set result [lindex [R 0 hotkeys get] 0]
|
||||||
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
|
set result [hotkeys_array_to_dict $result]
|
||||||
|
}
|
||||||
|
set slots [dict get $result "selected-slots"]
|
||||||
|
# 1-node cluster owns all slots, should return [[0, 16383]]
|
||||||
|
assert_equal 1 [llength $slots]
|
||||||
|
set range [lindex $slots 0]
|
||||||
|
assert_equal 2 [llength $range]
|
||||||
|
assert_equal 0 [lindex $range 0]
|
||||||
|
assert_equal 16383 [lindex $range 1]
|
||||||
|
|
||||||
assert_equal {OK} [R 0 hotkeys reset]
|
assert_equal {OK} [R 0 hotkeys reset]
|
||||||
}
|
}
|
||||||
|
|
@ -492,7 +565,7 @@ start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
||||||
R 0 set "{06S}key1" value1
|
R 0 set "{06S}key1" value1
|
||||||
assert_equal {OK} [R 0 hotkeys stop]
|
assert_equal {OK} [R 0 hotkeys stop]
|
||||||
|
|
||||||
set result [R 0 hotkeys get]
|
set result [lindex [R 0 hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -511,7 +584,7 @@ start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
||||||
R 0 set "{06S}key1" value1
|
R 0 set "{06S}key1" value1
|
||||||
assert_equal {OK} [R 0 hotkeys stop]
|
assert_equal {OK} [R 0 hotkeys stop]
|
||||||
|
|
||||||
set result [R 0 hotkeys get]
|
set result [lindex [R 0 hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -546,7 +619,7 @@ start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
||||||
|
|
||||||
assert_equal {OK} [R 0 hotkeys stop]
|
assert_equal {OK} [R 0 hotkeys stop]
|
||||||
|
|
||||||
set result [R 0 hotkeys get]
|
set result [lindex [R 0 hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -585,7 +658,7 @@ start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
||||||
|
|
||||||
assert_equal {OK} [R 0 hotkeys stop]
|
assert_equal {OK} [R 0 hotkeys stop]
|
||||||
|
|
||||||
set result [R 0 hotkeys get]
|
set result [lindex [R 0 hotkeys get] 0]
|
||||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
set result [hotkeys_array_to_dict $result]
|
set result [hotkeys_array_to_dict $result]
|
||||||
}
|
}
|
||||||
|
|
@ -618,3 +691,57 @@ start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start_cluster 2 0 {tags {external:skip cluster hotkeys}} {
|
||||||
|
|
||||||
|
test {HOTKEYS START - Error: slot not handled by this node} {
|
||||||
|
# In a 2-master cluster, each node handles half the slots.
|
||||||
|
# Node 0 handles slots 0-8191, Node 1 handles slots 8192-16383.
|
||||||
|
# Try to use a slot that belongs to node 1 on node 0.
|
||||||
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 1 8192} err
|
||||||
|
assert_match "*slot 8192 not handled by this node*" $err
|
||||||
|
catch {R 1 hotkeys start METRICS 1 CPU SLOTS 1 0} err
|
||||||
|
assert_match "*slot 0 not handled by this node*" $err
|
||||||
|
}
|
||||||
|
|
||||||
|
test {HOTKEYS GET - selected-slots returns each node's slot ranges in multi-node cluster} {
|
||||||
|
# In a 2-master cluster:
|
||||||
|
# Node 0 handles slots 0-8191
|
||||||
|
# Node 1 handles slots 8192-16383
|
||||||
|
|
||||||
|
# Test node 0
|
||||||
|
assert_equal {OK} [R 0 hotkeys start METRICS 1 CPU]
|
||||||
|
assert_equal {OK} [R 0 hotkeys stop]
|
||||||
|
|
||||||
|
set result [lindex [R 0 hotkeys get] 0]
|
||||||
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
|
set result [hotkeys_array_to_dict $result]
|
||||||
|
}
|
||||||
|
set slots [dict get $result "selected-slots"]
|
||||||
|
# Node 0 should return [[0, 8191]]
|
||||||
|
assert_equal 1 [llength $slots]
|
||||||
|
set range [lindex $slots 0]
|
||||||
|
assert_equal 2 [llength $range]
|
||||||
|
assert_equal 0 [lindex $range 0]
|
||||||
|
assert_equal 8191 [lindex $range 1]
|
||||||
|
|
||||||
|
assert_equal {OK} [R 0 hotkeys reset]
|
||||||
|
|
||||||
|
# Test node 1
|
||||||
|
assert_equal {OK} [R 1 hotkeys start METRICS 1 CPU]
|
||||||
|
assert_equal {OK} [R 1 hotkeys stop]
|
||||||
|
|
||||||
|
set result [lindex [R 1 hotkeys get] 0]
|
||||||
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||||
|
set result [hotkeys_array_to_dict $result]
|
||||||
|
}
|
||||||
|
set slots [dict get $result "selected-slots"]
|
||||||
|
# Node 1 should return [[8192, 16383]]
|
||||||
|
assert_equal 1 [llength $slots]
|
||||||
|
set range [lindex $slots 0]
|
||||||
|
assert_equal 2 [llength $range]
|
||||||
|
assert_equal 8192 [lindex $range 0]
|
||||||
|
assert_equal 16383 [lindex $range 1]
|
||||||
|
|
||||||
|
assert_equal {OK} [R 1 hotkeys reset]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue