From 0f64080dcb9f44c923379f909aae82f6c2b2ed19 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 14 Jul 2015 17:15:37 +0200 Subject: [PATCH] DEBUG HTSTATS added. The command reports information about the hash table internal state representing the specified database ID. This can be used in order to investigate rehashings, memory usage issues and for other debugging purposes. --- src/debug.c | 21 ++++++++ src/dict.c | 130 ++++++++++++++++-------------------------------- src/dict.h | 2 +- src/redis-cli.c | 6 +-- 4 files changed, 67 insertions(+), 92 deletions(-) diff --git a/src/debug.c b/src/debug.c index b8dcf648..2acba149 100644 --- a/src/debug.c +++ b/src/debug.c @@ -425,6 +425,27 @@ void debugCommand(redisClient *c) { sizes = sdscatprintf(sizes,"dictentry:%d ", (int)sizeof(dictEntry)); sizes = sdscatprintf(sizes,"sdshdr:%d", (int)sizeof(struct sdshdr)); addReplyBulkSds(c,sizes); + } else if (!strcasecmp(c->argv[1]->ptr,"htstats") && c->argc == 3) { + long dbid; + sds stats = sdsempty(); + char buf[4096]; + + if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != REDIS_OK) + return; + if (dbid < 0 || dbid >= server.dbnum) { + addReplyError(c,"Out of range database"); + return; + } + + stats = sdscatprintf(stats,"[Dictionary HT]\n"); + dictGetStats(buf,sizeof(buf),server.db[dbid].dict); + stats = sdscat(stats,buf); + + stats = sdscatprintf(stats,"[Expires HT]\n"); + dictGetStats(buf,sizeof(buf),server.db[dbid].expires); + stats = sdscat(stats,buf); + + addReplyBulkSds(c,stats); } else if (!strcasecmp(c->argv[1]->ptr,"jemalloc") && c->argc == 3) { #if defined(USE_JEMALLOC) if (!strcasecmp(c->argv[2]->ptr, "info")) { diff --git a/src/dict.c b/src/dict.c index f728d381..06826275 100644 --- a/src/dict.c +++ b/src/dict.c @@ -1002,24 +1002,21 @@ void dictDisableResize(void) { dict_can_resize = 0; } -#if 0 - -/* The following is code that we don't use for Redis currently, but that is part -of the library. */ - -/* ----------------------- Debugging ------------------------*/ +/* ------------------------------- Debugging ---------------------------------*/ #define DICT_STATS_VECTLEN 50 -static void _dictPrintStatsHt(dictht *ht) { +size_t _dictGetStatsHt(char *buf, size_t bufsize, dictht *ht, int tableid) { unsigned long i, slots = 0, chainlen, maxchainlen = 0; unsigned long totchainlen = 0; unsigned long clvector[DICT_STATS_VECTLEN]; + size_t l = 0; if (ht->used == 0) { - printf("No stats available for empty dictionaries\n"); - return; + return snprintf(buf,bufsize, + "No stats available for empty dictionaries\n"); } + /* Compute stats. */ for (i = 0; i < DICT_STATS_VECTLEN; i++) clvector[i] = 0; for (i = 0; i < ht->size; i++) { dictEntry *he; @@ -1040,89 +1037,46 @@ static void _dictPrintStatsHt(dictht *ht) { if (chainlen > maxchainlen) maxchainlen = chainlen; totchainlen += chainlen; } - printf("Hash table stats:\n"); - printf(" table size: %ld\n", ht->size); - printf(" number of elements: %ld\n", ht->used); - printf(" different slots: %ld\n", slots); - printf(" max chain length: %ld\n", maxchainlen); - printf(" avg chain length (counted): %.02f\n", (float)totchainlen/slots); - printf(" avg chain length (computed): %.02f\n", (float)ht->used/slots); - printf(" Chain length distribution:\n"); + + /* Generate human readable stats. */ + l += snprintf(buf+l,bufsize-l, + "Hash table %d stats (%s):\n" + " table size: %ld\n" + " number of elements: %ld\n" + " different slots: %ld\n" + " max chain length: %ld\n" + " avg chain length (counted): %.02f\n" + " avg chain length (computed): %.02f\n" + " Chain length distribution:\n", + tableid, (tableid == 0) ? "main hash table" : "rehashing target", + ht->size, ht->used, slots, maxchainlen, + (float)totchainlen/slots, (float)ht->used/slots); + for (i = 0; i < DICT_STATS_VECTLEN-1; i++) { if (clvector[i] == 0) continue; - printf(" %s%ld: %ld (%.02f%%)\n",(i == DICT_STATS_VECTLEN-1)?">= ":"", i, clvector[i], ((float)clvector[i]/ht->size)*100); + if (l >= bufsize) break; + l += snprintf(buf+l,bufsize-l, + " %s%ld: %ld (%.02f%%)\n", + (i == DICT_STATS_VECTLEN-1)?">= ":"", + i, clvector[i], ((float)clvector[i]/ht->size)*100); } + + /* Unlike snprintf(), teturn the number of characters actually written. */ + if (bufsize) buf[bufsize-1] = '\0'; + return strlen(buf); } -void dictPrintStats(dict *d) { - _dictPrintStatsHt(&d->ht[0]); - if (dictIsRehashing(d)) { - printf("-- Rehashing into ht[1]:\n"); - _dictPrintStatsHt(&d->ht[1]); +void dictGetStats(char *buf, size_t bufsize, dict *d) { + size_t l; + char *orig_buf = buf; + size_t orig_bufsize = bufsize; + + l = _dictGetStatsHt(buf,bufsize,&d->ht[0],0); + buf += l; + bufsize -= l; + if (dictIsRehashing(d) && bufsize > 0) { + _dictGetStatsHt(buf,bufsize,&d->ht[1],1); } + /* Make sure there is a NULL term at the end. */ + if (orig_bufsize) orig_buf[orig_bufsize-1] = '\0'; } - -/* ----------------------- StringCopy Hash Table Type ------------------------*/ - -static unsigned int _dictStringCopyHTHashFunction(const void *key) -{ - return dictGenHashFunction(key, strlen(key)); -} - -static void *_dictStringDup(void *privdata, const void *key) -{ - int len = strlen(key); - char *copy = zmalloc(len+1); - DICT_NOTUSED(privdata); - - memcpy(copy, key, len); - copy[len] = '\0'; - return copy; -} - -static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1, - const void *key2) -{ - DICT_NOTUSED(privdata); - - return strcmp(key1, key2) == 0; -} - -static void _dictStringDestructor(void *privdata, void *key) -{ - DICT_NOTUSED(privdata); - - zfree(key); -} - -dictType dictTypeHeapStringCopyKey = { - _dictStringCopyHTHashFunction, /* hash function */ - _dictStringDup, /* key dup */ - NULL, /* val dup */ - _dictStringCopyHTKeyCompare, /* key compare */ - _dictStringDestructor, /* key destructor */ - NULL /* val destructor */ -}; - -/* This is like StringCopy but does not auto-duplicate the key. - * It's used for intepreter's shared strings. */ -dictType dictTypeHeapStrings = { - _dictStringCopyHTHashFunction, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - _dictStringCopyHTKeyCompare, /* key compare */ - _dictStringDestructor, /* key destructor */ - NULL /* val destructor */ -}; - -/* This is like StringCopy but also automatically handle dynamic - * allocated C strings as values. */ -dictType dictTypeHeapStringCopyKeyValue = { - _dictStringCopyHTHashFunction, /* hash function */ - _dictStringDup, /* key dup */ - _dictStringDup, /* val dup */ - _dictStringCopyHTKeyCompare, /* key compare */ - _dictStringDestructor, /* key destructor */ - _dictStringDestructor, /* val destructor */ -}; -#endif diff --git a/src/dict.h b/src/dict.h index 014d1821..e31daee2 100644 --- a/src/dict.h +++ b/src/dict.h @@ -165,7 +165,7 @@ dictEntry *dictNext(dictIterator *iter); void dictReleaseIterator(dictIterator *iter); dictEntry *dictGetRandomKey(dict *d); unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count); -void dictPrintStats(dict *d); +void dictGetStats(char *buf, size_t bufsize, dict *d); unsigned int dictGenHashFunction(const void *key, int len); unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len); void dictEmpty(dict *d, void(callback)(void*)); diff --git a/src/redis-cli.c b/src/redis-cli.c index 251e42fa..acf7c98b 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -644,9 +644,9 @@ static int cliSendCommand(int argc, char **argv, int repeat) { output_raw = 0; if (!strcasecmp(command,"info") || - (argc == 3 && !strcasecmp(command,"debug") && - (!strcasecmp(argv[1],"jemalloc") && - !strcasecmp(argv[2],"info"))) || + (argc >= 2 && !strcasecmp(command,"debug") && + (!strcasecmp(argv[1],"jemalloc") || + !strcasecmp(argv[1],"htstats"))) || (argc == 2 && !strcasecmp(command,"cluster") && (!strcasecmp(argv[1],"nodes") || !strcasecmp(argv[1],"info"))) ||