HELP subcommand, continued (#5531)

* man-like consistent long formatting
* Uppercases commands, subcommands and options
* Adds 'HELP' to HELP for all
* Lexicographical order
* Uses value notation and other .md likeness
* Moves const char *help to top
* Keeps it under 80 chars
* Misc help typos, consistent conjuctioning (i.e return and not returns)
* Uses addReplySubcommandSyntaxError(c) all over

Signed-off-by: Itamar Haber <itamar@redislabs.com>
This commit is contained in:
Itamar Haber 2021-01-04 17:02:57 +02:00 committed by GitHub
parent 10f94b0ab1
commit 9dcdc7e79a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 363 additions and 188 deletions

View File

@ -174,15 +174,15 @@ sds ACLHashPassword(unsigned char *cleartext, size_t len) {
return sdsnewlen(hex,HASH_PASSWORD_LEN); return sdsnewlen(hex,HASH_PASSWORD_LEN);
} }
/* Given a hash and the hash length, returns C_OK if it is a valid password /* Given a hash and the hash length, returns C_OK if it is a valid password
* hash, or C_ERR otherwise. */ * hash, or C_ERR otherwise. */
int ACLCheckPasswordHash(unsigned char *hash, int hashlen) { int ACLCheckPasswordHash(unsigned char *hash, int hashlen) {
if (hashlen != HASH_PASSWORD_LEN) { if (hashlen != HASH_PASSWORD_LEN) {
return C_ERR; return C_ERR;
} }
/* Password hashes can only be characters that represent /* Password hashes can only be characters that represent
* hexadecimal values, which are numbers and lowercase * hexadecimal values, which are numbers and lowercase
* characters 'a' through 'f'. */ * characters 'a' through 'f'. */
for(int i = 0; i < HASH_PASSWORD_LEN; i++) { for(int i = 0; i < HASH_PASSWORD_LEN; i++) {
char c = hash[i]; char c = hash[i];
@ -2184,18 +2184,30 @@ void aclCommand(client *c) {
} }
} else if (c->argc == 2 && !strcasecmp(sub,"help")) { } else if (c->argc == 2 && !strcasecmp(sub,"help")) {
const char *help[] = { const char *help[] = {
"LOAD -- Reload users from the ACL file.", "CAT [<category>]",
"SAVE -- Save the current config to the ACL file.", " List all commands that belong to <category>, or all command categories",
"LIST -- Show user details in config file format.", " when no category is specified.",
"USERS -- List all the registered usernames.", "DELUSER <username> [<username> ...]",
"SETUSER <username> [attribs ...] -- Create or modify a user.", " Delete a list of users.",
"GETUSER <username> -- Get the user details.", "GETUSER <username>",
"DELUSER <username> [...] -- Delete a list of users.", " Get the user's details.",
"CAT -- List available categories.", "GENPASS [<bits>]",
"CAT <category> -- List commands inside category.", " Generate a secure 256-bit user password. The optional `bits` argument can",
"GENPASS [<bits>] -- Generate a secure user password.", " be used to specify a different size.",
"WHOAMI -- Return the current connection username.", "LIST",
"LOG [<count> | RESET] -- Show the ACL log entries.", " Show users details in config file format.",
"LOAD",
" Reload users from the ACL file.",
"LOG [<count> | RESET]",
" Show the ACL log entries.",
"SAVE",
" Save the current config to the ACL file.",
"SETUSER <username> <attribute> [<attribute> ...]",
" Create or modify a user with the specified attributes.",
"USERS",
" List all the registered usernames.",
"WHOAMI",
" Return the current connection username.",
NULL NULL
}; };
addReplyHelp(c,help); addReplyHelp(c,help);

View File

@ -4357,28 +4357,49 @@ void clusterCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"ADDSLOTS <slot> [slot ...] -- Assign slots to current node.", "ADDSLOTS <slot> [<slot> ...]",
"BUMPEPOCH -- Advance the cluster config epoch.", " Assign slots to current node.",
"COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.", "BUMPEPOCH",
"COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.", " Advance the cluster config epoch.",
"DELSLOTS <slot> [slot ...] -- Delete slots information from current node.", "COUNT-FAILURE-REPORTS <node-id>",
"FAILOVER [force|takeover] -- Promote current replica node to being a master.", " Return number of failure reports for <node-id>.",
"FORGET <node-id> -- Remove a node from the cluster.", "COUNTKEYSINSLOT <slot>",
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.", " Return the number of keys in <slot>.",
"FLUSHSLOTS -- Delete current node own slots information.", "DELSLOTS <slot> [<slot> ...]",
"INFO - Return information about the cluster.", " Delete slots information from current node.",
"KEYSLOT <key> -- Return the hash slot for <key>.", "FAILOVER [FORCE|TAKEOVER]",
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.", " Promote current replica node to being a master.",
"MYID -- Return the node id.", "FORGET <node-id>",
"NODES -- Return cluster configuration seen by node. Output format:", " Remove a node from the cluster.",
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>", "GETKEYSINSLOT <slot> <count>",
"REPLICATE <node-id> -- Configure current node as replica to <node-id>.", " Return key names stored by current node in a slot.",
"RESET [hard|soft] -- Reset current node (default: soft).", "FLUSHSLOTS",
"SET-config-epoch <epoch> - Set config epoch of current node.", " Delete current node own slots information.",
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.", "INFO",
"REPLICAS <node-id> -- Return <node-id> replicas.", " Return information about the cluster.",
"SAVECONFIG - Force saving cluster configuration on disk.", "KEYSLOT <key>",
"SLOTS -- Return information about slots range mappings. Each range is made of:", " Return the hash slot for <key>.",
"MEET <ip> <port> [<bus-port>]",
" Connect nodes into a working cluster.",
"MYID",
" Return the node id.",
"NODES",
" Return cluster configuration seen by node. Output format:",
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ...",
"REPLICATE <node-id>",
" Configure current node as replica to <node-id>.",
"RESET [HARD|SOFT]",
" Reset current node (default: soft).",
"SET-CONFIG-EPOCH <epoch>",
" Set config epoch of current node.",
"SETSLOT <slot> (IMPORTING|MIGRATING|STABLE|NODE <node-id>)",
" Set slot state.",
"REPLICAS <node-id>",
" Return <node-id> replicas.",
"SAVECONFIG",
" Force saving cluster configuration on disk.",
"SLOTS",
" Return information about slots range mappings. Each range is made of:",
" start, end, master and replicas IP addresses, ports and ids", " start, end, master and replicas IP addresses, ports and ids",
NULL NULL
}; };

View File

@ -2546,12 +2546,17 @@ void configCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"GET <pattern> -- Return parameters matching the glob-like <pattern> and their values.", "GET <pattern>",
"SET <parameter> <value> -- Set parameter to value.", " Return parameters matching the glob-like <pattern> and their values.",
"RESETSTAT -- Reset statistics reported by INFO.", "SET <directive> <value>",
"REWRITE -- Rewrite the configuration file.", " Set the configuration <directive> to <value>.",
"RESETSTAT",
" Reset statistics reported by the INFO command.",
"REWRITE",
" Rewrite the configuration file.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
} else if (!strcasecmp(c->argv[1]->ptr,"set") && c->argc == 4) { } else if (!strcasecmp(c->argv[1]->ptr,"set") && c->argc == 4) {
configSetCommand(c); configSetCommand(c);

View File

@ -381,39 +381,88 @@ void mallctl_string(client *c, robj **argv, int argc) {
void debugCommand(client *c) { void debugCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"ASSERT -- Crash by assertion failed.", "AOF-FLUSH-SLEEP <microsec>",
"CHANGE-REPL-ID -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.", " Server will sleep before flushing the AOF, this is used for testing.",
"CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.", "ASSERT",
"DIGEST -- Output a hex signature representing the current DB content.", " Crash by assertion failed.",
"DIGEST-VALUE <key-1> ... <key-N>-- Output a hex signature of the values of all the specified keys.", "CHANGE-REPL-ID"
"DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false]", " Change the replication IDs of the instance.",
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.", " Dangerous: should be used only for testing the replication subsystem.",
"LOG <message> -- write message to the server log.", "CONFIG-REWRITE-FORCE-ALL",
"LEAK <string> -- Create a memory leak of the input string.", " Like CONFIG REWRITE but writes all configuration options, including",
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.", " keywords not listed in original configuration file or default values.",
"HTSTATS-KEY <key> -- Like htstats but for the hash table stored as key's value.", "CRASH-AND-RECOVER <milliseconds>",
"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.", " Hard crash and restart after a <milliseconds> delay.",
"LUA-ALWAYS-REPLICATE-COMMANDS <0|1> -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.", "DIGEST",
"OBJECT <key> -- Show low level info about key and associated value.", " Output a hex signature representing the current DB content.",
"OOM -- Crash the server simulating an out-of-memory error.", "DIGEST-VALUE <key> [<key> ...]",
"PANIC -- Crash the server simulating a panic.", " Output a hex signature of the values of all the specified keys.",
"POPULATE <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.", "ERROR <string>",
"RELOAD [MERGE] [NOFLUSH] [NOSAVE] -- Save the RDB on disk and reload it back in memory. By default it will save the RDB file and load it back. With the NOFLUSH option the current database is not removed before loading the new one, but conflicts in keys will kill the server with an exception. When MERGE is used, conflicting keys will be loaded (the key in the loaded RDB file will win). When NOSAVE is used, the server will not save the current dataset in the RDB file before loading. Use DEBUG RELOAD NOSAVE when you want just to load the RDB file you placed in the Redis working directory in order to replace the current dataset in memory. Use DEBUG RELOAD NOSAVE NOFLUSH MERGE when you want to add what is in the current RDB file placed in the Redis current directory, with the current memory content. Use DEBUG RELOAD when you want to verify Redis is able to persist the current dataset in the RDB file, flush the memory content, and load it back.", " Return a Redis protocol error with <string> as message. Useful for clients",
"RESTART -- Graceful restart: save config, db, restart.", " unit tests to simulate Redis errors.",
"SDSLEN <key> -- Show low level SDS string info representing key and value.", "LOG <message>",
"SEGFAULT -- Crash the server with sigsegv.", " Write <message> to the server log.",
"SET-ACTIVE-EXPIRE <0|1> -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.", "HTSTATS <dbid>",
"SET-SKIP-CHECKSUM-VALIDATION <0|1> -- Enables or disables checksum checks for rdb or RESTORE payload.", " Return hash table statistics of the specified Redis database.",
"AOF-FLUSH-SLEEP <microsec> -- Server will sleep before flushing the AOF, this is used for testing", "HTSTATS-KEY <key>",
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.", " Like HTSTATS but for the hash table stored at <key>'s value.",
"STRUCTSIZE -- Return the size of different Redis core C structures.", "LOADAOF",
"ZIPLIST <key> -- Show low level info about the ziplist encoding.", " Flush the AOF buffers on disk and reload the AOF in memory.",
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.", "LUA-ALWAYS-REPLICATE-COMMANDS <0|1>",
"CONFIG-REWRITE-FORCE-ALL -- Like CONFIG REWRITE but writes all configuration options, including keywords not listed in original configuration file or default values.", " Setting it to 1 makes Lua replication defaulting to replicating single",
" commands, without the script having to enable effects replication.",
#ifdef USE_JEMALLOC #ifdef USE_JEMALLOC
"MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.", "MALLCTL <key> [<val>]",
"MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.", " Get or set a malloc tuning integer.",
"MALLCTL-STR <key> [<val>]",
" Get or set a malloc tuning string.",
#endif #endif
"OBJECT <key>",
" Show low level info about `key` and associated value.",
"OOM",
" Crash the server simulating an out-of-memory error.",
"PANIC",
" Crash the server simulating a panic.",
"POPULATE <count> [<prefix>] [<size>]",
" Create <count> string keys named key:<num>. If <prefix> is specified then",
" it is used instead of the 'key' prefix.",
"DEBUG PROTOCOL <type>",
" Reply with a test value of the specified type. <type> can be: string,",
" integer, double, bignum, null, array, set, map, attrib, push, verbatim,",
" true, false.",
"RELOAD [option ...]",
" Save the RDB on disk and reload it back to memory. Valid <option> values:",
" * MERGE: conflicting keys will be loaded from RDB.",
" * NOFLUSH: the existing database will not be removed before load, but",
" conflicting keys will generate an exception and kill the server."
" * NOSAVE: the database will be loaded from an existing RDB file.",
" Examples:",
" * DEBUG RELOAD: verify that the server is able to persist, flsuh and reload",
" the database.",
" * DEBUG RELOAD NOSAVE: replace the current database with the contents of an",
" existing RDB file.",
" * DEBUG RELOAD NOSAVE NOFLUSH MERGE: add the contents of an existing RDB",
" file to the database.",
"RESTART",
" Graceful restart: save config, db, restart.",
"SDSLEN <key>",
" Show low level SDS string info representing `key` and value.",
"SEGFAULT",
" Crash the server with sigsegv.",
"SET-ACTIVE-EXPIRE <0|1>",
" Setting it to 0 disables expiring keys in background when they are not",
" accessed (otherwise the Redis behavior). Setting it to 1 reenables back the",
" default.",
"SET-SKIP-CHECKSUM-VALIDATION <0|1>",
" Enables or disables checksum checks for RDB files and RESTORE's payload.",
"SLEEP <seconds>",
" Stop the server for <seconds>. Decimals allowed.",
"STRINGMATCH-TEST",
" Run a fuzz tester against the stringmatchlen() function.",
"STRUCTSIZE",
" Return the size of different Redis core C structures.",
"ZIPLIST <key>",
" Show low level info about the ziplist encoding of <key>.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);

View File

@ -584,16 +584,6 @@ sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
* LATENCY RESET: reset data of a specified event or all the data if no event provided. * LATENCY RESET: reset data of a specified event or all the data if no event provided.
*/ */
void latencyCommand(client *c) { void latencyCommand(client *c) {
const char *help[] = {
"DOCTOR -- Returns a human readable latency analysis report.",
"GRAPH <event> -- Returns an ASCII latency graph for the event class.",
"HISTORY <event> -- Returns time-latency samples for the event class.",
"LATEST -- Returns the latest latency samples for all events.",
"RESET [event ...] -- Resets latency data of one or more event classes.",
" (default: reset all data for all event classes)",
"HELP -- Prints this help.",
NULL
};
struct latencyTimeSeries *ts; struct latencyTimeSeries *ts;
if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) { if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) {
@ -639,6 +629,20 @@ NULL
addReplyLongLong(c,resets); addReplyLongLong(c,resets);
} }
} else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
const char *help[] = {
"DOCTOR",
" Return a human readable latency analysis report.",
"GRAPH <event>",
" Return an ASCII latency graph for the <event> class.",
"HISTORY <event>",
" Return time-latency samples for the <event> class.",
"LATEST",
" Return the latest latency samples for all events.",
"RESET [<event> ...]",
" Reset latency data of one or more <event> classes.",
" (default: reset all data for all event classes)",
NULL
};
addReplyHelp(c, help); addReplyHelp(c, help);
} else { } else {
addReplySubcommandSyntaxError(c); addReplySubcommandSyntaxError(c);

View File

@ -7936,14 +7936,21 @@ sds genModulesInfoString(sds info) {
/* Redis MODULE command. /* Redis MODULE command.
* *
* MODULE LOAD <path> [args...] */ * MODULE LIST
* MODULE LOAD <path> [args...]
* MODULE UNLOAD <name>
*/
void moduleCommand(client *c) { void moduleCommand(client *c) {
char *subcmd = c->argv[1]->ptr; char *subcmd = c->argv[1]->ptr;
if (c->argc == 2 && !strcasecmp(subcmd,"help")) { if (c->argc == 2 && !strcasecmp(subcmd,"help")) {
const char *help[] = { const char *help[] = {
"LIST -- Return a list of loaded modules.", "LIST",
"LOAD <path> [arg ...] -- Load a module library from <path>.", " Return a list of loaded modules.",
"UNLOAD <name> -- Unload a module.", "LOAD <path> [<arg> ...]",
" Load a module library from <path>, passing to it any optional arguments.",
"UNLOAD <name>",
" Unload a module.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);

View File

@ -855,12 +855,16 @@ void addReplyHelp(client *c, const char **help) {
sdstoupper(cmd); sdstoupper(cmd);
addReplyStatusFormat(c, addReplyStatusFormat(c,
"%s <subcommand> arg arg ... arg. Subcommands are:",cmd); "%s <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",cmd);
sdsfree(cmd); sdsfree(cmd);
while (help[blen]) addReplyStatus(c,help[blen++]); while (help[blen]) addReplyStatus(c,help[blen++]);
blen++; /* Account for the header line(s). */ addReplyStatus(c,"HELP");
addReplyStatus(c," Prints this help.");
blen += 1; /* Account for the header. */
blen += 2; /* Account for the footer. */
setDeferredArrayLen(c,blenp,blen); setDeferredArrayLen(c,blenp,blen);
} }
@ -2387,27 +2391,45 @@ void clientCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"ID -- Return the ID of the current connection.", "CACHING (YES|NO)",
"INFO -- Return information about the current client connection.", " Enable/disable tracking of the keys for next command in OPTIN/OPTOUT modes.",
"GETNAME -- Return the name of the current connection.", "GETREDIR",
"KILL <ip:port> -- Kill connection made from <ip:port>.", " Return the client ID we are redirecting to when tracking is enabled.",
"KILL <option> <value> [option value ...] -- Kill connections. Options are:", "GETNAME",
" ADDR <ip:port> -- Kill connection made from <ip:port>", " Return the name of the current connection.",
" LADDR <ip:port> -- Kill connection made to <ip:port>", "ID",
" TYPE (normal|master|replica|pubsub) -- Kill connections by type.", " Return the ID of the current connection.",
" USER <username> -- Kill connections authenticated with such user.", "INFO",
" SKIPME (yes|no) -- Skip killing current connection (default: yes).", " Return information about the current client connection.",
"LIST [options ...] -- Return information about client connections. Options:", "KILL <ip:port>",
" TYPE (normal|master|replica|pubsub) -- Return clients of specified type.", " Kill connection made from <ip:port>.",
" ID id [id ...] -- Return clients of specified IDs only.", "KILL <option> <value> [<option> <value> [...]]",
"PAUSE <timeout> -- Suspend all Redis clients for <timeout> milliseconds.", " Kill connections. Options are:",
"REPLY (on|off|skip) -- Control the replies sent to the current connection.", " * ADDR <ip:port>",
"SETNAME <name> -- Assign the name <name> to the current connection.", " Kill connection made from <ip:port>",
"UNBLOCK <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.", " * TYPE (normal|master|replica|pubsub)",
"TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first] [PREFIX second] [OPTIN] [OPTOUT] [NOLOOP]... -- Enable client keys tracking for client side caching.", " Kill connections by type.",
"CACHING (yes|no) -- Enable/Disable tracking of the keys for next command in OPTIN/OPTOUT mode.", " * USER <username>",
"GETREDIR -- Return the client ID we are redirecting to when tracking is enabled.", " Kill connections authenticated by <username>.",
"TRACKINGINFO -- Return information about current client's tracking status.", " * SKIPME (YES|NO)",
" Skip killing current connection (default: yes).",
"LIST [options ...]",
" Return information about client connections. Options:",
" * TYPE (NORMAL|MASTER|REPLICA|PUBSUB)",
" Return clients of specified type.",
"PAUSE <timeout>",
" Suspend all clients for <timout> milliseconds.",
"REPLY (ON|OFF|SKIP)",
" Control the replies sent to the current connection.",
"SETNAME <name>",
" Assign the name <name> to the current connection.",
"UNBLOCK <clientid> [TIMEOUT|ERROR]",
" Unblock the specified blocked client.",
"TRACKING (ON|OFF) [REDIRECT <id>] [BCAST] [PREFIX <prefix> [...]]",
" [OPTIN] [OPTOUT]",
" Control server assisted client side caching.",
"TRACKINGINFO",
" Report tracking status for the current connection.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
@ -2836,7 +2858,7 @@ NULL
addReplyArrayLen(c,0); addReplyArrayLen(c,0);
} }
} else { } else {
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CLIENT HELP", (char*)c->argv[1]->ptr); addReplySubcommandSyntaxError(c);
} }
} }

View File

@ -1035,7 +1035,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) + mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) +
dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*); dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*);
if (listLength(server.repl_scriptcache_fifo) > 0) { if (listLength(server.repl_scriptcache_fifo) > 0) {
mem += listLength(server.repl_scriptcache_fifo) * (sizeof(listNode) + mem += listLength(server.repl_scriptcache_fifo) * (sizeof(listNode) +
sdsZmallocSize(listNodeValue(listFirst(server.repl_scriptcache_fifo)))); sdsZmallocSize(listNodeValue(listFirst(server.repl_scriptcache_fifo))));
} }
mh->lua_caches = mem; mh->lua_caches = mem;
@ -1256,10 +1256,18 @@ void objectCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"ENCODING <key> -- Return the kind of internal representation used in order to store the value associated with a key.", "ENCODING <key>",
"FREQ <key> -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.", " Return the kind of internal representation used in order to store the value",
"IDLETIME <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.", " associated with a <key>.",
"REFCOUNT <key> -- Return the number of references of the value associated with the specified key.", "FREQ <key>",
" Return the access frequency index of the <key>. The returned integer is",
" proportional to the logarithm of the recent access frequency of the key.",
"IDLETIME <key>",
" Return the idle time of the <key>, that is the approximated number of",
" seconds elapsed since the last access to the key.",
"REFCOUNT <key>",
" Return the number of references of the value associated with the specified",
" <key>.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
@ -1303,11 +1311,17 @@ NULL
void memoryCommand(client *c) { void memoryCommand(client *c) {
if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) { if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
const char *help[] = { const char *help[] = {
"DOCTOR - Return memory problems reports.", "DOCTOR",
"MALLOC-STATS -- Return internal statistics report from the memory allocator.", " Return memory problems reports.",
"PURGE -- Attempt to purge dirty pages for reclamation by the allocator.", "MALLOC-STATS"
"STATS -- Return information about the memory usage of the server.", " Return internal statistics report from the memory allocator.",
"USAGE <key> [SAMPLES <count>] -- Return memory in bytes used by <key> and its value. Nested values are sampled up to <count> times (default: 5).", "PURGE",
" Attempt to purge dirty pages for reclamation by the allocator.",
"STATS",
" Return information about the memory usage of the server.",
"USAGE <key> [SAMPLES <count>]",
" Return memory in bytes used by <key> and its value. Nested values are",
" sampled up to <count> times (default: 5).",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
@ -1452,6 +1466,6 @@ NULL
else else
addReplyError(c, "Error purging dirty pages"); addReplyError(c, "Error purging dirty pages");
} else { } else {
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)c->argv[1]->ptr); addReplySubcommandSyntaxError(c);
} }
} }

View File

@ -455,9 +455,13 @@ void publishCommand(client *c) {
void pubsubCommand(client *c) { void pubsubCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"CHANNELS [<pattern>] -- Return the currently active channels matching a pattern (default: all).", "CHANNELS [<pattern>]",
"NUMPAT -- Return number of subscriptions to patterns.", " Return the currently active channels matching a <pattern> (default: '*').",
"NUMSUB [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).", "NUMPAT",
" Return number of subscriptions to patterns.",
"NUMSUB [<channel> ...]",
" Return the number of subscribers for the specified channels, excluding",
" pattern subscriptions(default: no channels).",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);

View File

@ -691,11 +691,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,&error_code) != if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,&error_code) !=
server.cluster->myself) server.cluster->myself)
{ {
if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) { if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) {
luaPushError(lua, luaPushError(lua,
"Lua script attempted to execute a write command while the " "Lua script attempted to execute a write command while the "
"cluster is down and readonly"); "cluster is down and readonly");
} else if (error_code == CLUSTER_REDIR_DOWN_STATE) { } else if (error_code == CLUSTER_REDIR_DOWN_STATE) {
luaPushError(lua, luaPushError(lua,
"Lua script attempted to execute a command while the " "Lua script attempted to execute a command while the "
"cluster is down"); "cluster is down");
@ -1707,11 +1707,16 @@ void evalShaCommand(client *c) {
void scriptCommand(client *c) { void scriptCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"DEBUG (yes|sync|no) -- Set the debug mode for subsequent scripts executed.", "DEBUG (YES|SYNC|NO)",
"EXISTS <sha1> [<sha1> ...] -- Return information about the existence of the scripts in the script cache.", " Set the debug mode for subsequent scripts executed.",
"FLUSH -- Flush the Lua scripts cache. Very dangerous on replicas.", "EXISTS <sha1> [<sha1> ...]",
"KILL -- Kill the currently executing Lua script.", " Return information about the existence of the scripts in the script cache.",
"LOAD <script> -- Load a script into the scripts cache, without executing it.", "FLUSH",
" Flush the Lua scripts cache. Very dangerous on replicas.",
"KILL",
" Kill the currently executing Lua script.",
"LOAD <script>",
" Load a script into the scripts cache without executing it.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
@ -1762,7 +1767,7 @@ NULL
addReply(c,shared.ok); addReply(c,shared.ok);
c->flags |= CLIENT_LUA_DEBUG_SYNC; c->flags |= CLIENT_LUA_DEBUG_SYNC;
} else { } else {
addReplyError(c,"Use SCRIPT DEBUG yes/sync/no"); addReplyError(c,"Use SCRIPT DEBUG YES/SYNC/NO");
return; return;
} }
} else { } else {

View File

@ -3090,25 +3090,45 @@ int sentinelIsQuorumReachable(sentinelRedisInstance *master, int *usableptr) {
void sentinelCommand(client *c) { void sentinelCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"MASTERS -- Show a list of monitored masters and their state.", "CKQUORUM <master-name>",
"MASTER <master-name> -- Show the state and info of the specified master.", " Check if the current Sentinel configuration is able to reach the quorum",
"REPLICAS <master-name> -- Show a list of replicas for this master and their state.", " needed to failover a master and the majority needed to authorize the",
"SENTINELS <master-name> -- Show a list of Sentinel instances for this master and their state.", " failover.",
"MYID -- Show Current Sentinel Id", "GET-MASTER-ADDR-BY-NAME <master-name>",
"IS-MASTER-DOWN-BY-ADDR <ip> <port> <current-epoch> <runid> -- Check if the master specified by ip:port is down from current Sentinel's point of view.", " Return the ip and port number of the master with that name.",
"GET-MASTER-ADDR-BY-NAME <master-name> -- Return the ip and port number of the master with that name.", "FAILOVER <master-name>",
"RESET <pattern> -- Reset masters for specific master name matching this pattern.", " Manually failover a master node without asking for agreement from other",
"FAILOVER <master-name> -- Manually failover a master node without asking for agreement from other Sentinels", " Sentinels",
"PENDING-SCRIPTS -- Get pending scripts information.", "FLUSHCONFIG",
"MONITOR <name> <ip> <port> <quorum> -- Start monitoring a new master with the specified name, ip, port and quorum.", " Force Sentinel to rewrite its configuration on disk, including the current",
"FLUSHCONFIG -- Force Sentinel to rewrite its configuration on disk, including the current Sentinel state.", " Sentinel state.",
"REMOVE <master-name> -- Remove master from Sentinel's monitor list.", "INFO-CACHE <master-name>",
"CKQUORUM <master-name> -- Check if the current Sentinel configuration is able to reach the quorum needed to failover a master " " Return last cached INFO output from masters and all its replicas.",
"and the majority needed to authorize the failover.", "IS-MASTER-DOWN-BY-ADDR <ip> <port> <current-epoch> <runid>",
"SET <master-name> <option> <value> -- Set configuration paramters for certain masters.", " Check if the master specified by ip:port is down from current Sentinel's",
"INFO-CACHE <master-name> -- Return last cached INFO output from masters and all its replicas.", " point of view.",
"SIMULATE-FAILURE (crash-after-election|crash-after-promotion|help) -- Simulate a Sentinel crash.", "MASTER <master-name>",
"HELP -- Prints this help.", " Show the state and info of the specified master.",
"MASTERS",
" Show a list of monitored masters and their state.",
"MONITOR <name> <ip> <port> <quorum>",
" Start monitoring a new master with the specified name, ip, port and quorum.",
"MYID",
" Return the ID of the Sentinel instance.",
"PENDING-SCRIPTS",
" Get pending scripts information.",
"REMOVE <master-name>",
" Remove master from Sentinel's monitor list.",
"REPLICAS <master-name>",
" Show a list of replicas for this master and their state.",
"RESET <pattern>",
" Reset masters for specific master name matching this pattern.",
"SENTINELS <master-name>",
" Show a list of Sentinel instances for this master and their state.",
"SET <master-name> <option> <value>",
" Set configuration paramters for certain masters.",
"SIMULATE-FAILURE (CRASH-AFTER-ELECTION|CRASH-AFTER-PROMOTION|HELP)",
" Simulate a Sentinel crash.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);

View File

@ -2233,6 +2233,7 @@ void whileBlockedCron() {
activeDefragCycle(); activeDefragCycle();
server.blocked_last_cron += hz_ms; server.blocked_last_cron += hz_ms;
/* Increment cronloop so that run_with_period works. */ /* Increment cronloop so that run_with_period works. */
server.cronloops++; server.cronloops++;
} }
@ -4248,10 +4249,14 @@ void commandCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"(no subcommand) -- Return details about all Redis commands.", "(no subcommand)",
"COUNT -- Return the total number of commands in this Redis server.", " Return details about all Redis commands.",
"GETKEYS <full-command> -- Return the keys from a full Redis command.", "COUNT",
"INFO [command-name ...] -- Return details about multiple Redis commands.", " Return the total number of commands in this Redis server.",
"GETKEYS <full-command>",
" Return the keys from a full Redis command.",
"INFO [<command-name> ...]",
" Return details about multiple Redis commands.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
@ -5365,7 +5370,7 @@ void loadDataFromDisk(void) {
void redisOutOfMemoryHandler(size_t allocation_size) { void redisOutOfMemoryHandler(size_t allocation_size) {
serverLog(LL_WARNING,"Out Of Memory allocating %zu bytes!", serverLog(LL_WARNING,"Out Of Memory allocating %zu bytes!",
allocation_size); allocation_size);
serverPanic("Redis aborting for OUT OF MEMORY. Allocating %zu bytes!", serverPanic("Redis aborting for OUT OF MEMORY. Allocating %zu bytes!",
allocation_size); allocation_size);
} }

View File

@ -142,11 +142,15 @@ void slowlogReset(void) {
void slowlogCommand(client *c) { void slowlogCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"GET [count] -- Return top entries from the slowlog (default: 10)." "GET [<count>]",
" Entries are made of:", " Return top <count> entries from the slowlog (default: 10). Entries are",
" id, timestamp, time in microseconds, arguments array, client IP and port, client name", " made of:",
"LEN -- Return the length of the slowlog.", " id, timestamp, time in microseconds, arguments array, client IP and port,",
"RESET -- Reset the slowlog.", " client name",
"LEN",
" Return the length of the slowlog.",
"RESET",
" Reset the slowlog.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);

View File

@ -1981,19 +1981,9 @@ uint64_t streamDelConsumer(streamCG *cg, sds name) {
/* XGROUP CREATE <key> <groupname> <id or $> [MKSTREAM] /* XGROUP CREATE <key> <groupname> <id or $> [MKSTREAM]
* XGROUP SETID <key> <groupname> <id or $> * XGROUP SETID <key> <groupname> <id or $>
* XGROUP DESTROY <key> <groupname> * XGROUP DESTROY <key> <groupname>
* CREATECONSUMER <key> <groupname> <consumer> * XGROUP CREATECONSUMER <key> <groupname> <consumer>
* XGROUP DELCONSUMER <key> <groupname> <consumername> */ * XGROUP DELCONSUMER <key> <groupname> <consumername> */
void xgroupCommand(client *c) { void xgroupCommand(client *c) {
const char *help[] = {
"CREATE <key> <groupname> <id or $> [opt] -- Create a new consumer group.",
" option MKSTREAM: create the empty stream if it does not exist.",
"SETID <key> <groupname> <id or $> -- Set the current group ID.",
"DESTROY <key> <groupname> -- Remove the specified group.",
"CREATECONSUMER <key> <groupname> <consumer> -- Create new consumer in the specified group.",
"DELCONSUMER <key> <groupname> <consumer> -- Remove the specified consumer.",
"HELP -- Prints this help.",
NULL
};
stream *s = NULL; stream *s = NULL;
sds grpname = NULL; sds grpname = NULL;
streamCG *cg = NULL; streamCG *cg = NULL;
@ -2047,7 +2037,24 @@ NULL
} }
/* Dispatch the different subcommands. */ /* Dispatch the different subcommands. */
if (!strcasecmp(opt,"CREATE") && (c->argc == 5 || c->argc == 6)) { if (c->argc == 2 && !strcasecmp(opt,"HELP")) {
const char *help[] = {
"CREATE <key> <groupname> <id|$> [option]",
" Create a new consumer group. Options are:",
" * MKSTREAM",
" Create the empty stream if it does not exist.",
"CREATECONSUMER <key> <groupname> <consumer>",
" Create a new consumer in the specified group.",
"DELCONSUMER <key> <groupname> <consumer>",
" Remove the specified consumer.",
"DESTROY <key> <groupname>"
" Remove the specified group.",
"SETID <key> <groupname> <id|$>",
" Set the current group ID.",
NULL
};
addReplyHelp(c, help);
} else if (!strcasecmp(opt,"CREATE") && (c->argc == 5 || c->argc == 6)) {
streamID id; streamID id;
if (!strcmp(c->argv[4]->ptr,"$")) { if (!strcmp(c->argv[4]->ptr,"$")) {
if (s) { if (s) {
@ -2119,8 +2126,6 @@ NULL
server.dirty++; server.dirty++;
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-delconsumer", notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-delconsumer",
c->argv[2],c->db->id); c->argv[2],c->db->id);
} else if (c->argc == 2 && !strcasecmp(opt,"HELP")) {
addReplyHelp(c, help);
} else { } else {
addReplySubcommandSyntaxError(c); addReplySubcommandSyntaxError(c);
} }
@ -2971,27 +2976,25 @@ void xinfoReplyWithStreamInfo(client *c, stream *s) {
* XINFO STREAM <key> [FULL [COUNT <count>]] * XINFO STREAM <key> [FULL [COUNT <count>]]
* XINFO HELP. */ * XINFO HELP. */
void xinfoCommand(client *c) { void xinfoCommand(client *c) {
const char *help[] = {
"CONSUMERS <key> <groupname> -- Show consumer groups of group <groupname>.",
"GROUPS <key> -- Show the stream consumer groups.",
"STREAM <key> [FULL [COUNT <count>]] -- Show information about the stream.",
" FULL will return the full state of the stream,",
" including all entries, groups, consumers and PELs.",
" It's possible to show only the first stream/PEL entries",
" by using the COUNT modifier (Default is 10)",
"HELP -- Print this help.",
NULL
};
stream *s = NULL; stream *s = NULL;
char *opt; char *opt;
robj *key; robj *key;
/* HELP is special. Handle it ASAP. */ /* HELP is special. Handle it ASAP. */
if (!strcasecmp(c->argv[1]->ptr,"HELP")) { if (!strcasecmp(c->argv[1]->ptr,"HELP")) {
const char *help[] = {
"CONSUMERS <key> <groupname>",
" Show consumers of <groupname>.",
"GROUPS <key>",
" Show the stream consumer groups.",
"STREAM <key> [FULL [COUNT <count>]",
" Show information about the stream.",
NULL
};
addReplyHelp(c, help); addReplyHelp(c, help);
return; return;
} else if (c->argc < 3) { } else if (c->argc < 3) {
addReplyError(c,"syntax error, try 'XINFO HELP'"); addReplySubcommandSyntaxError(c);
return; return;
} }