From 6ec7b16cc144fd98f9898facd44a37116c2cea1c Mon Sep 17 00:00:00 2001 From: Mincho Paskalev Date: Mon, 23 Feb 2026 11:04:14 +0200 Subject: [PATCH] Add HOTKEYS HELP subcommand and fix hotkeys INFO section (#14785) Each command having subcommands needs a HELP subcommand which is currently missing for HOTKEYS. Also the newly added section "Hotkeys" for INFO was messing up modules INFOs in some cases. Fixed both issues in this PR. --- src/commands.def | 18 ++++++++++++++++++ src/commands/hotkeys-help.json | 22 ++++++++++++++++++++++ src/hotkeys.c | 28 +++++++++++++++++++++++++++- src/server.c | 16 +++++++++------- tests/modules/infotest.c | 2 ++ tests/unit/moduleapi/infotest.tcl | 10 ++++++++++ 6 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 src/commands/hotkeys-help.json diff --git a/src/commands.def b/src/commands.def index af7201453..aab618531 100644 --- a/src/commands.def +++ b/src/commands.def @@ -7332,6 +7332,23 @@ const char *HOTKEYS_GET_Tips[] = { #define HOTKEYS_GET_Keyspecs NULL #endif +/********** HOTKEYS HELP ********************/ + +#ifndef SKIP_CMD_HISTORY_TABLE +/* HOTKEYS HELP history */ +#define HOTKEYS_HELP_History NULL +#endif + +#ifndef SKIP_CMD_TIPS_TABLE +/* HOTKEYS HELP tips */ +#define HOTKEYS_HELP_Tips NULL +#endif + +#ifndef SKIP_CMD_KEY_SPECS_TABLE +/* HOTKEYS HELP key specs */ +#define HOTKEYS_HELP_Keyspecs NULL +#endif + /********** HOTKEYS RESET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE @@ -7414,6 +7431,7 @@ const char *HOTKEYS_STOP_Tips[] = { /* 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,3,hotkeysCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0,HOTKEYS_GET_Keyspecs,0,NULL,0)}, +{MAKE_CMD("help","Return helpful text about HOTKEYS command parameters.","O(1)","8.6.1",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,HOTKEYS_HELP_History,0,HOTKEYS_HELP_Tips,0,hotkeysCommand,2,CMD_LOADING|CMD_STALE,0,HOTKEYS_HELP_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)}, diff --git a/src/commands/hotkeys-help.json b/src/commands/hotkeys-help.json new file mode 100644 index 000000000..602811035 --- /dev/null +++ b/src/commands/hotkeys-help.json @@ -0,0 +1,22 @@ +{ + "HELP": { + "summary": "Return helpful text about HOTKEYS command parameters.", + "complexity": "O(1)", + "group": "server", + "since": "8.6.1", + "arity": 2, + "container": "HOTKEYS", + "function": "hotkeysCommand", + "command_flags": [ + "LOADING", + "STALE" + ], + "reply_schema": { + "type": "array", + "description": "Helpful text about subcommands.", + "items": { + "type": "string" + } + } + } +} diff --git a/src/hotkeys.c b/src/hotkeys.c index a8681cf6f..bdcc831e4 100644 --- a/src/hotkeys.c +++ b/src/hotkeys.c @@ -266,7 +266,33 @@ void hotkeysCommand(client *c) { char *sub = c->argv[1]->ptr; - if (!strcasecmp(sub, "START")) { + if (!strcasecmp(sub, "HELP")) { + const char *help[] = { + "START [COUNT k] [DURATION duration] [SAMPLE ratio] [SLOTS count slot...]", + " Starts hotkeys tracking with specified metrics.", + " * METRICS count [CPU] [NET]", + " Specify count of metrics and choose amongst:", + " - CPU: Track hotkeys by CPU time percentage", + " - NET: Track hotkeys by network bytes percentage", + " * COUNT k", + " Specifies the value of K for the top-K hotkeys tracking. Default: 10", + " * DURATION duration", + " Specifies tracking duration in seconds. 0 means tracking will continue until manually stopped. Default: 0", + " * SAMPLE ratio", + " Keys are tracked with probability 1/ratio. Default: 1 (tracks every key)", + " * SLOTS count slot...", + " Specify which slots to track keys from. Only available in cluster mode. Default: empty (track all slots)", + "STOP", + " Stop hotkeys tracking. Results are still available via GET", + "GET", + " Get results from hotkeys tracking.", + "RESET", + " Reset memory used for hotkeys tracking. Tracking must have been stopped.", + " Results will no longer be available after this command.", + NULL + }; + addReplyHelp(c, help); + } else if (!strcasecmp(sub, "START")) { /* HOTKEYS START * * [COUNT k] diff --git a/src/server.c b/src/server.c index 13bc517b0..3143213b4 100644 --- a/src/server.c +++ b/src/server.c @@ -6726,16 +6726,18 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) { } /* Hotkeys */ - if (server.hotkeys && - (all_sections || (dictFind(section_dict,"hotkeys") != NULL))) + if (all_sections || (dictFind(section_dict,"hotkeys") != NULL)) { if (sections++) info = sdscat(info,"\r\n"); - info = sdscatprintf(info, "# Hotkeys\r\n" - "hotkeys-tracking-active:%d\r\n" - "hotkeys-cmd-cpu-time:%lld\r\n", - server.hotkeys->active ? 1 : 0, - server.hotkeys->cpu_time); + info = sdscatprintf(info, "# Hotkeys\r\n"); + if (server.hotkeys) { + info = sdscatprintf(info, + "hotkeys-tracking-active:%d\r\n" + "hotkeys-cmd-cpu-time:%lld\r\n", + server.hotkeys->active ? 1 : 0, + server.hotkeys->cpu_time); + } } /* Modules */ diff --git a/tests/modules/infotest.c b/tests/modules/infotest.c index b93a0c489..a9f2b0ab9 100644 --- a/tests/modules/infotest.c +++ b/tests/modules/infotest.c @@ -3,9 +3,11 @@ #include void InfoFunc(RedisModuleInfoCtx *ctx, int for_crash_report) { + static int info_func_calls = 0; RedisModule_InfoAddSection(ctx, ""); RedisModule_InfoAddFieldLongLong(ctx, "global", -2); RedisModule_InfoAddFieldULongLong(ctx, "uglobal", (unsigned long long)-2); + RedisModule_InfoAddFieldLongLong(ctx, "info_calls", ++info_func_calls); RedisModule_InfoAddSection(ctx, "Spanish"); RedisModule_InfoAddFieldCString(ctx, "uno", "one"); diff --git a/tests/unit/moduleapi/infotest.tcl b/tests/unit/moduleapi/infotest.tcl index 493d24de1..53171d62f 100644 --- a/tests/unit/moduleapi/infotest.tcl +++ b/tests/unit/moduleapi/infotest.tcl @@ -10,6 +10,16 @@ proc field {info property} { start_server {tags {"modules external:skip"}} { r module load $testmodule log-key 0 + test {module info not attempted in INFO ALL} { + # call INFO in a few different ways, check that regardless of the section filtering, + # the module isn't at all being called when unneeded. + r INFO + r INFO all + r INFO memory + set info [r info everything] + set calls [field $info infotest_info_calls] + } {1} + test {module reading info} { # check string, integer and float fields assert_equal [r info.gets replication role] "master"