Make INFO command variadic (#6891)

This is an enhancement for INFO command, previously INFO only support one argument
for different info section , if user want to get more categories information, either perform
INFO all / default or calling INFO for multiple times.

**Description of the feature**

The goal of adding this feature is to let the user retrieve multiple categories via the INFO
command, and still avoid emitting the same section twice.

A use case for this is like Redis Sentinel, which periodically calling INFO command to refresh
info from monitored Master/Slaves, only Server and Replication part categories are used for
parsing information. If the INFO command can return just enough categories that client side
needs, it can save a lot of time for client side parsing it as well as network bandwidth.

**Implementation**
To share code between redis, sentinel, and other users of INFO (DEBUG and modules),
we have a new `genInfoSectionDict` function that returns a dict and some boolean flags
(e.g. `all`) to the caller (built from user input).
Sentinel is later purging unwanted sections from that, and then it is forwarded to the info `genRedisInfoString`.

**Usage Examples**
INFO Server Replication   
INFO CPU Memory
INFO default commandstats

Co-authored-by: Oran Agra <oran@redislabs.com>
This commit is contained in:
Wen Hui 2022-02-08 06:14:42 -05:00 committed by GitHub
parent b76016a948
commit 2e1bc942aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 342 additions and 81 deletions

View File

@ -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 */

View File

@ -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
}
]

View File

@ -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 */

View File

@ -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; i<totlines; i++) {
@ -8988,6 +8993,8 @@ RedisModuleServerInfoData *RM_GetServerInfo(RedisModuleCtx *ctx, const char *sec
}
sdsfree(info);
sdsfreesplitres(lines,totlines);
releaseInfoSectionDict(section_dict);
if(argv[0]) decrRefCount(argv[0]);
return d;
}

View File

@ -4099,46 +4099,52 @@ numargserr:
addReplyErrorArity(c);
}
#define info_section_from_redis(section_name) do { \
if (defsections || allsections || !strcasecmp(section,section_name)) { \
sds redissection; \
if (sections++) info = sdscat(info,"\r\n"); \
redissection = genRedisInfoString(section_name); \
info = sdscatlen(info,redissection,sdslen(redissection)); \
sdsfree(redissection); \
} \
} while(0)
void addInfoSectionsToDict(dict *section_dict, char **sections);
/* SENTINEL INFO [section] */
void sentinelInfoCommand(client *c) {
if (c->argc > 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);
}

View File

@ -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) {

View File

@ -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);

View File

@ -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] }
}

View File

@ -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

View File

@ -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] }
}
}

View File

@ -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]
}