Extend modules API to allow modules report to redis INFO

this implements #6012
This commit is contained in:
Oran Agra 2019-07-24 12:58:15 +03:00
parent bc5cb168f5
commit e91d9a6fff
9 changed files with 260 additions and 3 deletions

View File

@ -13,4 +13,4 @@ then
fi
make -C tests/modules && \
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter "${@}"
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/infotest "${@}"

View File

@ -1337,6 +1337,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
/* Log dump of processor registers */
logRegisters(uc);
/* Log Modules INFO */
serverLogRaw(LL_WARNING|LL_RAW, "\n------ MODULES INFO OUTPUT ------\n");
infostring = modulesCollectInfo(sdsempty(), NULL, 1, 0);
serverLogRaw(LL_WARNING|LL_RAW, infostring);
sdsfree(infostring);
#if defined(HAVE_PROC_MAPS)
/* Test memory */
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");

View File

@ -40,6 +40,16 @@
* pointers that have an API the module can call with them)
* -------------------------------------------------------------------------- */
typedef struct RedisModuleInfoCtx {
struct RedisModule *module;
sds requested_section;
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 */
} RedisModuleInfoCtx;
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
/* This structure represents a module inside the system. */
struct RedisModule {
void *handle; /* Module dlopen() handle. */
@ -51,6 +61,7 @@ struct RedisModule {
list *using; /* List of modules we use some APIs of. */
list *filters; /* List of filters the module has registered. */
int in_call; /* RM_Call() nesting level */
RedisModuleInfoFunc info_cb; /* callback for module to add INFO fields. */
};
typedef struct RedisModule RedisModule;
@ -4686,6 +4697,118 @@ int RM_DictCompare(RedisModuleDictIter *di, const char *op, RedisModuleString *k
return res ? REDISMODULE_OK : REDISMODULE_ERR;
}
/* --------------------------------------------------------------------------
* Modules Info fields
* -------------------------------------------------------------------------- */
/* Used to start a new section, before adding any fields. the section name will
* be prefixed by "<modulename>_" and must only include A-Z,a-z,0-9.
* When return value is REDISMODULE_ERR, the section should and will be skipped. */
int RM_AddInfoSection(RedisModuleInfoCtx *ctx, char *name) {
sds full_name = sdscatprintf(sdsdup(ctx->module->name), "_%s", name);
/* proceed only if:
* 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)) {
sdsfree(full_name);
ctx->in_section = 0;
return REDISMODULE_ERR;
}
}
if (ctx->sections++) ctx->info = sdscat(ctx->info,"\r\n");
ctx->info = sdscatprintf(ctx->info, "# %s\r\n", full_name);
ctx->in_section = 1;
sdsfree(full_name);
return REDISMODULE_OK;
}
/* Used by RedisModuleInfoFunc to add info fields.
* Each field will be automatically prefixed by "<modulename>_".
* Field names or values must not include \r\n of ":" */
int RM_AddInfoFieldString(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value) {
if (!ctx->in_section)
return REDISMODULE_ERR;
ctx->info = sdscatprintf(ctx->info,
"%s_%s:%s\r\n",
ctx->module->name,
field,
(sds)value->ptr);
return REDISMODULE_OK;
}
int RM_AddInfoFieldCString(RedisModuleInfoCtx *ctx, char *field, char *value) {
if (!ctx->in_section)
return REDISMODULE_ERR;
ctx->info = sdscatprintf(ctx->info,
"%s_%s:%s\r\n",
ctx->module->name,
field,
value);
return REDISMODULE_OK;
}
int RM_AddInfoFieldDouble(RedisModuleInfoCtx *ctx, char *field, double value) {
if (!ctx->in_section)
return REDISMODULE_ERR;
ctx->info = sdscatprintf(ctx->info,
"%s_%s:%.17g\r\n",
ctx->module->name,
field,
value);
return REDISMODULE_OK;
}
int RM_AddInfoFieldLongLong(RedisModuleInfoCtx *ctx, char *field, long long value) {
if (!ctx->in_section)
return REDISMODULE_ERR;
ctx->info = sdscatprintf(ctx->info,
"%s_%s:%lld\r\n",
ctx->module->name,
field,
value);
return REDISMODULE_OK;
}
int RM_AddInfoFieldULongLong(RedisModuleInfoCtx *ctx, char *field, unsigned long long value) {
if (!ctx->in_section)
return REDISMODULE_ERR;
ctx->info = sdscatprintf(ctx->info,
"%s_%s:%llu\r\n",
ctx->module->name,
field,
value);
return REDISMODULE_OK;
}
int RM_RegisterInfoFunc(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) {
ctx->module->info_cb = cb;
return REDISMODULE_OK;
}
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections) {
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct RedisModule *module = dictGetVal(de);
if (!module->info_cb)
continue;
RedisModuleInfoCtx info_ctx = {module, section, info, sections, 0};
module->info_cb(&info_ctx, for_crash_report);
info = info_ctx.info;
sections = info_ctx.sections;
}
dictReleaseIterator(di);
return info;
}
/* --------------------------------------------------------------------------
* Modules utility APIs
* -------------------------------------------------------------------------- */
@ -5490,4 +5613,11 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(CommandFilterArgInsert);
REGISTER_API(CommandFilterArgReplace);
REGISTER_API(CommandFilterArgDelete);
REGISTER_API(RegisterInfoFunc);
REGISTER_API(AddInfoSection);
REGISTER_API(AddInfoFieldString);
REGISTER_API(AddInfoFieldCString);
REGISTER_API(AddInfoFieldDouble);
REGISTER_API(AddInfoFieldLongLong);
REGISTER_API(AddInfoFieldULongLong);
}

View File

@ -160,6 +160,7 @@ typedef struct RedisModuleDict RedisModuleDict;
typedef struct RedisModuleDictIter RedisModuleDictIter;
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
@ -173,6 +174,7 @@ typedef void (*RedisModuleTypeFreeFunc)(void *value);
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
#define REDISMODULE_TYPE_METHOD_VERSION 1
typedef struct RedisModuleTypeMethods {
@ -317,6 +319,13 @@ RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ct
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
int REDISMODULE_API_FUNC(RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb);
int REDISMODULE_API_FUNC(RedisModule_AddInfoSection)(RedisModuleInfoCtx *ctx, char *name);
int REDISMODULE_API_FUNC(RedisModule_AddInfoFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value);
int REDISMODULE_API_FUNC(RedisModule_AddInfoFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value);
int REDISMODULE_API_FUNC(RedisModule_AddInfoFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value);
int REDISMODULE_API_FUNC(RedisModule_AddInfoFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value);
int REDISMODULE_API_FUNC(RedisModule_AddInfoFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value);
/* Experimental APIs */
#ifdef REDISMODULE_EXPERIMENTAL_API
@ -490,6 +499,13 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(DictPrev);
REDISMODULE_GET_API(DictCompare);
REDISMODULE_GET_API(DictCompareC);
REDISMODULE_GET_API(RegisterInfoFunc);
REDISMODULE_GET_API(AddInfoSection);
REDISMODULE_GET_API(AddInfoFieldString);
REDISMODULE_GET_API(AddInfoFieldCString);
REDISMODULE_GET_API(AddInfoFieldDouble);
REDISMODULE_GET_API(AddInfoFieldLongLong);
REDISMODULE_GET_API(AddInfoFieldULongLong);
#ifdef REDISMODULE_EXPERIMENTAL_API
REDISMODULE_GET_API(GetThreadSafeContext);

View File

@ -3809,12 +3809,15 @@ sds genRedisInfoString(char *section) {
time_t uptime = server.unixtime-server.stat_starttime;
int j;
struct rusage self_ru, c_ru;
int allsections = 0, defsections = 0;
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;
getrusage(RUSAGE_SELF, &self_ru);
getrusage(RUSAGE_CHILDREN, &c_ru);
@ -4357,6 +4360,17 @@ sds genRedisInfoString(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)) {
info = modulesCollectInfo(info,
everything || modules ? NULL: section,
0, /* not a crash report */
sections);
}
return info;
}

View File

@ -1528,6 +1528,7 @@ void moduleAcquireGIL(void);
void moduleReleaseGIL(void);
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
void moduleCallCommandFilters(client *c);
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections);
/* Utils */
long long ustime(void);

View File

@ -13,12 +13,17 @@ endif
.SUFFIXES: .c .so .xo .o
all: commandfilter.so
all: commandfilter.so infotest.so
.c.xo:
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
commandfilter.xo: ../../src/redismodule.h
infotest.xo: ../../src/redismodule.h
commandfilter.so: commandfilter.xo
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
infotest.so: infotest.xo
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc

32
tests/modules/infotest.c Normal file
View File

@ -0,0 +1,32 @@
#include "redismodule.h"
#include <string.h>
void InfoFunc(RedisModuleInfoCtx *ctx, int for_crash_report) {
RedisModule_AddInfoSection(ctx, "Spanish");
RedisModule_AddInfoFieldCString(ctx, "uno", "one");
RedisModule_AddInfoFieldLongLong(ctx, "dos", 2);
RedisModule_AddInfoSection(ctx, "Italian");
RedisModule_AddInfoFieldLongLong(ctx, "due", 2);
RedisModule_AddInfoFieldDouble(ctx, "tre", 3.3);
if (for_crash_report) {
RedisModule_AddInfoSection(ctx, "Klingon");
RedisModule_AddInfoFieldCString(ctx, "one", "wa");
RedisModule_AddInfoFieldCString(ctx, "two", "cha");
RedisModule_AddInfoFieldCString(ctx, "three", "wej");
}
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx,"infotest",1,REDISMODULE_APIVER_1)
== REDISMODULE_ERR) return REDISMODULE_ERR;
if (RedisModule_RegisterInfoFunc(ctx, InfoFunc) == REDISMODULE_ERR) return REDISMODULE_ERR;
return REDISMODULE_OK;
}

View File

@ -0,0 +1,53 @@
set testmodule [file normalize tests/modules/infotest.so]
# Return value for INFO property
proc field {info property} {
if {[regexp "\r\n$property:(.*?)\r\n" $info _ value]} {
set _ $value
}
}
start_server {tags {"modules"}} {
r module load $testmodule log-key 0
test {module info all} {
set info [r info all]
# info all does not contain modules
assert { ![string match "*Spanish*" $info] }
assert { [string match "*used_memory*" $info] }
}
test {module info everything} {
set info [r info everything]
# info everything contains all default sections, but not ones for crash report
assert { [string match "*Spanish*" $info] }
assert { [string match "*Italian*" $info] }
assert { [string match "*used_memory*" $info] }
assert { ![string match "*Klingon*" $info] }
field $info infotest_dos
} {2}
test {module info modules} {
set info [r info modules]
# info all does not contain modules
assert { [string match "*Spanish*" $info] }
assert { ![string match "*used_memory*" $info] }
}
test {module info one module} {
set info [r info INFOTEST]
# info all does not contain modules
assert { [string match "*Spanish*" $info] }
assert { ![string match "*used_memory*" $info] }
}
test {module info one section} {
set info [r info INFOTEST_SPANISH]
assert { ![string match "*used_memory*" $info] }
assert { ![string match "*Italian*" $info] }
field $info infotest_uno
} {one}
# TODO: test crash report.
}