From 91dbd1176a07444099a024c8abf305d80cf266f6 Mon Sep 17 00:00:00 2001 From: "zhaozhao.zz" Date: Tue, 12 Nov 2024 11:58:46 +0800 Subject: [PATCH] add commandlog and its subcommands Signed-off-by: zhaozhao.zz --- src/commandlog.c | 107 ++++++++++++++++++++----- src/commands.def | 121 +++++++++++++++++++++++++++++ src/commands/commandlog-get.json | 71 +++++++++++++++++ src/commands/commandlog-help.json | 22 ++++++ src/commands/commandlog-len.json | 32 ++++++++ src/commands/commandlog-reset.json | 29 +++++++ src/commands/commandlog.json | 9 +++ src/server.h | 1 + 8 files changed, 371 insertions(+), 21 deletions(-) create mode 100644 src/commands/commandlog-get.json create mode 100644 src/commands/commandlog-help.json create mode 100644 src/commands/commandlog-len.json create mode 100644 src/commands/commandlog-reset.json create mode 100644 src/commands/commandlog.json diff --git a/src/commandlog.c b/src/commandlog.c index 1e8e622b3f..5ad6eeac49 100644 --- a/src/commandlog.c +++ b/src/commandlog.c @@ -134,6 +134,33 @@ void commandlogReset(int type) { while (listLength(server.commandlog[type].entries) > 0) listDelNode(server.commandlog[type].entries, listLast(server.commandlog[type].entries)); } +/* Reply command logs to client. */ +void commandlogGetReply(client *c, int type, long count) { + listIter li; + listNode *ln; + commandlogEntry *ce; + + if (count > (long)listLength(server.commandlog[type].entries)) { + count = listLength(server.commandlog[type].entries); + } + addReplyArrayLen(c, count); + listRewind(server.commandlog[type].entries, &li); + while (count--) { + int j; + + ln = listNext(&li); + ce = ln->value; + addReplyArrayLen(c, 6); + addReplyLongLong(c, ce->id); + addReplyLongLong(c, ce->time); + addReplyLongLong(c, ce->value); + addReplyArrayLen(c, ce->argc); + for (j = 0; j < ce->argc; j++) addReplyBulk(c, ce->argv[j]); + addReplyBulkCBuffer(c, ce->peerid, sdslen(ce->peerid)); + addReplyBulkCBuffer(c, ce->cname, sdslen(ce->cname)); + } +} + /* The SLOWLOG command. Implements all the subcommands needed to handle the * slow log. */ void slowlogCommand(client *c) { @@ -158,9 +185,6 @@ void slowlogCommand(client *c) { addReplyLongLong(c, listLength(server.commandlog[COMMANDLOG_TYPE_SLOW].entries)); } else if ((c->argc == 2 || c->argc == 3) && !strcasecmp(c->argv[1]->ptr, "get")) { long count = 10; - listIter li; - listNode *ln; - commandlogEntry *ce; if (c->argc == 3) { /* Consume count arg. */ @@ -175,25 +199,66 @@ void slowlogCommand(client *c) { } } - if (count > (long)listLength(server.commandlog[COMMANDLOG_TYPE_SLOW].entries)) { - count = listLength(server.commandlog[COMMANDLOG_TYPE_SLOW].entries); - } - addReplyArrayLen(c, count); - listRewind(server.commandlog[COMMANDLOG_TYPE_SLOW].entries, &li); - while (count--) { - int j; - - ln = listNext(&li); - ce = ln->value; - addReplyArrayLen(c, 6); - addReplyLongLong(c, ce->id); - addReplyLongLong(c, ce->time); - addReplyLongLong(c, ce->value); - addReplyArrayLen(c, ce->argc); - for (j = 0; j < ce->argc; j++) addReplyBulk(c, ce->argv[j]); - addReplyBulkCBuffer(c, ce->peerid, sdslen(ce->peerid)); - addReplyBulkCBuffer(c, ce->cname, sdslen(ce->cname)); + commandlogGetReply(c, COMMANDLOG_TYPE_SLOW, count); + } else { + addReplySubcommandSyntaxError(c); + } +} + +int commandlogGetTypeOrReply(client *c, robj *o) { + if (!strcasecmp(o->ptr, "slow")) return COMMANDLOG_TYPE_SLOW; + if (!strcasecmp(o->ptr, "heavytraffic-input")) return COMMANDLOG_TYPE_HEAVYTRAFFIC_INPUT; + if (!strcasecmp(o->ptr, "heavytraffic-output")) return COMMANDLOG_TYPE_HEAVYTRAFFIC_OUTPUT; + addReplyError(c, "type should be one of the following: slow, heavytraffic-input, heavytraffic-output"); + return -1; +} + +/* The COMMANDLOG command. Implements all the subcommands needed to handle the + * command log. */ +void commandlogCommand(client *c) { + int type; + if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr, "help")) { + const char *help[] = { + "GET ", + " Return top entries of the specified from the commandlog (-1 mean all).", + " Entries are made of:", + " id, timestamp,", + " time in microseconds for type of slow,", + " or size in bytes for type of heavytraffic-input,", + " or size in bytes for type of heavytraffic-output", + " arguments array, client IP and port,", + " client name", + "LEN ", + " Return the length of the specified type of commandlog.", + "RESET ", + " Reset the specified type of commandlog.", + NULL, + }; + addReplyHelp(c, help); + } else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr, "reset")) { + if ((type = commandlogGetTypeOrReply(c, c->argv[2])) == -1) return; + commandlogReset(type); + addReply(c, shared.ok); + } else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr, "len")) { + if ((type = commandlogGetTypeOrReply(c, c->argv[2])) == -1) return; + addReplyLongLong(c, listLength(server.commandlog[type].entries)); + } else if (c->argc == 4 && !strcasecmp(c->argv[1]->ptr, "get")) { + long count; + + /* Consume count arg. */ + if (getRangeLongFromObjectOrReply(c, c->argv[2], -1, LONG_MAX, &count, + "count should be greater than or equal to -1") != C_OK) + return; + + if (count == -1) { + /* We treat -1 as a special value, which means to get all command logs. + * Simply set count to the length of server.commandlog. */ + count = listLength(server.commandlog[type].entries); } + + if ((type = commandlogGetTypeOrReply(c, c->argv[3])) == -1) return; + + commandlogGetReply(c, type, count); } else { addReplySubcommandSyntaxError(c); } diff --git a/src/commands.def b/src/commands.def index cd9f8e2984..f94301b826 100644 --- a/src/commands.def +++ b/src/commands.def @@ -6618,6 +6618,126 @@ const char *COMMAND_Tips[] = { #define COMMAND_Keyspecs NULL #endif +/********** COMMANDLOG GET ********************/ + +#ifndef SKIP_CMD_HISTORY_TABLE +/* COMMANDLOG GET history */ +#define COMMANDLOG_GET_History NULL +#endif + +#ifndef SKIP_CMD_TIPS_TABLE +/* COMMANDLOG GET tips */ +const char *COMMANDLOG_GET_Tips[] = { +"request_policy:all_nodes", +"nondeterministic_output", +}; +#endif + +#ifndef SKIP_CMD_KEY_SPECS_TABLE +/* COMMANDLOG GET key specs */ +#define COMMANDLOG_GET_Keyspecs NULL +#endif + +/* COMMANDLOG GET argument table */ +struct COMMAND_ARG COMMANDLOG_GET_Args[] = { +{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, +{MAKE_ARG("type",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, +}; + +/********** COMMANDLOG HELP ********************/ + +#ifndef SKIP_CMD_HISTORY_TABLE +/* COMMANDLOG HELP history */ +#define COMMANDLOG_HELP_History NULL +#endif + +#ifndef SKIP_CMD_TIPS_TABLE +/* COMMANDLOG HELP tips */ +#define COMMANDLOG_HELP_Tips NULL +#endif + +#ifndef SKIP_CMD_KEY_SPECS_TABLE +/* COMMANDLOG HELP key specs */ +#define COMMANDLOG_HELP_Keyspecs NULL +#endif + +/********** COMMANDLOG LEN ********************/ + +#ifndef SKIP_CMD_HISTORY_TABLE +/* COMMANDLOG LEN history */ +#define COMMANDLOG_LEN_History NULL +#endif + +#ifndef SKIP_CMD_TIPS_TABLE +/* COMMANDLOG LEN tips */ +const char *COMMANDLOG_LEN_Tips[] = { +"request_policy:all_nodes", +"response_policy:agg_sum", +"nondeterministic_output", +}; +#endif + +#ifndef SKIP_CMD_KEY_SPECS_TABLE +/* COMMANDLOG LEN key specs */ +#define COMMANDLOG_LEN_Keyspecs NULL +#endif + +/* COMMANDLOG LEN argument table */ +struct COMMAND_ARG COMMANDLOG_LEN_Args[] = { +{MAKE_ARG("type",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, +}; + +/********** COMMANDLOG RESET ********************/ + +#ifndef SKIP_CMD_HISTORY_TABLE +/* COMMANDLOG RESET history */ +#define COMMANDLOG_RESET_History NULL +#endif + +#ifndef SKIP_CMD_TIPS_TABLE +/* COMMANDLOG RESET tips */ +const char *COMMANDLOG_RESET_Tips[] = { +"request_policy:all_nodes", +"response_policy:all_succeeded", +}; +#endif + +#ifndef SKIP_CMD_KEY_SPECS_TABLE +/* COMMANDLOG RESET key specs */ +#define COMMANDLOG_RESET_Keyspecs NULL +#endif + +/* COMMANDLOG RESET argument table */ +struct COMMAND_ARG COMMANDLOG_RESET_Args[] = { +{MAKE_ARG("type",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, +}; + +/* COMMANDLOG command table */ +struct COMMAND_STRUCT COMMANDLOG_Subcommands[] = { +{MAKE_CMD("get","Returns the specified command log's entries.","O(N) where N is the number of entries returned","8.2.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMANDLOG_GET_History,0,COMMANDLOG_GET_Tips,2,commandlogCommand,4,CMD_ADMIN|CMD_LOADING|CMD_STALE,0,COMMANDLOG_GET_Keyspecs,0,NULL,2),.args=COMMANDLOG_GET_Args}, +{MAKE_CMD("help","Show helpful text about the different subcommands","O(1)","8.2.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMANDLOG_HELP_History,0,COMMANDLOG_HELP_Tips,0,commandlogCommand,2,CMD_LOADING|CMD_STALE,0,COMMANDLOG_HELP_Keyspecs,0,NULL,0)}, +{MAKE_CMD("len","Returns the number of entries in the specified type of command log.","O(1)","8.2.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMANDLOG_LEN_History,0,COMMANDLOG_LEN_Tips,3,commandlogCommand,3,CMD_ADMIN|CMD_LOADING|CMD_STALE,0,COMMANDLOG_LEN_Keyspecs,0,NULL,1),.args=COMMANDLOG_LEN_Args}, +{MAKE_CMD("reset","Clears all entries from the specified type of command log.","O(N) where N is the number of entries in the commandlog","8.2.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMANDLOG_RESET_History,0,COMMANDLOG_RESET_Tips,2,commandlogCommand,3,CMD_ADMIN|CMD_LOADING|CMD_STALE,0,COMMANDLOG_RESET_Keyspecs,0,NULL,1),.args=COMMANDLOG_RESET_Args}, +{0} +}; + +/********** COMMANDLOG ********************/ + +#ifndef SKIP_CMD_HISTORY_TABLE +/* COMMANDLOG history */ +#define COMMANDLOG_History NULL +#endif + +#ifndef SKIP_CMD_TIPS_TABLE +/* COMMANDLOG tips */ +#define COMMANDLOG_Tips NULL +#endif + +#ifndef SKIP_CMD_KEY_SPECS_TABLE +/* COMMANDLOG key specs */ +#define COMMANDLOG_Keyspecs NULL +#endif + /********** CONFIG GET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE @@ -10991,6 +11111,7 @@ struct COMMAND_STRUCT serverCommandTable[] = { {MAKE_CMD("bgrewriteaof","Asynchronously rewrites the append-only file to disk.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,BGREWRITEAOF_History,0,BGREWRITEAOF_Tips,0,bgrewriteaofCommand,1,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT,0,BGREWRITEAOF_Keyspecs,0,NULL,0)}, {MAKE_CMD("bgsave","Asynchronously saves the database(s) to disk.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,BGSAVE_History,1,BGSAVE_Tips,0,bgsaveCommand,-1,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT,0,BGSAVE_Keyspecs,0,NULL,1),.args=BGSAVE_Args}, {MAKE_CMD("command","Returns detailed information about all commands.","O(N) where N is the total number of commands","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMAND_History,0,COMMAND_Tips,1,commandCommand,-1,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,COMMAND_Keyspecs,0,NULL,0),.subcommands=COMMAND_Subcommands}, +{MAKE_CMD("commandlog","A container for command log commands.","Depends on subcommand.","8.2.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMANDLOG_History,0,COMMANDLOG_Tips,0,NULL,-2,0,0,COMMANDLOG_Keyspecs,0,NULL,0),.subcommands=COMMANDLOG_Subcommands}, {MAKE_CMD("config","A container for server configuration commands.","Depends on subcommand.","2.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,CONFIG_History,0,CONFIG_Tips,0,NULL,-2,0,0,CONFIG_Keyspecs,0,NULL,0),.subcommands=CONFIG_Subcommands}, {MAKE_CMD("dbsize","Returns the number of keys in the database.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,DBSIZE_History,0,DBSIZE_Tips,2,dbsizeCommand,1,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,DBSIZE_Keyspecs,0,NULL,0)}, {MAKE_CMD("debug","A container for debugging commands.","Depends on subcommand.","1.0.0",CMD_DOC_SYSCMD,NULL,NULL,"server",COMMAND_GROUP_SERVER,DEBUG_History,0,DEBUG_Tips,0,debugCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_PROTECTED,0,DEBUG_Keyspecs,0,NULL,0)}, diff --git a/src/commands/commandlog-get.json b/src/commands/commandlog-get.json new file mode 100644 index 0000000000..2bb6fdaf5a --- /dev/null +++ b/src/commands/commandlog-get.json @@ -0,0 +1,71 @@ +{ + "GET": { + "summary": "Returns the specified command log's entries.", + "complexity": "O(N) where N is the number of entries returned", + "group": "server", + "since": "8.2.0", + "arity": 4, + "container": "COMMANDLOG", + "function": "commandlogCommand", + "command_flags": [ + "ADMIN", + "LOADING", + "STALE" + ], + "command_tips": [ + "REQUEST_POLICY:ALL_NODES", + "NONDETERMINISTIC_OUTPUT" + ], + "reply_schema": { + "type": "array", + "description": "Entries from the command log in chronological order.", + "uniqueItems": true, + "items": { + "type": "array", + "minItems": 6, + "maxItems": 6, + "items": [ + { + "type": "integer", + "description": "Command log entry ID." + }, + { + "type": "integer", + "description": "The unix timestamp at which the logged command was processed.", + "minimum": 0 + }, + { + "type": "integer", + "description": "Determined by the type parameter.", + "minimum": 0 + }, + { + "type": "array", + "description": "The arguments of the command.", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Client IP address and port." + }, + { + "type": "string", + "description": "Client name if set via the CLIENT SETNAME command." + } + ] + } + }, + "arguments": [ + { + "name": "count", + "type": "integer" + }, + { + "name": "type", + "type": "string" + } + ] + } +} diff --git a/src/commands/commandlog-help.json b/src/commands/commandlog-help.json new file mode 100644 index 0000000000..bac4e8117d --- /dev/null +++ b/src/commands/commandlog-help.json @@ -0,0 +1,22 @@ +{ + "HELP": { + "summary": "Show helpful text about the different subcommands", + "complexity": "O(1)", + "group": "server", + "since": "8.2.0", + "arity": 2, + "container": "COMMANDLOG", + "function": "commandlogCommand", + "command_flags": [ + "LOADING", + "STALE" + ], + "reply_schema": { + "type": "array", + "description": "Helpful text about subcommands.", + "items": { + "type": "string" + } + } + } +} diff --git a/src/commands/commandlog-len.json b/src/commands/commandlog-len.json new file mode 100644 index 0000000000..f699140ccb --- /dev/null +++ b/src/commands/commandlog-len.json @@ -0,0 +1,32 @@ +{ + "LEN": { + "summary": "Returns the number of entries in the specified type of command log.", + "complexity": "O(1)", + "group": "server", + "since": "8.2.0", + "arity": 3, + "container": "COMMANDLOG", + "function": "commandlogCommand", + "command_flags": [ + "ADMIN", + "LOADING", + "STALE" + ], + "command_tips": [ + "REQUEST_POLICY:ALL_NODES", + "RESPONSE_POLICY:AGG_SUM", + "NONDETERMINISTIC_OUTPUT" + ], + "reply_schema": { + "type": "integer", + "description": "Number of entries in the command log.", + "minimum": 0 + }, + "arguments": [ + { + "name": "type", + "type": "string" + } + ] + } +} diff --git a/src/commands/commandlog-reset.json b/src/commands/commandlog-reset.json new file mode 100644 index 0000000000..39e925e942 --- /dev/null +++ b/src/commands/commandlog-reset.json @@ -0,0 +1,29 @@ +{ + "RESET": { + "summary": "Clears all entries from the specified type of command log.", + "complexity": "O(N) where N is the number of entries in the commandlog", + "group": "server", + "since": "8.2.0", + "arity": 3, + "container": "COMMANDLOG", + "function": "commandlogCommand", + "command_flags": [ + "ADMIN", + "LOADING", + "STALE" + ], + "command_tips": [ + "REQUEST_POLICY:ALL_NODES", + "RESPONSE_POLICY:ALL_SUCCEEDED" + ], + "reply_schema": { + "const": "OK" + }, + "arguments": [ + { + "name": "type", + "type": "string" + } + ] + } +} diff --git a/src/commands/commandlog.json b/src/commands/commandlog.json new file mode 100644 index 0000000000..4f717dc252 --- /dev/null +++ b/src/commands/commandlog.json @@ -0,0 +1,9 @@ +{ + "COMMANDLOG": { + "summary": "A container for command log commands.", + "complexity": "Depends on subcommand.", + "group": "server", + "since": "8.2.0", + "arity": -2 + } +} diff --git a/src/server.h b/src/server.h index 2e0b4bbf88..071663a19d 100644 --- a/src/server.h +++ b/src/server.h @@ -3766,6 +3766,7 @@ void bgsaveCommand(client *c); void bgrewriteaofCommand(client *c); void shutdownCommand(client *c); void slowlogCommand(client *c); +void commandlogCommand(client *c); void moveCommand(client *c); void copyCommand(client *c); void renameCommand(client *c);