diff --git a/src/commands.c b/src/commands.c index 37f031683..650076f2f 100644 --- a/src/commands.c +++ b/src/commands.c @@ -3404,7 +3404,7 @@ struct redisCommandArg FUNCTION_LOAD_Args[] = { {"engine-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE}, {"library-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE}, {"replace",ARG_TYPE_PURE_TOKEN,-1,"REPLACE",NULL,NULL,CMD_ARG_OPTIONAL}, -{"library-description",ARG_TYPE_STRING,-1,"DESCRIPTION",NULL,NULL,CMD_ARG_OPTIONAL}, +{"library-description",ARG_TYPE_STRING,-1,"DESC",NULL,NULL,CMD_ARG_OPTIONAL}, {"function-code",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE}, {0} }; @@ -4309,7 +4309,10 @@ struct redisCommandArg FLUSHDB_Args[] = { /********** INFO ********************/ /* INFO history */ -#define INFO_History NULL +commandHistory INFO_History[] = { +{"7.0.0","Added support for taking multiple section arguments."}, +{0} +}; /* INFO tips */ const char *INFO_tips[] = { @@ -4321,7 +4324,7 @@ NULL /* INFO argument table */ struct redisCommandArg INFO_Args[] = { -{"section",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL}, +{"section",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE}, {0} }; @@ -6944,8 +6947,8 @@ struct redisCommand redisCommandTable[] = { {"evalsha","Execute a Lua script server side","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,EVALSHA_History,EVALSHA_tips,evalShaCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},evalGetKeys,.args=EVALSHA_Args}, {"evalsha_ro","Execute a read-only Lua script server side","Depends on the script that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,EVALSHA_RO_History,EVALSHA_RO_tips,evalShaRoCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},evalGetKeys,.args=EVALSHA_RO_Args}, {"eval_ro","Execute a read-only Lua script server side","Depends on the script that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,EVAL_RO_History,EVAL_RO_tips,evalRoCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RO and ACCESS",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},evalGetKeys,.args=EVAL_RO_Args}, -{"fcall","Invoke a function","Depends on the function that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FCALL_History,FCALL_tips,fcallCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RW and UPDATE",CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},functionGetKeys,.args=FCALL_Args}, -{"fcall_ro","Invoke a read-only function","Depends on the function that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FCALL_RO_History,FCALL_RO_tips,fcallroCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RO and ACCESS",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},functionGetKeys,.args=FCALL_RO_Args}, +{"fcall","PATCH__TBD__38__","PATCH__TBD__37__","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FCALL_History,FCALL_tips,fcallCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RW and UPDATE",CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},functionGetKeys,.args=FCALL_Args}, +{"fcall_ro","PATCH__TBD__7__","PATCH__TBD__6__","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FCALL_RO_History,FCALL_RO_tips,fcallroCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RO and ACCESS",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},functionGetKeys,.args=FCALL_RO_Args}, {"function","A container for function commands","Depends on subcommand.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FUNCTION_History,FUNCTION_tips,NULL,-2,0,0,.subcommands=FUNCTION_Subcommands}, {"script","A container for Lua scripts management commands","Depends on subcommand.","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_History,SCRIPT_tips,NULL,-2,0,0,.subcommands=SCRIPT_Subcommands}, /* sentinel */ diff --git a/src/commands/info.json b/src/commands/info.json index 48720f9c3..612294d34 100644 --- a/src/commands/info.json +++ b/src/commands/info.json @@ -6,6 +6,12 @@ "since": "1.0.0", "arity": -1, "function": "infoCommand", + "history": [ + [ + "7.0.0", + "Added support for taking multiple section arguments." + ] + ], "command_flags": [ "LOADING", "STALE", @@ -23,6 +29,7 @@ { "name": "section", "type": "string", + "multiple": true, "optional": true } ] diff --git a/src/debug.c b/src/debug.c index 305941586..c4f30a3ae 100644 --- a/src/debug.c +++ b/src/debug.c @@ -1681,13 +1681,19 @@ void logStackTrace(void *eip, int uplevel) { void logServerInfo(void) { sds infostring, clients; serverLogRaw(LL_WARNING|LL_RAW, "\n------ INFO OUTPUT ------\n"); - infostring = genRedisInfoString("all"); + int all = 0, everything = 0; + robj *argv[1]; + argv[0] = createStringObject("all", strlen("all")); + dict *section_dict = genInfoSectionDict(argv, 1, NULL, &all, &everything); + infostring = genRedisInfoString(section_dict, all, everything); serverLogRaw(LL_WARNING|LL_RAW, infostring); serverLogRaw(LL_WARNING|LL_RAW, "\n------ CLIENT LIST OUTPUT ------\n"); clients = getAllClientsInfoString(-1); serverLogRaw(LL_WARNING|LL_RAW, clients); sdsfree(infostring); sdsfree(clients); + releaseInfoSectionDict(section_dict); + decrRefCount(argv[0]); } /* Log certain config values, which can be used for debuggin */ diff --git a/src/module.c b/src/module.c index 62bbd35e7..5584fbdd6 100644 --- a/src/module.c +++ b/src/module.c @@ -70,7 +70,7 @@ typedef struct RedisModuleInfoCtx { struct RedisModule *module; - const char *requested_section; + dict *requested_sections; sds info; /* info string we collected so far */ int sections; /* number of sections we collected so far */ int in_section; /* indication if we're in an active section or not */ @@ -8791,9 +8791,10 @@ int RM_InfoAddSection(RedisModuleInfoCtx *ctx, const char *name) { * 1) no section was requested (emit all) * 2) the module name was requested (emit all) * 3) this specific section was requested. */ - if (ctx->requested_section) { - if (strcasecmp(ctx->requested_section, full_name) && - strcasecmp(ctx->requested_section, ctx->module->name)) { + if (ctx->requested_sections) { + if ((!full_name || !dictFind(ctx->requested_sections, full_name)) && + (!dictFind(ctx->requested_sections, ctx->module->name))) + { sdsfree(full_name); ctx->in_section = 0; return REDISMODULE_ERR; @@ -8942,7 +8943,7 @@ int RM_RegisterInfoFunc(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) { return REDISMODULE_OK; } -sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int sections) { +sds modulesCollectInfo(sds info, dict *sections_dict, int for_crash_report, int sections) { dictIterator *di = dictGetIterator(modules); dictEntry *de; @@ -8950,7 +8951,7 @@ sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int struct RedisModule *module = dictGetVal(de); if (!module->info_cb) continue; - RedisModuleInfoCtx info_ctx = {module, section, info, sections, 0, 0}; + RedisModuleInfoCtx info_ctx = {module, sections_dict, info, sections, 0, 0}; module->info_cb(&info_ctx, for_crash_report); /* Implicitly end dicts (no way to handle errors, and we must add the newline). */ if (info_ctx.in_dict_field) @@ -8972,7 +8973,11 @@ RedisModuleServerInfoData *RM_GetServerInfo(RedisModuleCtx *ctx, const char *sec struct RedisModuleServerInfoData *d = zmalloc(sizeof(*d)); d->rax = raxNew(); if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_INFO,d); - sds info = genRedisInfoString(section); + int all = 0, everything = 0; + robj *argv[1]; + argv[0] = section ? createStringObject(section, strlen(section)) : NULL; + dict *section_dict = genInfoSectionDict(argv, section ? 1 : 0, NULL, &all, &everything); + sds info = genRedisInfoString(section_dict, all, everything); int totlines, i; sds *lines = sdssplitlen(info, sdslen(info), "\r\n", 2, &totlines); for(i=0; iargc > 2) { - addReplyErrorObject(c,shared.syntaxerr); - return; + char *sentinel_sections[] = {"server", "clients", "cpu", "stats", "sentinel", NULL}; + int sec_all = 0, sec_everything = 0; + static dict *cached_all_info_sectoins = NULL; + + /* Get requested section list. */ + dict *sections_dict = genInfoSectionDict(c->argv+1, c->argc-1, sentinel_sections, &sec_all, &sec_everything); + + /* Purge unsupported sections from the requested ones. */ + dictEntry *de; + dictIterator *di = dictGetSafeIterator(sections_dict); + while((de = dictNext(di)) != NULL) { + int i; + sds sec = dictGetKey(de); + for (i=0; sentinel_sections[i]; i++) + if (!strcasecmp(sentinel_sections[i], sec)) + break; + /* section not found? remove it */ + if (!sentinel_sections[i]) + dictDelete(sections_dict, sec); + } + dictReleaseIterator(di); + + /* Insert explicit all sections (don't pass these vars to genRedisInfoString) */ + if (sec_all || sec_everything) { + releaseInfoSectionDict(sections_dict); + /* We cache this dict as an optimization. */ + if (!cached_all_info_sectoins) { + cached_all_info_sectoins = dictCreate(&stringSetDictType); + addInfoSectionsToDict(cached_all_info_sectoins, sentinel_sections); + } + sections_dict = cached_all_info_sectoins; } - int defsections = 0, allsections = 0; - char *section = c->argc == 2 ? c->argv[1]->ptr : NULL; - if (section) { - allsections = !strcasecmp(section,"all"); - defsections = !strcasecmp(section,"default"); - } else { - defsections = 1; - } - - int sections = 0; sds info = sdsempty(); - - info_section_from_redis("server"); - info_section_from_redis("clients"); - info_section_from_redis("cpu"); - info_section_from_redis("stats"); - - if (defsections || allsections || !strcasecmp(section,"sentinel")) { + info = genRedisInfoString(sections_dict, 0, 0); + if (sec_all || (dictFind(sections_dict, "sentinel") != NULL)) { dictIterator *di; dictEntry *de; int master_id = 0; - if (sections++) info = sdscat(info,"\r\n"); + if (sdslen(info) != 0) + info = sdscat(info,"\r\n"); info = sdscatprintf(info, "# Sentinel\r\n" "sentinel_masters:%lu\r\n" @@ -4171,7 +4177,8 @@ void sentinelInfoCommand(client *c) { } dictReleaseIterator(di); } - + if (sections_dict != cached_all_info_sectoins) + releaseInfoSectionDict(sections_dict); addReplyBulkSds(c, info); } diff --git a/src/server.c b/src/server.c index 314e0b301..35a9d8062 100644 --- a/src/server.c +++ b/src/server.c @@ -289,6 +289,33 @@ uint64_t dictSdsCaseHash(const void *key) { return dictGenCaseHashFunction((unsigned char*)key, sdslen((char*)key)); } +/* Dict hash function for null terminated string */ +uint64_t distCStrHash(const void *key) { + return dictGenHashFunction((unsigned char*)key, strlen((char*)key)); +} + +/* Dict hash function for null terminated string */ +uint64_t distCStrCaseHash(const void *key) { + return dictGenCaseHashFunction((unsigned char*)key, strlen((char*)key)); +} + +/* Dict compare function for null terminated string */ +int distCStrKeyCompare(dict *d, const void *key1, const void *key2) { + int l1,l2; + UNUSED(d); + + l1 = strlen((char*)key1); + l2 = strlen((char*)key2); + if (l1 != l2) return 0; + return memcmp(key1, key2, l1) == 0; +} + +/* Dict case insensitive compare function for null terminated string */ +int distCStrKeyCaseCompare(dict *d, const void *key1, const void *key2) { + UNUSED(d); + return strcasecmp(key1, key2) == 0; +} + int dictEncObjKeyCompare(dict *d, const void *key1, const void *key2) { robj *o1 = (robj*) key1, *o2 = (robj*) key2; @@ -500,6 +527,18 @@ dictType replScriptCacheDictType = { NULL /* allow to expand */ }; +/* Dict for for case-insensitive search using null terminated C strings. + * The keys stored in dict are sds though. */ +dictType stringSetDictType = { + distCStrCaseHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + distCStrKeyCaseCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + NULL, /* val destructor */ + NULL /* allow to expand */ +}; + int htNeedsResize(dict *dict) { long long size, used; @@ -4894,25 +4933,78 @@ sds genRedisInfoStringLatencyStats(sds info, dict *commands) { return info; } +/* Takes a null terminated sections list, and adds them to the dict. */ +void addInfoSectionsToDict(dict *section_dict, char **sections) { + while (*sections) { + sds section = sdsnew(*sections); + if (dictAdd(section_dict, section, NULL)==DICT_ERR) + sdsfree(section); + sections++; + } +} + +/* Cached copy of the default sections, as an optimization. */ +static dict *cached_default_info_sections = NULL; + +void releaseInfoSectionDict(dict *sec) { + if (sec != cached_default_info_sections) + dictRelease(sec); +} + +/* Create a dictionary with unique section names to be used by genRedisInfoString. + * 'argv' and 'argc' are list of arguments for INFO. + * 'defaults' is an optional null terminated list of default sections. + * 'out_all' and 'out_everything' are optional. + * The resulting dictionary should be released with releaseInfoSectionDict. */ +dict *genInfoSectionDict(robj **argv, int argc, char **defaults, int *out_all, int *out_everything) { + char *default_sections[] = { + "server", "clients", "memory", "persistence", "stats", "replication", + "cpu", "module_list", "errorstats", "cluster", "keyspace", NULL}; + if (!defaults) + defaults = default_sections; + + if (argc == 0) { + /* In this case we know the dict is not gonna be modified, so we cache + * it as an optimization for a common case. */ + if (cached_default_info_sections) + return cached_default_info_sections; + cached_default_info_sections = dictCreate(&stringSetDictType); + dictExpand(cached_default_info_sections, 16); + addInfoSectionsToDict(cached_default_info_sections, defaults); + return cached_default_info_sections; + } + + dict *section_dict = dictCreate(&stringSetDictType); + dictExpand(section_dict, min(argc,16)); + for (int i = 0; i < argc; i++) { + if (!strcasecmp(argv[i]->ptr,"default")) { + addInfoSectionsToDict(section_dict, defaults); + } else if (!strcasecmp(argv[i]->ptr,"all")) { + if (out_all) *out_all = 1; + } else if (!strcasecmp(argv[i]->ptr,"everything")) { + if (out_everything) *out_everything = 1; + if (out_all) *out_all = 1; + } else { + sds section = sdsnew(argv[i]->ptr); + if (dictAdd(section_dict, section, NULL) != DICT_OK) + sdsfree(section); + } + } + return section_dict; +} + /* Create the string returned by the INFO command. This is decoupled * by the INFO command itself as we need to report the same information * on memory corruption problems. */ -sds genRedisInfoString(const char *section) { +sds genRedisInfoString(dict *section_dict, int all_sections, int everything) { sds info = sdsempty(); time_t uptime = server.unixtime-server.stat_starttime; int j; - int allsections = 0, defsections = 0, everything = 0, modules = 0; int sections = 0; - - if (section == NULL) section = "default"; - allsections = strcasecmp(section,"all") == 0; - defsections = strcasecmp(section,"default") == 0; - everything = strcasecmp(section,"everything") == 0; - modules = strcasecmp(section,"modules") == 0; - if (everything) allsections = 1; + if (everything) all_sections = 1; /* Server */ - if (allsections || defsections || !strcasecmp(section,"server")) { + if (all_sections || (dictFind(section_dict,"server") != NULL)) { static int call_uname = 1; static struct utsname name; char *mode; @@ -5002,7 +5094,7 @@ sds genRedisInfoString(const char *section) { } /* Clients */ - if (allsections || defsections || !strcasecmp(section,"clients")) { + if (all_sections || (dictFind(section_dict,"clients") != NULL)) { size_t maxin, maxout; getExpansiveClientsInfo(&maxin,&maxout); if (sections++) info = sdscat(info,"\r\n"); @@ -5026,7 +5118,7 @@ sds genRedisInfoString(const char *section) { } /* Memory */ - if (allsections || defsections || !strcasecmp(section,"memory")) { + if (all_sections || (dictFind(section_dict,"memory") != NULL)) { char hmem[64]; char peak_hmem[64]; char total_system_hmem[64]; @@ -5171,7 +5263,7 @@ sds genRedisInfoString(const char *section) { } /* Persistence */ - if (allsections || defsections || !strcasecmp(section,"persistence")) { + if (all_sections || (dictFind(section_dict,"persistence") != NULL)) { if (sections++) info = sdscat(info,"\r\n"); double fork_perc = 0; if (server.stat_module_progress) { @@ -5300,7 +5392,7 @@ sds genRedisInfoString(const char *section) { } /* Stats */ - if (allsections || defsections || !strcasecmp(section,"stats")) { + if (all_sections || (dictFind(section_dict,"stats") != NULL)) { long long stat_total_reads_processed, stat_total_writes_processed; long long stat_net_input_bytes, stat_net_output_bytes; long long current_eviction_exceeded_time = server.stat_last_eviction_exceeded_time ? @@ -5404,7 +5496,7 @@ sds genRedisInfoString(const char *section) { } /* Replication */ - if (allsections || defsections || !strcasecmp(section,"replication")) { + if (all_sections || (dictFind(section_dict,"replication") != NULL)) { if (sections++) info = sdscat(info,"\r\n"); info = sdscatprintf(info, "# Replication\r\n" @@ -5540,7 +5632,7 @@ sds genRedisInfoString(const char *section) { } /* CPU */ - if (allsections || defsections || !strcasecmp(section,"cpu")) { + if (all_sections || (dictFind(section_dict,"cpu") != NULL)) { if (sections++) info = sdscat(info,"\r\n"); struct rusage self_ru, c_ru; @@ -5568,20 +5660,21 @@ sds genRedisInfoString(const char *section) { } /* Modules */ - if (allsections || defsections || !strcasecmp(section,"modules")) { + if (all_sections || (dictFind(section_dict,"module_list") != NULL) || (dictFind(section_dict,"modules") != NULL)) { if (sections++) info = sdscat(info,"\r\n"); info = sdscatprintf(info,"# Modules\r\n"); info = genModulesInfoString(info); } /* Command statistics */ - if (allsections || !strcasecmp(section,"commandstats")) { + if (all_sections || (dictFind(section_dict,"commandstats") != NULL)) { if (sections++) info = sdscat(info,"\r\n"); info = sdscatprintf(info, "# Commandstats\r\n"); info = genRedisInfoStringCommandStats(info, server.commands); } + /* Error statistics */ - if (allsections || defsections || !strcasecmp(section,"errorstats")) { + if (all_sections || (dictFind(section_dict,"errorstats") != NULL)) { if (sections++) info = sdscat(info,"\r\n"); info = sdscat(info, "# Errorstats\r\n"); raxIterator ri; @@ -5600,7 +5693,7 @@ sds genRedisInfoString(const char *section) { } /* Latency by percentile distribution per command */ - if (allsections || !strcasecmp(section,"latencystats")) { + if (all_sections || (dictFind(section_dict,"latencystats") != NULL)) { if (sections++) info = sdscat(info,"\r\n"); info = sdscatprintf(info, "# Latencystats\r\n"); if (server.latency_tracking_enabled) { @@ -5609,7 +5702,7 @@ sds genRedisInfoString(const char *section) { } /* Cluster */ - if (allsections || defsections || !strcasecmp(section,"cluster")) { + if (all_sections || (dictFind(section_dict,"cluster") != NULL)) { if (sections++) info = sdscat(info,"\r\n"); info = sdscatprintf(info, "# Cluster\r\n" @@ -5618,7 +5711,7 @@ sds genRedisInfoString(const char *section) { } /* Key space */ - if (allsections || defsections || !strcasecmp(section,"keyspace")) { + if (all_sections || (dictFind(section_dict,"keyspace") != NULL)) { if (sections++) info = sdscat(info,"\r\n"); info = sdscatprintf(info, "# Keyspace\r\n"); for (j = 0; j < server.dbnum; j++) { @@ -5637,10 +5730,10 @@ sds genRedisInfoString(const char *section) { /* Get info from modules. * if user asked for "everything" or "modules", or a specific section * that's not found yet. */ - if (everything || modules || - (!allsections && !defsections && sections==0)) { + if (everything || dictFind(section_dict, "modules") != NULL || sections < (int)dictSize(section_dict)) { + info = modulesCollectInfo(info, - everything || modules ? NULL: section, + everything || dictFind(section_dict, "modules") != NULL ? NULL: section_dict, 0, /* not a crash report */ sections); } @@ -5652,16 +5745,14 @@ void infoCommand(client *c) { sentinelInfoCommand(c); return; } - - char *section = c->argc == 2 ? c->argv[1]->ptr : "default"; - - if (c->argc > 2) { - addReplyErrorObject(c,shared.syntaxerr); - return; - } - sds info = genRedisInfoString(section); + int all_sections = 0; + int everything = 0; + dict *sections_dict = genInfoSectionDict(c->argv+1, c->argc-1, NULL, &all_sections, &everything); + sds info = genRedisInfoString(sections_dict, all_sections, everything); addReplyVerbatim(c,info,sdslen(info),"txt"); sdsfree(info); + releaseInfoSectionDict(sections_dict); + return; } void monitorCommand(client *c) { diff --git a/src/server.h b/src/server.h index e4f7246c8..1f9e05062 100644 --- a/src/server.h +++ b/src/server.h @@ -2297,11 +2297,13 @@ extern struct sharedObjectsStruct shared; extern dictType objectKeyPointerValueDictType; extern dictType objectKeyHeapPointerValueDictType; extern dictType setDictType; +extern dictType BenchmarkDictType; extern dictType zsetDictType; extern dictType dbDictType; extern double R_Zero, R_PosInf, R_NegInf, R_Nan; extern dictType hashDictType; extern dictType replScriptCacheDictType; +extern dictType stringSetDictType; extern dictType dbExpiresDictType; extern dictType modulesDictType; extern dictType sdsReplyDictType; @@ -2341,7 +2343,7 @@ int TerminateModuleForkChild(int child_pid, int wait); ssize_t rdbSaveModulesAux(rio *rdb, int when); int moduleAllDatatypesHandleErrors(); int moduleAllModulesHandleReplAsyncLoad(); -sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int sections); +sds modulesCollectInfo(sds info, dict *sections_dict, int for_crash_report, int sections); void moduleFireServerEvent(uint64_t eid, int subid, void *data); void processModuleLoadingProgressEvent(int is_aof); int moduleTryServeClientBlockedOnKey(client *c, robj *key); @@ -3396,7 +3398,9 @@ void _serverPanic(const char *file, int line, const char *msg, ...); void serverLogObjectDebugInfo(const robj *o); void sigsegvHandler(int sig, siginfo_t *info, void *secret); const char *getSafeInfoString(const char *s, size_t len, char **tmp); -sds genRedisInfoString(const char *section); +dict *genInfoSectionDict(robj **argv, int argc, char **defaults, int *out_all, int *out_everything); +void releaseInfoSectionDict(dict *sec); +sds genRedisInfoString(dict *section_dict, int all_sections, int everything); sds genModulesInfoString(sds info); void applyWatchdogPeriod(); void watchdogScheduleSignal(int period); diff --git a/tests/sentinel/tests/13-info-command.tcl b/tests/sentinel/tests/13-info-command.tcl new file mode 100644 index 000000000..4772a1da8 --- /dev/null +++ b/tests/sentinel/tests/13-info-command.tcl @@ -0,0 +1,48 @@ +# Check the basic monitoring and failover capabilities. +source "../tests/includes/init-tests.tcl" + +test "info command with at most one argument" { + set subCommandList {} + foreach arg {"" "all" "default" "everything"} { + if {$arg == ""} { + set info [S 0 info] + } else { + set info [S 0 info $arg] + } + assert { [string match "*redis_version*" $info] } + assert { [string match "*maxclients*" $info] } + assert { [string match "*used_cpu_user*" $info] } + assert { [string match "*sentinel_tilt*" $info] } + assert { ![string match "*used_memory*" $info] } + assert { ![string match "*rdb_last_bgsave*" $info] } + assert { ![string match "*master_repl_offset*" $info] } + assert { ![string match "*cluster_enabled*" $info] } + } +} + +test "info command with one sub-section" { + set info [S 0 info cpu] + assert { [string match "*used_cpu_user*" $info] } + assert { ![string match "*sentinel_tilt*" $info] } + assert { ![string match "*redis_version*" $info] } + + set info [S 0 info sentinel] + assert { [string match "*sentinel_tilt*" $info] } + assert { ![string match "*used_cpu_user*" $info] } + assert { ![string match "*redis_version*" $info] } +} + +test "info command with multiple sub-sections" { + set info [S 0 info server sentinel replication] + assert { [string match "*redis_version*" $info] } + assert { [string match "*sentinel_tilt*" $info] } + assert { ![string match "*used_memory*" $info] } + assert { ![string match "*used_cpu_user*" $info] } + + set info [S 0 info cpu all] + assert { [string match "*used_cpu_user*" $info] } + assert { [string match "*sentinel_tilt*" $info] } + assert { [string match "*redis_version*" $info] } + assert { ![string match "*used_memory*" $info] } + assert { ![string match "*master_repl_offset*" $info] } +} diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index 1a5096937..c92ce06f0 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -20,6 +20,7 @@ set ::all_tests { unit/keyspace unit/scan unit/info + unit/info-command unit/type/string unit/type/incr unit/type/list diff --git a/tests/unit/info-command.tcl b/tests/unit/info-command.tcl new file mode 100644 index 000000000..bc24ed256 --- /dev/null +++ b/tests/unit/info-command.tcl @@ -0,0 +1,62 @@ +start_server {tags {"info and its relative command"}} { + test "info command with at most one sub command" { + foreach arg {"" "all" "default" "everything"} { + if {$arg == ""} { + set info [r 0 info] + } else { + set info [r 0 info $arg] + } + + assert { [string match "*redis_version*" $info] } + assert { [string match "*used_cpu_user*" $info] } + assert { ![string match "*sentinel_tilt*" $info] } + assert { [string match "*used_memory*" $info] } + if {$arg == "" || $arg == "default"} { + assert { ![string match "*rejected_calls*" $info] } + } else { + assert { [string match "*rejected_calls*" $info] } + } + } + } + + test "info command with one sub-section" { + set info [r info cpu] + assert { [string match "*used_cpu_user*" $info] } + assert { ![string match "*sentinel_tilt*" $info] } + assert { ![string match "*used_memory*" $info] } + + set info [r info sentinel] + assert { ![string match "*sentinel_tilt*" $info] } + assert { ![string match "*used_memory*" $info] } + + set info [r info commandSTATS] ;# test case insensitive compare + assert { ![string match "*used_memory*" $info] } + assert { [string match "*rejected_calls*" $info] } + } + + test "info command with multiple sub-sections" { + set info [r info cpu sentinel] + assert { [string match "*used_cpu_user*" $info] } + assert { ![string match "*sentinel_tilt*" $info] } + assert { ![string match "*master_repl_offset*" $info] } + + set info [r info cpu all] + assert { [string match "*used_cpu_user*" $info] } + assert { ![string match "*sentinel_tilt*" $info] } + assert { [string match "*used_memory*" $info] } + assert { [string match "*master_repl_offset*" $info] } + assert { [string match "*rejected_calls*" $info] } + # check that we didn't get the same info twice + assert { ![string match "*used_cpu_user_children*used_cpu_user_children*" $info] } + + set info [r info cpu default] + assert { [string match "*used_cpu_user*" $info] } + assert { ![string match "*sentinel_tilt*" $info] } + assert { [string match "*used_memory*" $info] } + assert { [string match "*master_repl_offset*" $info] } + assert { ![string match "*rejected_calls*" $info] } + # check that we didn't get the same info twice + assert { ![string match "*used_cpu_user_children*used_cpu_user_children*" $info] } + } + +} diff --git a/tests/unit/moduleapi/infotest.tcl b/tests/unit/moduleapi/infotest.tcl index 0d07aaa7e..354487a19 100644 --- a/tests/unit/moduleapi/infotest.tcl +++ b/tests/unit/moduleapi/infotest.tcl @@ -64,7 +64,7 @@ start_server {tags {"modules"}} { } test {module info one module} { - set info [r info INFOTEST] + set info [r info INFOtest] ;# test case insensitive compare # info all does not contain modules assert { [string match "*Spanish*" $info] } assert { ![string match "*used_memory*" $info] } @@ -72,7 +72,7 @@ start_server {tags {"modules"}} { } {-2} test {module info one section} { - set info [r info INFOTEST_SPANISH] + set info [r info INFOtest_SpanisH] ;# test case insensitive compare assert { ![string match "*used_memory*" $info] } assert { ![string match "*Italian*" $info] } assert { ![string match "*infotest_global*" $info] } @@ -90,6 +90,31 @@ start_server {tags {"modules"}} { assert_match {*infotest_unsafe_field:value=1*} $info } + test {module info multiply sections without all, everything, default keywords} { + set info [r info replication INFOTEST] + assert { [string match "*Spanish*" $info] } + assert { ![string match "*used_memory*" $info] } + assert { [string match "*repl_offset*" $info] } + } + + test {module info multiply sections with all keyword and modules} { + set info [r info all modules] + assert { [string match "*cluster*" $info] } + assert { [string match "*cmdstat_info*" $info] } + assert { [string match "*infotest_global*" $info] } + } + + test {module info multiply sections with everything keyword} { + set info [r info replication everything cpu] + assert { [string match "*client_recent*" $info] } + assert { [string match "*cmdstat_info*" $info] } + assert { [string match "*Italian*" $info] } + # check that we didn't get the same info twice + assert { ![string match "*used_cpu_user_children*used_cpu_user_children*" $info] } + assert { ![string match "*Italian*Italian*" $info] } + field $info infotest_dos + } {2} + test "Unload the module - infotest" { assert_equal {OK} [r module unload infotest] }