diff --git a/src/functions.c b/src/functions.c index 1a94bacc3..57fb7ec24 100644 --- a/src/functions.c +++ b/src/functions.c @@ -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; } diff --git a/tests/unit/functions.tcl b/tests/unit/functions.tcl index 118362d25..187f7cfc4 100644 --- a/tests/unit/functions.tcl +++ b/tests/unit/functions.tcl @@ -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}}} }