Add module api for looking into INFO fields

- Add RM_GetServerInfo and friends
- Add auto memory for new opaque struct
- Add tests for new APIs

other minor fixes:
- add const in various char pointers
- requested_section in modulesCollectInfo was actually not sds but char*
- extract new string2d out of getDoubleFromObject for code reuse

Add module API for
This commit is contained in:
Oran Agra 2019-11-03 15:02:25 +02:00
parent fdaea2a7a7
commit 4d580438b0
9 changed files with 220 additions and 14 deletions

View File

@ -41,7 +41,7 @@
typedef struct RedisModuleInfoCtx {
struct RedisModule *module;
sds requested_section;
const char *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 */
@ -93,6 +93,7 @@ struct AutoMemEntry {
#define REDISMODULE_AM_REPLY 2
#define REDISMODULE_AM_FREED 3 /* Explicitly freed by user already. */
#define REDISMODULE_AM_DICT 4
#define REDISMODULE_AM_INFO 5
/* The pool allocator block. Redis Modules can allocate memory via this special
* allocator that will automatically release it all once the callback returns.
@ -322,6 +323,10 @@ static struct RedisModuleForkInfo {
void* done_handler_user_data;
} moduleForkInfo = {0};
typedef struct RedisModuleServerInfoData {
rax *rax; /* parsed info data. */
} RedisModuleServerInfoData;
/* Flags for moduleCreateArgvFromUserFormat(). */
#define REDISMODULE_ARGV_REPLICATE (1<<0)
#define REDISMODULE_ARGV_NO_AOF (1<<1)
@ -361,6 +366,7 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx);
void RM_ZsetRangeStop(RedisModuleKey *kp);
static void zsetKeyReset(RedisModuleKey *key);
void RM_FreeDict(RedisModuleCtx *ctx, RedisModuleDict *d);
void RM_FreeServerInfo(RedisModuleCtx *ctx, RedisModuleServerInfoData *data);
/* --------------------------------------------------------------------------
* Heap allocation raw functions
@ -946,6 +952,7 @@ void autoMemoryCollect(RedisModuleCtx *ctx) {
case REDISMODULE_AM_REPLY: RM_FreeCallReply(ptr); break;
case REDISMODULE_AM_KEY: RM_CloseKey(ptr); break;
case REDISMODULE_AM_DICT: RM_FreeDict(NULL,ptr); break;
case REDISMODULE_AM_INFO: RM_FreeServerInfo(NULL,ptr); break;
}
}
ctx->flags |= REDISMODULE_CTX_AUTO_MEMORY;
@ -5449,7 +5456,7 @@ int RM_RegisterInfoFunc(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) {
return REDISMODULE_OK;
}
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections) {
sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int sections) {
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
@ -5469,6 +5476,102 @@ sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections
return info;
}
/* Get information about the server similar to the one that returns from the
* INFO command. This function takes an optional 'section' argument that may
* be NULL. The return value holds the output and can be used with
* RedisModule_ServerInfoGetField and alike to get the individual fields.
* When done, it needs to be freed with RedisModule_FreeServerInfo or with the
* automatic memory management mechanism if enabled. */
RedisModuleServerInfoData *RM_GetServerInfo(RedisModuleCtx *ctx, const char *section) {
struct RedisModuleServerInfoData *d = zmalloc(sizeof(*d));
d->rax = raxNew();
if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_INFO,d);
sds info = genRedisInfoString(section);
int totlines, i;
sds *lines = sdssplitlen(info, sdslen(info), "\r\n", 2, &totlines);
for(i=0; i<totlines; i++) {
sds line = lines[i];
if (line[0]=='#') continue;
char *sep = strchr(line, ':');
if (!sep) continue;
unsigned char *key = (unsigned char*)line;
size_t keylen = (intptr_t)sep-(intptr_t)line;
sds val = sdsnewlen(sep+1,sdslen(line)-((intptr_t)sep-(intptr_t)line)-1);
raxTryInsert(d->rax,key,keylen,val,NULL);
}
sdsfree(info);
sdsfreesplitres(lines,totlines);
return d;
}
/* Free data created with RM_GetServerInfo(). You need to pass the
* context pointer 'ctx' only if the dictionary was created using the
* context instead of passing NULL. */
void RM_FreeServerInfo(RedisModuleCtx *ctx, RedisModuleServerInfoData *data) {
if (ctx != NULL) autoMemoryFreed(ctx,REDISMODULE_AM_INFO,data);
raxIterator ri;
raxStart(&ri,data->rax);
while(1) {
raxSeek(&ri,"^",NULL,0);
if (!raxNext(&ri)) break;
raxRemove(data->rax,(unsigned char*)ri.key,ri.key_len,NULL);
sdsfree(ri.data);
}
raxStop(&ri);
raxFree(data->rax);
zfree(data);
}
/* Get the value of a field from data collected with RM_GetServerInfo(). You
* need to pass the context pointer 'ctx' only if you want to use auto memory
* mechanism to release the returned string. Return value will be NULL if the
* field was not found. */
RedisModuleString *RM_ServerInfoGetField(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field) {
sds val = raxFind(data->rax, (unsigned char *)field, strlen(field));
if (val == raxNotFound) return NULL;
RedisModuleString *o = createStringObject(val,sdslen(val));
if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
return o;
}
/* Get the value of a field from data collected with RM_GetServerInfo(). If the
* field is not found, or is not numerical, return value will be 0, and the
* optional out_err argument will be set to REDISMODULE_ERR. */
long long RM_ServerInfoGetFieldNumerical(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field, int *out_err) {
UNUSED(ctx);
long long ll;
sds val = raxFind(data->rax, (unsigned char *)field, strlen(field));
if (val == raxNotFound) {
if (out_err) *out_err = REDISMODULE_ERR;
return 0;
}
if (!string2ll(val,sdslen(val),&ll)) {
if (out_err) *out_err = REDISMODULE_ERR;
return 0;
}
if (out_err) *out_err = REDISMODULE_OK;
return ll;
}
/* Get the value of a field from data collected with RM_GetServerInfo(). If the
* field is not found, or is not a double, return value will be 0, and the
* optional out_err argument will be set to REDISMODULE_ERR. */
double RM_ServerInfoGetFieldDouble(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field, int *out_err) {
UNUSED(ctx);
double dbl;
sds val = raxFind(data->rax, (unsigned char *)field, strlen(field));
if (val == raxNotFound) {
if (out_err) *out_err = REDISMODULE_ERR;
return 0;
}
if (!string2d(val,sdslen(val),&dbl)) {
if (out_err) *out_err = REDISMODULE_ERR;
return 0;
}
if (out_err) *out_err = REDISMODULE_OK;
return dbl;
}
/* --------------------------------------------------------------------------
* Modules utility APIs
* -------------------------------------------------------------------------- */
@ -6756,6 +6859,11 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(InfoAddFieldDouble);
REGISTER_API(InfoAddFieldLongLong);
REGISTER_API(InfoAddFieldULongLong);
REGISTER_API(GetServerInfo);
REGISTER_API(FreeServerInfo);
REGISTER_API(ServerInfoGetField);
REGISTER_API(ServerInfoGetFieldNumerical);
REGISTER_API(ServerInfoGetFieldDouble);
REGISTER_API(GetClientInfoById);
REGISTER_API(SubscribeToServerEvent);
REGISTER_API(BlockClientOnKeys);

View File

@ -606,21 +606,13 @@ size_t stringObjectLen(robj *o) {
int getDoubleFromObject(const robj *o, double *target) {
double value;
char *eptr;
if (o == NULL) {
value = 0;
} else {
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
if (sdsEncodedObject(o)) {
errno = 0;
value = strtod(o->ptr, &eptr);
if (sdslen(o->ptr) == 0 ||
isspace(((const char*)o->ptr)[0]) ||
(size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
(errno == ERANGE &&
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
isnan(value))
if (!string2d(o->ptr, sdslen(o->ptr), &value))
return C_ERR;
} else if (o->encoding == OBJ_ENCODING_INT) {
value = (long)o->ptr;

View File

@ -318,6 +318,7 @@ typedef struct RedisModuleDictIter RedisModuleDictIter;
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
typedef struct RedisModuleServerInfoData RedisModuleServerInfoData;
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
@ -502,6 +503,11 @@ int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ct
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value);
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value);
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value);
RedisModuleServerInfoData *REDISMODULE_API_FUNC(RedisModule_GetServerInfo)(RedisModuleCtx *ctx, const char *section);
void REDISMODULE_API_FUNC(RedisModule_FreeServerInfo)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ServerInfoGetField)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field);
long long REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldNumerical)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field, int *out_err);
double REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldDouble)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field, int *out_err);
int REDISMODULE_API_FUNC(RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback);
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClientOnKeys)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata);
void REDISMODULE_API_FUNC(RedisModule_SignalKeyAsReady)(RedisModuleCtx *ctx, RedisModuleString *key);
@ -704,6 +710,11 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(InfoAddFieldDouble);
REDISMODULE_GET_API(InfoAddFieldLongLong);
REDISMODULE_GET_API(InfoAddFieldULongLong);
REDISMODULE_GET_API(GetServerInfo);
REDISMODULE_GET_API(FreeServerInfo);
REDISMODULE_GET_API(ServerInfoGetField);
REDISMODULE_GET_API(ServerInfoGetFieldNumerical);
REDISMODULE_GET_API(ServerInfoGetFieldDouble);
REDISMODULE_GET_API(GetClientInfoById);
REDISMODULE_GET_API(SubscribeToServerEvent);
REDISMODULE_GET_API(BlockClientOnKeys);

View File

@ -3909,7 +3909,7 @@ void bytesToHuman(char *s, unsigned long long n) {
/* 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(char *section) {
sds genRedisInfoString(const char *section) {
sds info = sdsempty();
time_t uptime = server.unixtime-server.stat_starttime;
int j;

View File

@ -1600,7 +1600,7 @@ void ModuleForkDoneHandler(int exitcode, int bysignal);
int TerminateModuleForkChild(int child_pid, int wait);
ssize_t rdbSaveModulesAux(rio *rdb, int when);
int moduleAllDatatypesHandleErrors();
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections);
sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int sections);
void moduleFireServerEvent(uint64_t eid, int subid, void *data);
int moduleTryServeClientBlockedOnKey(client *c, robj *key);
void moduleUnblockClient(client *c);
@ -2409,7 +2409,7 @@ void _serverPanic(const char *file, int line, const char *msg, ...);
void bugReportStart(void);
void serverLogObjectDebugInfo(const robj *o);
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
sds genRedisInfoString(char *section);
sds genRedisInfoString(const char *section);
sds genModulesInfoString(sds info);
void enableWatchdog(int period);
void disableWatchdog(void);

View File

@ -468,6 +468,27 @@ int string2ld(const char *s, size_t slen, long double *dp) {
return 1;
}
/* Convert a string into a double. Returns 1 if the string could be parsed
* into a (non-overflowing) double, 0 otherwise. The value will be set to
* the parsed value when appropriate.
*
* Note that this function demands that the string strictly represents
* a double: no spaces or other characters before or after the string
* representing the number are accepted. */
int string2d(const char *s, size_t slen, double *dp) {
errno = 0;
char *eptr;
*dp = strtod(s, &eptr);
if (slen == 0 ||
isspace(((const char*)s)[0]) ||
(size_t)(eptr-(char*)s) != slen ||
(errno == ERANGE &&
(*dp == HUGE_VAL || *dp == -HUGE_VAL || *dp == 0)) ||
isnan(*dp))
return 0;
return 1;
}
/* Convert a double to a string representation. Returns the number of bytes
* required. The representation should always be parsable by strtod(3).
* This function does not support human-friendly formatting like ld2string

View File

@ -48,6 +48,7 @@ int ll2string(char *s, size_t len, long long value);
int string2ll(const char *s, size_t slen, long long *value);
int string2l(const char *s, size_t slen, long *value);
int string2ld(const char *s, size_t slen, long double *dp);
int string2d(const char *s, size_t slen, double *dp);
int d2string(char *buf, size_t len, double value);
int ld2string(char *buf, size_t len, long double value, int humanfriendly);
sds getAbsolutePath(char *filename);

View File

@ -29,6 +29,51 @@ void InfoFunc(RedisModuleInfoCtx *ctx, int for_crash_report) {
}
int info_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, char field_type)
{
if (argc != 3 && argc != 4) {
RedisModule_WrongArity(ctx);
return REDISMODULE_OK;
}
int err = REDISMODULE_OK;
const char *section, *field;
section = RedisModule_StringPtrLen(argv[1], NULL);
field = RedisModule_StringPtrLen(argv[2], NULL);
RedisModuleServerInfoData *info = RedisModule_GetServerInfo(ctx, section);
if (field_type=='i') {
long long ll = RedisModule_ServerInfoGetFieldNumerical(ctx, info, field, &err);
if (err==REDISMODULE_OK)
RedisModule_ReplyWithLongLong(ctx, ll);
} else if (field_type=='d') {
double d = RedisModule_ServerInfoGetFieldDouble(ctx, info, field, &err);
if (err==REDISMODULE_OK)
RedisModule_ReplyWithDouble(ctx, d);
} else {
RedisModuleString *str = RedisModule_ServerInfoGetField(ctx, info, field);
if (str) {
RedisModule_ReplyWithString(ctx, str);
RedisModule_FreeString(ctx, str);
} else
err=REDISMODULE_ERR;
}
if (err!=REDISMODULE_OK)
RedisModule_ReplyWithError(ctx, "not found");
RedisModule_FreeServerInfo(ctx, info);
return REDISMODULE_OK;
}
int info_gets(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return info_get(ctx, argv, argc, 's');
}
int info_geti(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return info_get(ctx, argv, argc, 'i');
}
int info_getd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return info_get(ctx, argv, argc, 'd');
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
@ -37,5 +82,12 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_RegisterInfoFunc(ctx, InfoFunc) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"info.gets", info_gets,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"info.geti", info_geti,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"info.getd", info_getd,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}

View File

@ -10,6 +10,27 @@ proc field {info property} {
start_server {tags {"modules"}} {
r module load $testmodule log-key 0
test {module reading info} {
# check string, integer and float fields
assert_equal [r info.gets replication role] "master"
assert_equal [r info.geti stats expired_keys] 0
assert_equal [r info.getd stats expired_stale_perc] 0
# the above are always 0, try module info that is non-zero
assert_equal [r info.geti infotest_italian infotest_due] 2
set tre [r info.getd infotest_italian infotest_tre]
assert {$tre > 3.2 && $tre < 3.4 }
# search using the wrong section
catch { [r info.gets badname redis_version] } e
assert_match {*not found*} $e
# check that section filter works
assert { [string match "*usec_per_call*" [r info.gets all cmdstat_info.gets] ] }
catch { [r info.gets default cmdstat_info.gets] ] } e
assert_match {*not found*} $e
}
test {module info all} {
set info [r info all]
# info all does not contain modules