Added engine stats to FUNCTION STATS command. (#10179)

Added the following statistics (per engine) to FUNCTION STATS command:
* number of functions
* number of libraries

Output example:
```
> FUNCTION stats
1) "running_script"
2) (nil)
3) "engines"
4) 1) "LUA"
   2) 1) "libraries_count"
      2) (integer) 1
      3) "functions_count"
      4) (integer) 1
```

To collect the stats, added a new dictionary to libraries_ctx that contains
for each engine, the engine statistics representing the current libraries_ctx.
Update the stats on:
1. Link library to libraries_ctx
2. Unlink library from libraries_ctx
3. Flushing libraries_ctx
This commit is contained in:
Meir Shpilraien (Spielrein) 2022-01-25 15:50:14 +02:00 committed by GitHub
parent d26453a3f8
commit 5a38ccc253
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 97 additions and 6 deletions

View File

@ -41,13 +41,20 @@ static size_t engine_cache_memory = 0;
/* Forward declaration */
static void engineFunctionDispose(dict *d, void *obj);
static void engineStatsDispose(dict *d, void *obj);
static void engineLibraryDispose(dict *d, void *obj);
static int functionsVerifyName(sds name);
typedef struct functionsLibEngineStats {
size_t n_lib;
size_t n_functions;
} functionsLibEngineStats;
struct functionsLibCtx {
dict *libraries; /* Function name -> Function object that can be used to run the function */
dict *functions; /* Function name -> Function object that can be used to run the function */
size_t cache_memory /* Overhead memory (structs, dictionaries, ..) used by all the functions */;
dict *libraries; /* Library name -> Library object */
dict *functions; /* Function name -> Function object that can be used to run the function */
size_t cache_memory; /* Overhead memory (structs, dictionaries, ..) used by all the functions */
dict *engines_stats; /* Per engine statistics */
};
dictType engineDictType = {
@ -70,6 +77,16 @@ dictType functionDictType = {
NULL /* allow to expand */
};
dictType engineStatsDictType = {
dictSdsCaseHash, /* hash function */
dictSdsDup, /* key dup */
NULL, /* val dup */
dictSdsKeyCaseCompare,/* key compare */
dictSdsDestructor, /* key destructor */
engineStatsDispose, /* val destructor */
NULL /* allow to expand */
};
dictType libraryFunctionDictType = {
dictSdsHash, /* hash function */
dictSdsDup, /* key dup */
@ -111,6 +128,12 @@ static size_t libraryMallocSize(functionLibInfo *li) {
+ sdsZmallocSize(li->code);
}
static void engineStatsDispose(dict *d, void *obj) {
UNUSED(d);
functionsLibEngineStats *stats = obj;
zfree(stats);
}
/* Dispose function memory */
static void engineFunctionDispose(dict *d, void *obj) {
UNUSED(d);
@ -147,6 +170,14 @@ static void engineLibraryDispose(dict *d, void *obj) {
void functionsLibCtxClear(functionsLibCtx *lib_ctx) {
dictEmpty(lib_ctx->functions, NULL);
dictEmpty(lib_ctx->libraries, NULL);
dictIterator *iter = dictGetIterator(lib_ctx->engines_stats);
dictEntry *entry = NULL;
while ((entry = dictNext(iter))) {
functionsLibEngineStats *stats = dictGetVal(entry);
stats->n_functions = 0;
stats->n_lib = 0;
}
dictReleaseIterator(iter);
curr_functions_lib_ctx->cache_memory = 0;
}
@ -165,6 +196,7 @@ void functionsLibCtxFree(functionsLibCtx *functions_lib_ctx) {
functionsLibCtxClear(functions_lib_ctx);
dictRelease(functions_lib_ctx->functions);
dictRelease(functions_lib_ctx->libraries);
dictRelease(functions_lib_ctx->engines_stats);
zfree(functions_lib_ctx);
}
@ -185,6 +217,15 @@ functionsLibCtx* functionsLibCtxCreate() {
functionsLibCtx *ret = zmalloc(sizeof(functionsLibCtx));
ret->libraries = dictCreate(&librariesDictType);
ret->functions = dictCreate(&functionDictType);
ret->engines_stats = dictCreate(&engineStatsDictType);
dictIterator *iter = dictGetIterator(engines);
dictEntry *entry = NULL;
while ((entry = dictNext(iter))) {
engineInfo *ei = dictGetVal(entry);
functionsLibEngineStats *stats = zcalloc(sizeof(*stats));
dictAdd(ret->engines_stats, ei->name, stats);
}
dictReleaseIterator(iter);
ret->cache_memory = 0;
return ret;
}
@ -250,6 +291,12 @@ static void libraryUnlink(functionsLibCtx *lib_ctx, functionLibInfo* li) {
dictSetVal(lib_ctx->libraries, entry, NULL);
dictFreeUnlinkedEntry(lib_ctx->libraries, entry);
lib_ctx->cache_memory += libraryMallocSize(li);
/* update stats */
functionsLibEngineStats *stats = dictFetchValue(lib_ctx->engines_stats, li->ei->name);
serverAssert(stats);
stats->n_lib--;
stats->n_functions -= dictSize(li->functions);
}
static void libraryLink(functionsLibCtx *lib_ctx, functionLibInfo* li) {
@ -264,6 +311,12 @@ static void libraryLink(functionsLibCtx *lib_ctx, functionLibInfo* li) {
dictAdd(lib_ctx->libraries, li->name, li);
lib_ctx->cache_memory += libraryMallocSize(li);
/* update stats */
functionsLibEngineStats *stats = dictFetchValue(lib_ctx->engines_stats, li->ei->name);
serverAssert(stats);
stats->n_lib++;
stats->n_functions += dictSize(li->functions);
}
/* Takes all libraries from lib_ctx_src and add to lib_ctx_dst.
@ -401,12 +454,18 @@ void functionStatsCommand(client *c) {
}
addReplyBulkCString(c, "engines");
addReplyArrayLen(c, dictSize(engines));
addReplyMapLen(c, dictSize(engines));
dictIterator *iter = dictGetIterator(engines);
dictEntry *entry = NULL;
while ((entry = dictNext(iter))) {
engineInfo *ei = dictGetVal(entry);
addReplyBulkCString(c, ei->name);
addReplyMapLen(c, 2);
functionsLibEngineStats *e_stats = dictFetchValue(curr_functions_lib_ctx->engines_stats, ei->name);
addReplyBulkCString(c, "libraries_count");
addReplyLongLong(c, e_stats->n_lib);
addReplyBulkCString(c, "functions_count");
addReplyLongLong(c, e_stats->n_functions);
}
dictReleaseIterator(iter);
}
@ -979,11 +1038,13 @@ size_t functionsLibCtxfunctionsLen(functionsLibCtx *functions_ctx) {
* Should be called once on server initialization */
int functionsInit() {
engines = dictCreate(&engineDictType);
curr_functions_lib_ctx = functionsLibCtxCreate();
if (luaEngineInitEngine() != C_OK) {
return C_ERR;
}
/* Must be initialized after engines initialization */
curr_functions_lib_ctx = functionsLibCtxCreate();
return C_OK;
}

View File

@ -245,7 +245,7 @@ start_server {tags {"scripting"}} {
after 200
catch {r ping} e
assert_match {BUSY*} $e
assert_match {running_script {name test command {fcall test 0} duration_ms *} engines LUA} [r FUNCTION STATS]
assert_match {running_script {name test command {fcall test 0} duration_ms *} engines {*}} [r FUNCTION STATS]
r function kill
after 200 ; # Give some time to Lua to call the hook again...
assert_equal [r ping] "PONG"
@ -1102,4 +1102,34 @@ start_server {tags {"scripting"}} {
catch {[r fcall f1 0]} e
assert_equal [r fcall get_version_v1 0] [r fcall get_version_v2 0]
}
test {FUNCTION - function stats} {
r FUNCTION FLUSH
r FUNCTION load lua test1 {
redis.register_function('f1', function() return 1 end)
redis.register_function('f2', function() return 1 end)
}
r FUNCTION load lua test2 {
redis.register_function('f3', function() return 1 end)
}
r function stats
} {running_script {} engines {LUA {libraries_count 2 functions_count 3}}}
test {FUNCTION - function stats reloaded correctly from rdb} {
r debug reload
r function stats
} {running_script {} engines {LUA {libraries_count 2 functions_count 3}}} {needs:debug}
test {FUNCTION - function stats delete library} {
r function delete test1
r function stats
} {running_script {} engines {LUA {libraries_count 1 functions_count 1}}}
test {FUNCTION - function stats cleaned after flush} {
r function flush
r function stats
} {running_script {} engines {LUA {libraries_count 0 functions_count 0}}}
}