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:
parent
b76016a948
commit
2e1bc942aa
@ -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 */
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
|
@ -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 */
|
||||
|
21
src/module.c
21
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; 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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
157
src/server.c
157
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) {
|
||||
|
@ -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);
|
||||
|
48
tests/sentinel/tests/13-info-command.tcl
Normal file
48
tests/sentinel/tests/13-info-command.tcl
Normal 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] }
|
||||
}
|
@ -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
|
||||
|
62
tests/unit/info-command.tcl
Normal file
62
tests/unit/info-command.tcl
Normal 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] }
|
||||
}
|
||||
|
||||
}
|
@ -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]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user