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
|
||||
/* HOTKEYS GET tips */
|
||||
#define HOTKEYS_GET_Tips NULL
|
||||
const char *HOTKEYS_GET_Tips[] = {
|
||||
"nondeterministic_output",
|
||||
"request_policy:special",
|
||||
"response_policy:special",
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
|
|
@ -7337,7 +7341,9 @@ struct COMMAND_ARG FLUSHDB_Args[] = {
|
|||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HOTKEYS RESET tips */
|
||||
#define HOTKEYS_RESET_Tips NULL
|
||||
const char *HOTKEYS_RESET_Tips[] = {
|
||||
"request_policy:special",
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
|
|
@ -7354,7 +7360,9 @@ struct COMMAND_ARG FLUSHDB_Args[] = {
|
|||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HOTKEYS START tips */
|
||||
#define HOTKEYS_START_Tips NULL
|
||||
const char *HOTKEYS_START_Tips[] = {
|
||||
"request_policy:special",
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
|
|
@ -7393,7 +7401,9 @@ struct COMMAND_ARG HOTKEYS_START_Args[] = {
|
|||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HOTKEYS STOP tips */
|
||||
#define HOTKEYS_STOP_Tips NULL
|
||||
const char *HOTKEYS_STOP_Tips[] = {
|
||||
"request_policy:special",
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
|
|
@ -7403,10 +7413,10 @@ struct COMMAND_ARG HOTKEYS_START_Args[] = {
|
|||
|
||||
/* HOTKEYS command table */
|
||||
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("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("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("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("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,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,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,1,hotkeysCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0,HOTKEYS_STOP_Keyspecs,0,NULL,0)},
|
||||
{0}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,17 @@
|
|||
"ADMIN",
|
||||
"NOSCRIPT"
|
||||
],
|
||||
"command_tips": [
|
||||
"NONDETERMINISTIC_OUTPUT",
|
||||
"REQUEST_POLICY:SPECIAL",
|
||||
"RESPONSE_POLICY:SPECIAL"
|
||||
],
|
||||
"reply_schema": {
|
||||
"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": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tracking-active": {
|
||||
|
|
@ -26,11 +33,16 @@
|
|||
"description": "The sampling ratio used for tracking."
|
||||
},
|
||||
"selected-slots": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "Array of slot numbers being tracked (empty if tracking all slots)."
|
||||
"minItems": 1,
|
||||
"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."
|
||||
},
|
||||
"sampled-command-selected-slots-us": {
|
||||
"type": "integer",
|
||||
|
|
@ -106,6 +118,7 @@
|
|||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "If no tracking is started",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"ADMIN",
|
||||
"NOSCRIPT"
|
||||
],
|
||||
"command_tips": [
|
||||
"REQUEST_POLICY:SPECIAL"
|
||||
],
|
||||
"reply_schema": {
|
||||
"const": "OK"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"ADMIN",
|
||||
"NOSCRIPT"
|
||||
],
|
||||
"command_tips": [
|
||||
"REQUEST_POLICY:SPECIAL"
|
||||
],
|
||||
"reply_schema": {
|
||||
"const": "OK"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"ADMIN",
|
||||
"NOSCRIPT"
|
||||
],
|
||||
"command_tips": [
|
||||
"REQUEST_POLICY:SPECIAL"
|
||||
],
|
||||
"reply_schema": {
|
||||
"const": "OK"
|
||||
}
|
||||
|
|
|
|||
126
src/hotkeys.c
126
src/hotkeys.c
|
|
@ -18,12 +18,17 @@ static inline int nearestNextPowerOf2(unsigned int count) {
|
|||
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
|
||||
* 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
|
||||
* will free it upon release. On failure the slots memory is released. */
|
||||
* populated slotRangeArray. The hotkeys structure takes ownership of the array
|
||||
* and will free it upon release. On failure the slots memory is released. */
|
||||
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));
|
||||
|
||||
|
|
@ -44,7 +49,6 @@ hotkeyStats *hotkeyStatsCreate(int count, int duration, int sample_ratio,
|
|||
hotkeys->duration = duration;
|
||||
hotkeys->sample_ratio = sample_ratio;
|
||||
hotkeys->slots = slots;
|
||||
hotkeys->numslots = slots_count;
|
||||
hotkeys->active = 1;
|
||||
hotkeys->keys_result = (getKeysResult)GETKEYS_RESULT_INIT;
|
||||
hotkeys->start = server.mstime;
|
||||
|
|
@ -62,20 +66,17 @@ void hotkeyStatsRelease(hotkeyStats *hotkeys) {
|
|||
if (!hotkeys) return;
|
||||
if (hotkeys->cpu) chkTopKRelease(hotkeys->cpu);
|
||||
if (hotkeys->net) chkTopKRelease(hotkeys->net);
|
||||
zfree(hotkeys->slots);
|
||||
slotRangeArrayFree(hotkeys->slots);
|
||||
getKeysFreeResult(&hotkeys->keys_result);
|
||||
|
||||
zfree(hotkeys);
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
if (hotkeys->numslots == 0) return 1;
|
||||
for (int i = 0; i < hotkeys->numslots; i++) {
|
||||
if (hotkeys->slots[i] == slot) return 1;
|
||||
}
|
||||
return 0;
|
||||
if (hotkeys->slots == NULL) return 1;
|
||||
return slotRangeArrayContains(hotkeys->slots, slot);
|
||||
}
|
||||
|
||||
/* Preparation for updates of the hotkeyStats for the current command, f.e
|
||||
|
|
@ -192,9 +193,9 @@ size_t hotkeysGetMemoryUsage(hotkeyStats *hotkeys) {
|
|||
if (hotkeys->net) {
|
||||
memory_usage += chkTopKGetMemoryUsage(hotkeys->net);
|
||||
}
|
||||
/* Add memory for slots array if present */
|
||||
/* Add memory for slotRangeArray if present */
|
||||
if (hotkeys->slots) {
|
||||
memory_usage += sizeof(int) * hotkeys->numslots;
|
||||
memory_usage += sizeof(slotRangeArray) + sizeof(slotRange) * hotkeys->slots->num_ranges;
|
||||
}
|
||||
|
||||
return memory_usage;
|
||||
|
|
@ -212,6 +213,39 @@ static int64_t time_diff_ms(struct timeval a, struct timeval b) {
|
|||
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 START
|
||||
|
|
@ -300,8 +334,7 @@ void hotkeysCommand(client *c) {
|
|||
int count = 10; /* default */
|
||||
long duration = 0; /* default: no auto-stop */
|
||||
int sample_ratio = 1; /* default: track every key */
|
||||
int slots_count = 0;
|
||||
int *slots = NULL;
|
||||
slotRangeArray *slots = NULL;
|
||||
while (j < c->argc) {
|
||||
int moreargs = (c->argc-1) - j;
|
||||
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,
|
||||
&count_val, "COUNT must be between 1 and 64") != C_OK)
|
||||
{
|
||||
zfree(slots);
|
||||
slotRangeArrayFree(slots);
|
||||
return;
|
||||
}
|
||||
count = (int)count_val;
|
||||
|
|
@ -320,7 +353,7 @@ void hotkeysCommand(client *c) {
|
|||
if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, 1000000,
|
||||
&duration, "DURATION must be between 1 and 1000000") != C_OK)
|
||||
{
|
||||
zfree(slots);
|
||||
slotRangeArrayFree(slots);
|
||||
return;
|
||||
}
|
||||
duration *= 1000;
|
||||
|
|
@ -330,7 +363,7 @@ void hotkeysCommand(client *c) {
|
|||
if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, INT_MAX,
|
||||
&ratio_val, "SAMPLE ratio must be positive") != C_OK)
|
||||
{
|
||||
zfree(slots);
|
||||
slotRangeArrayFree(slots);
|
||||
return;
|
||||
}
|
||||
sample_ratio = (int)ratio_val;
|
||||
|
|
@ -343,7 +376,7 @@ void hotkeysCommand(client *c) {
|
|||
|
||||
if (slots) {
|
||||
addReplyError(c, "SLOTS parameter already specified");
|
||||
zfree(slots);
|
||||
slotRangeArrayFree(slots);
|
||||
return;
|
||||
}
|
||||
long slots_count_val;
|
||||
|
|
@ -355,41 +388,59 @@ void hotkeysCommand(client *c) {
|
|||
{
|
||||
return;
|
||||
}
|
||||
slots_count = (int)slots_count_val;
|
||||
int slots_count = (int)slots_count_val;
|
||||
|
||||
/* Parse slot numbers */
|
||||
if (j + 1 + slots_count >= c->argc) {
|
||||
addReplyError(c, "not enough slot numbers provided");
|
||||
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++) {
|
||||
long slot_val;
|
||||
if ((slot_val = getSlotOrReply(c, c->argv[j+2+i])) == -1) {
|
||||
zfree(slots);
|
||||
zfree(temp_slots);
|
||||
return;
|
||||
}
|
||||
/* Check for duplicate slot indices */
|
||||
for (int k = 0; k < i; ++k) {
|
||||
if (slots[k] == slot_val) {
|
||||
if (!clusterNodeCoversSlot(getMyClusterNode(), slot_val)) {
|
||||
addReplyErrorFormat(c, "slot %ld not handled by this node", 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");
|
||||
zfree(slots);
|
||||
zfree(temp_slots);
|
||||
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;
|
||||
} else {
|
||||
addReplyError(c, "syntax error");
|
||||
if (slots) zfree(slots);
|
||||
slotRangeArrayFree(slots);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
hotkeyStats *hotkeys = hotkeyStatsCreate(count, duration, sample_ratio,
|
||||
slots, slots_count, tracked_metrics);
|
||||
slots, tracked_metrics);
|
||||
|
||||
hotkeyStatsRelease(server.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);
|
||||
|
||||
/* We return an array of map for easy aggregation of results from
|
||||
* different nodes. */
|
||||
addReplyArrayLen(c, 1);
|
||||
|
||||
int total_len = 7;
|
||||
void *arraylenptr = addReplyDeferredLen(c);
|
||||
void *maplenptr = addReplyDeferredLen(c);
|
||||
|
||||
/* tracking-active */
|
||||
addReplyBulkCString(c, "tracking-active");
|
||||
|
|
@ -486,12 +541,9 @@ void hotkeysCommand(client *c) {
|
|||
addReplyBulkCString(c, "sample-ratio");
|
||||
addReplyLongLong(c, server.hotkeys->sample_ratio);
|
||||
|
||||
/* selected-slots */
|
||||
/* selected-slots - array of arrays with merged ranges */
|
||||
addReplyBulkCString(c, "selected-slots");
|
||||
addReplyArrayLen(c, server.hotkeys->numslots);
|
||||
for (int i = 0; i < server.hotkeys->numslots; i++) {
|
||||
addReplyLongLong(c, server.hotkeys->slots[i]);
|
||||
}
|
||||
addReplySelectedSlots(c, server.hotkeys);
|
||||
|
||||
/* sampled-command-selected-slots-us (conditional) */
|
||||
if (has_sampling && has_selected_slots) {
|
||||
|
|
@ -592,7 +644,7 @@ void hotkeysCommand(client *c) {
|
|||
++total_len;
|
||||
}
|
||||
|
||||
setDeferredMapLen(c, arraylenptr, total_len);
|
||||
setDeferredMapLen(c, maplenptr, total_len);
|
||||
|
||||
} else if (!strcasecmp(sub, "RESET")) {
|
||||
/* HOTKEYS RESET */
|
||||
|
|
|
|||
|
|
@ -2538,10 +2538,9 @@ struct hotkeyStats {
|
|||
struct chkTopK *net;
|
||||
mstime_t start; /* Initial time point for wall time tracking */
|
||||
|
||||
/* Only keys from selected slots will be tracked. If slots are not
|
||||
* initialized - all keys are tracked. */
|
||||
int *slots;
|
||||
int numslots;
|
||||
/* Only keys from selected slots will be tracked. If slots is NULL,
|
||||
* all keys are tracked. Stored as a sorted slotRangeArray. */
|
||||
struct slotRangeArray *slots;
|
||||
|
||||
/* Statistics counters. */
|
||||
uint64_t time_sampled_commands_selected_slots; /* microseconds */
|
||||
|
|
@ -4132,7 +4131,7 @@ int validateHexDigest(client *c, const sds digest);
|
|||
|
||||
/* Hotkey tracking */
|
||||
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 hotkeyStatsPreCurrentCmd(hotkeyStats *hotkeys, client *c);
|
||||
void hotkeyStatsUpdateCurrentCmd(hotkeyStats *hotkeys, hotkeyMetrics metrics);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ proc hotkeys_array_to_dict {arr} {
|
|||
return $result
|
||||
}
|
||||
|
||||
start_server {tags {"hotkeys"}} {
|
||||
start_server {tags {external:skip "hotkeys"}} {
|
||||
test {HOTKEYS START - METRICS required} {
|
||||
catch {r hotkeys start} err
|
||||
assert_match "*METRICS parameter is required*" $err
|
||||
|
|
@ -20,7 +20,7 @@ start_server {tags {"hotkeys"}} {
|
|||
r set key1 value1
|
||||
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"} {
|
||||
set result [hotkeys_array_to_dict $result]
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ start_server {tags {"hotkeys"}} {
|
|||
r set key1 value1
|
||||
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"} {
|
||||
set result [hotkeys_array_to_dict $result]
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ start_server {tags {"hotkeys"}} {
|
|||
r set key1 value1
|
||||
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"} {
|
||||
set result [hotkeys_array_to_dict $result]
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ start_server {tags {"hotkeys"}} {
|
|||
|
||||
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"} {
|
||||
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]
|
||||
after 1500
|
||||
|
||||
set result [r hotkeys get]
|
||||
set result [lindex [r hotkeys get] 0]
|
||||
if {[llength $result] > 0 && [lindex $result 0] eq "tracking-active"} {
|
||||
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 stop]
|
||||
|
||||
set result [r hotkeys get]
|
||||
set result [lindex [r hotkeys get] 0]
|
||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||
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 reset]
|
||||
# After reset, GET should return nil
|
||||
set result [r hotkeys get]
|
||||
set result [lindex [r hotkeys get] 0]
|
||||
assert_equal {} $result
|
||||
}
|
||||
|
||||
|
|
@ -210,7 +210,7 @@ start_server {tags {"hotkeys"}} {
|
|||
}
|
||||
|
||||
test {HOTKEYS GET - returns nil when not started} {
|
||||
set result [r hotkeys get]
|
||||
set result [lindex [r hotkeys get] 0]
|
||||
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 stop]
|
||||
|
||||
set result [r hotkeys get]
|
||||
set result [lindex [r hotkeys get] 0]
|
||||
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
||||
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', 3)" 1 x
|
||||
|
||||
set result [r hotkeys get]
|
||||
set result [lindex [r hotkeys get] 0]
|
||||
set result [dict get $result "by-net-bytes"]
|
||||
assert [dict exists $result "x"]
|
||||
assert [dict exists $result "y"]
|
||||
|
|
@ -260,7 +260,7 @@ start_server {tags {"hotkeys"}} {
|
|||
r exec
|
||||
|
||||
assert_equal {OK} [r hotkeys stop]
|
||||
set result [r hotkeys get]
|
||||
set result [lindex [r hotkeys get] 0]
|
||||
assert_equal {OK} [r hotkeys reset]
|
||||
|
||||
# Check NET metrics
|
||||
|
|
@ -290,7 +290,7 @@ start_server {tags {"hotkeys"}} {
|
|||
r exec
|
||||
|
||||
assert_equal {OK} [r hotkeys stop]
|
||||
set result [r hotkeys get]
|
||||
set result [lindex [r hotkeys get] 0]
|
||||
assert_equal {OK} [r hotkeys reset]
|
||||
|
||||
# Check NET metrics - both keys should be tracked through EVAL commands
|
||||
|
|
@ -306,7 +306,7 @@ start_server {tags {"hotkeys"}} {
|
|||
r set key1 value1
|
||||
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"} {
|
||||
set result [hotkeys_array_to_dict $result]
|
||||
}
|
||||
|
|
@ -354,7 +354,7 @@ start_server {tags {"hotkeys"}} {
|
|||
|
||||
assert_equal {OK} [r hotkeys stop]
|
||||
|
||||
set result [r hotkeys get]
|
||||
set result [lindex [r hotkeys get] 0]
|
||||
assert_not_equal $result {}
|
||||
|
||||
# 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} {
|
||||
r hello 3
|
||||
|
||||
|
|
@ -415,7 +415,7 @@ start_server {tags {"hotkeys"}} {
|
|||
r set testkey testvalue
|
||||
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)
|
||||
assert [dict exists $result "tracking-active"]
|
||||
|
|
@ -446,6 +446,25 @@ start_server {tags {"hotkeys"}} {
|
|||
|
||||
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}} {
|
||||
|
|
@ -471,18 +490,72 @@ start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
|||
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 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"} {
|
||||
set result [hotkeys_array_to_dict $result]
|
||||
}
|
||||
set slots [dict get $result "selected-slots"]
|
||||
# Two individual slots should return two 1-element arrays
|
||||
assert_equal 2 [llength $slots]
|
||||
assert_equal 0 [lindex $slots 0]
|
||||
assert_equal 5 [lindex $slots 1]
|
||||
assert_equal {0} [lindex $slots 0]
|
||||
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]
|
||||
}
|
||||
|
|
@ -492,7 +565,7 @@ start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
|||
R 0 set "{06S}key1" value1
|
||||
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"} {
|
||||
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
|
||||
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"} {
|
||||
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]
|
||||
|
||||
set result [R 0 hotkeys get]
|
||||
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]
|
||||
}
|
||||
|
|
@ -585,7 +658,7 @@ start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
|||
|
||||
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"} {
|
||||
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