summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorantirez <antirez@gmail.com>2015-07-14 17:15:37 +0200
committerantirez <antirez@gmail.com>2015-07-14 17:15:37 +0200
commit0f64080dcb9f44c923379f909aae82f6c2b2ed19 (patch)
tree6980f140b6b835c71a2dcbbc3eaadbbcf31a392f
parent4c7ee0d5848ab12b9d2b18bca62cffcbfac0e885 (diff)
downloadredis-0f64080dcb9f44c923379f909aae82f6c2b2ed19.tar.gz
DEBUG HTSTATS <dbid> 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.
-rw-r--r--src/debug.c21
-rw-r--r--src/dict.c128
-rw-r--r--src/dict.h2
-rw-r--r--src/redis-cli.c6
4 files changed, 66 insertions, 91 deletions
diff --git a/src/debug.c b/src/debug.c
index b8dcf648e..2acba1495 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 f728d381e..068262757 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);
}
-}
-
-void dictPrintStats(dict *d) {
- _dictPrintStatsHt(&d->ht[0]);
- if (dictIsRehashing(d)) {
- printf("-- Rehashing into ht[1]:\n");
- _dictPrintStatsHt(&d->ht[1]);
- }
-}
-
-/* ----------------------- 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;
+ /* Unlike snprintf(), teturn the number of characters actually written. */
+ if (bufsize) buf[bufsize-1] = '\0';
+ return strlen(buf);
}
-static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1,
- const void *key2)
-{
- DICT_NOTUSED(privdata);
+void dictGetStats(char *buf, size_t bufsize, dict *d) {
+ size_t l;
+ char *orig_buf = buf;
+ size_t orig_bufsize = bufsize;
- return strcmp(key1, key2) == 0;
-}
-
-static void _dictStringDestructor(void *privdata, void *key)
-{
- DICT_NOTUSED(privdata);
-
- zfree(key);
+ 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';
}
-
-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 014d18212..e31daee2a 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 251e42fad..acf7c98b9 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"))) ||