I kind of feel lonely on my topic ;-)
Anyway, still in the same area, I've made prefix stats collection use the
same pattern with per thread statistics and global aggregation on request.
Those stats overhead shall then not be more than the other ones.
Which by the way lead me to a question:
Is it really useful to have per thread mutex? I mean, per construction, this
is only updated from one thread at a time, isn't it? The only concurrent
access is the global aggregation, in which case atomic primitives should
work perfectly (also I do agree, there is nothing very portable :-( ), or
even no locking at all could be a solution, I mean aggregation may not see
the "right" values because of cache(s) and/or reordering stuffs but they
would'nt be "so far" from the true, which is already what stats command are
answering considering the thoughput usually going through memcached servers
...
cheers,
---
Jean-Charles


On Sun, May 31, 2009 at 17:26, Jean-Charles Redoutey
<[email protected]>wrote:

> I have also added the bytes in/out for data content per prefix.
> cheers,
> ---
> Jean-Charles
>
>
> On Sat, May 30, 2009 at 18:26, Jean-Charles Redoutey <
> [email protected]> wrote:
>
>> Hi evreyone,
>>
>> I just found out the "stats detail" command, which is not far from a point
>> I raised some time ago about more detailed statistics :-)
>> However, in my use, the keys are using a fixed size prefix without
>> delimiter so the -D option is pretty useless :-(
>> So I have just added in what I hope a seamless way the support for fixed
>> sized prefix.
>>
>> the patch is attached,
>>
>> cheers,
>> Jean-Charles
>>
>>
>>
>
diff --git a/memcached.c b/memcached.c
index 98578c4..6cc40a2 100644
--- a/memcached.c
+++ b/memcached.c
@@ -158,7 +158,6 @@ static void stats_init(void) {
        like 'settings.oldest_live' which act as booleans as well as
        values are now false in boolean context... */
     process_started = time(0) - 2;
-    stats_prefix_init();
 }
 
 static void stats_reset(void) {
@@ -166,7 +165,6 @@ static void stats_reset(void) {
     stats.total_items = stats.total_conns = 0;
     stats.evictions = 0;
     stats.listen_disabled_num = 0;
-    stats_prefix_clear();
     STATS_UNLOCK();
     threadlocal_stats_reset();
     item_stats_reset();
@@ -189,6 +187,7 @@ static void settings_init(void) {
     settings.chunk_size = 48;         /* space for a modest key and value */
     settings.num_threads = 4 + 1;     /* N workers + 1 dispatcher */
     settings.prefix_delimiter = ':';
+    settings.prefix_size = 0;
     settings.detail_enabled = 0;
     settings.reqs_per_event = 20;
     settings.backlog = 1024;
@@ -1219,7 +1218,7 @@ static void process_bin_get(conn *c) {
     }
 
     if (settings.detail_enabled) {
-        stats_prefix_record_get(key, nkey, NULL != it);
+        stats_prefix_record_get(&c->thread->stats, key, nkey, NULL != it, NULL 
!= it ? it->nbytes-2 : 0);
     }
 }
 
@@ -1581,7 +1580,7 @@ static void process_bin_update(conn *c) {
     }
 
     if (settings.detail_enabled) {
-        stats_prefix_record_set(key, nkey);
+        stats_prefix_record_set(&c->thread->stats, key, nkey, vlen);
     }
 
     it = item_alloc(key, nkey, req->message.body.flags,
@@ -1653,7 +1652,7 @@ static void process_bin_append_prepend(conn *c) {
     }
 
     if (settings.detail_enabled) {
-        stats_prefix_record_set(key, nkey);
+        stats_prefix_record_set(&c->thread->stats, key, nkey, vlen);
     }
 
     it = item_alloc(key, nkey, 0, 0, vlen+2);
@@ -1724,7 +1723,7 @@ static void process_bin_delete(conn *c) {
     }
 
     if (settings.detail_enabled) {
-        stats_prefix_record_delete(key, nkey);
+        stats_prefix_record_delete(&c->thread->stats, key, nkey);
     }
 
     it = item_get(key, nkey);
@@ -2136,6 +2135,7 @@ static void process_stat_settings(ADD_STAT add_stats, 
void *c) {
     APPEND_STAT("chunk_size", "%d", settings.chunk_size);
     APPEND_STAT("num_threads", "%d", settings.num_threads);
     APPEND_STAT("stat_key_prefix", "%c", settings.prefix_delimiter);
+    APPEND_STAT("stat_key_prefix_size", "%d", settings.prefix_size);
     APPEND_STAT("detail_enabled", "%s",
                 settings.detail_enabled ? "yes" : "no");
     APPEND_STAT("reqs_per_event", "%d", settings.reqs_per_event);
@@ -2252,7 +2252,7 @@ static inline void process_get_command(conn *c, token_t 
*tokens, size_t ntokens,
             stats_get_cmds++;
             it = item_get(key, nkey);
             if (settings.detail_enabled) {
-                stats_prefix_record_get(key, nkey, NULL != it);
+                stats_prefix_record_get(&c->thread->stats, key, nkey, NULL != 
it, NULL != it ? it->nbytes : 0);
             }
             if (it) {
                 if (i >= c->isize) {
@@ -2435,7 +2435,7 @@ static void process_update_command(conn *c, token_t 
*tokens, const size_t ntoken
     }
 
     if (settings.detail_enabled) {
-        stats_prefix_record_set(key, nkey);
+        stats_prefix_record_set(&c->thread->stats, key, nkey, vlen+2);
     }
 
     it = item_alloc(key, nkey, flags, realtime(exptime), vlen+2);
@@ -2596,7 +2596,7 @@ static void process_delete_command(conn *c, token_t 
*tokens, const size_t ntoken
     }
 
     if (settings.detail_enabled) {
-        stats_prefix_record_delete(key, nkey);
+        stats_prefix_record_delete(&c->thread->stats, key, nkey);
     }
 
     it = item_get(key, nkey);
@@ -3717,7 +3717,9 @@ static void usage(void) {
 #endif
            );
 
-    printf("-D <char>     Use <char> as the delimiter between key prefixes and 
IDs.\n"
+    printf("-D <char>|[len]  Use <char> as the delimiter between key prefixes 
and IDs.\n"
+           "              If the given value is surrounded by [], this is 
interpreted\n"
+           "              as a fixed size prefix length. \n"
            "              This is used for per-prefix stats reporting. The 
default is\n"
            "              \":\" (colon). If this option is specified, stats 
collection\n"
            "              is turned on automatically; if not, then it may be 
turned on\n"
@@ -4024,7 +4026,17 @@ int main (int argc, char **argv) {
                 fprintf(stderr, "No delimiter specified\n");
                 return 1;
             }
-            settings.prefix_delimiter = optarg[0];
+            if(optarg[1] != 0 && (optarg[0] == '[' || optarg[0] == '_')) /* 
looks like a prefix length, '_' is to allow bypassing perl issue with '['*/
+            {
+              settings.prefix_size = atoi(optarg+1);
+              if (settings.prefix_size == 0) {
+                  fprintf(stderr, "Prefix size must be greater than 0\n");
+                  return 1;
+              }
+            }
+            else {
+              settings.prefix_delimiter = optarg[0]; /* single character 
delimiter */
+            }
             settings.detail_enabled = 1;
             break;
         case 'L' :
diff --git a/memcached.h b/memcached.h
index 2764b52..676844c 100644
--- a/memcached.h
+++ b/memcached.h
@@ -194,6 +194,33 @@ struct slab_stats {
 };
 
 /**
+ * Prefix detailed stats
+ * Stats are tracked on the basis of key prefixes. This is a simple
+ * fixed-size hash of prefixes; we run the prefixes through the same
+ * CRC function used by the cache hashtable.
+ */
+
+struct _prefix_stats {
+    char                    *prefix;
+    size_t                  prefix_len;
+    uint64_t                num_gets;
+    uint64_t                num_sets;
+    uint64_t                num_deletes;
+    uint64_t                num_hits;
+    uint64_t                bytes_in;
+    uint64_t                bytes_out;
+    struct _prefix_stats    *next;
+};
+
+#define PREFIX_HASH_SIZE 256
+
+struct _prefix_stats_holder {
+    int                   num_prefixes;
+    int                   total_prefix_size;
+    struct _prefix_stats  *prefix_stats[PREFIX_HASH_SIZE];
+};
+
+/**
  * Stats stored per-thread.
  */
 struct thread_stats {
@@ -208,8 +235,10 @@ struct thread_stats {
     uint64_t          bytes_written;
     uint64_t          flush_cmds;
     struct slab_stats slab_stats[MAX_NUMBER_OF_SLAB_CLASSES];
+    struct _prefix_stats_holder prefix_stats_holder;
 };
 
+
 /**
  * Global stats.
  */
@@ -252,6 +281,7 @@ struct settings {
     int chunk_size;
     int num_threads;        /* number of libevent threads to run */
     char prefix_delimiter;  /* character that marks a key prefix (for stats) */
+    int prefix_size;        /* prefix length if fixed size prefix (used rather 
than prefix_delimiter if not null)*/
     int detail_enabled;     /* nonzero if we're collecting detailed stats */
     int reqs_per_event;     /* Maximum number of io to process on each
                                io-event. */
@@ -452,6 +482,7 @@ void STATS_UNLOCK(void);
 void threadlocal_stats_reset(void);
 void threadlocal_stats_aggregate(struct thread_stats *stats);
 void slab_stats_aggregate(struct thread_stats *stats, struct slab_stats *out);
+void detail_stats_aggregate(struct _prefix_stats_holder* stats_holder);
 
 /* Stat processing functions */
 void append_stat(const char *name, ADD_STAT add_stats, conn *c,
diff --git a/stats.c b/stats.c
index 636107e..1e2f3cd 100644
--- a/stats.c
+++ b/stats.c
@@ -13,50 +13,27 @@
 #include <string.h>
 #include <assert.h>
 
-/*
- * Stats are tracked on the basis of key prefixes. This is a simple
- * fixed-size hash of prefixes; we run the prefixes through the same
- * CRC function used by the cache hashtable.
- */
+
 typedef struct _prefix_stats PREFIX_STATS;
-struct _prefix_stats {
-    char         *prefix;
-    size_t        prefix_len;
-    uint64_t      num_gets;
-    uint64_t      num_sets;
-    uint64_t      num_deletes;
-    uint64_t      num_hits;
-    PREFIX_STATS *next;
-};
-
-#define PREFIX_HASH_SIZE 256
-
-static PREFIX_STATS *prefix_stats[PREFIX_HASH_SIZE];
-static int num_prefixes = 0;
-static int total_prefix_size = 0;
-
-void stats_prefix_init() {
-    memset(prefix_stats, 0, sizeof(prefix_stats));
-}
 
 /*
- * Cleans up all our previously collected stats. NOTE: the stats lock is
+ * Cleans up all our previously collected stats. NOTE: the relevant lock is
  * assumed to be held when this is called.
  */
-void stats_prefix_clear() {
+void stats_prefix_clear(struct _prefix_stats_holder *stats_holder) {
     int i;
 
     for (i = 0; i < PREFIX_HASH_SIZE; i++) {
         PREFIX_STATS *cur, *next;
-        for (cur = prefix_stats[i]; cur != NULL; cur = next) {
+        for (cur = stats_holder->prefix_stats[i]; cur != NULL; cur = next) {
             next = cur->next;
             free(cur->prefix);
             free(cur);
         }
-        prefix_stats[i] = NULL;
+        stats_holder->prefix_stats[i] = NULL;
     }
-    num_prefixes = 0;
-    total_prefix_size = 0;
+    stats_holder->num_prefixes = 0;
+    stats_holder->total_prefix_size = 0;
 }
 
 /*
@@ -64,20 +41,25 @@ void stats_prefix_clear() {
  * in the list.
  */
 /*...@null@*/
-static PREFIX_STATS *stats_prefix_find(const char *key, const size_t nkey) {
+static PREFIX_STATS *stats_prefix_find(struct _prefix_stats_holder 
*stats_holder, const char *key, const size_t nkey) {
     PREFIX_STATS *pfs;
     uint32_t hashval;
     size_t length;
 
     assert(key != NULL);
 
-    for (length = 0; key[length] != '\0' && length < nkey; length++)
-        if (key[length] == settings.prefix_delimiter)
-            break;
+    if( settings.prefix_size == 0) /* we are using prefix_delimiter */
+    {
+      for (length = 0; key[length] != '\0' && length < nkey; length++)
+          if (key[length] == settings.prefix_delimiter)
+              break;
+    } else { /* we are using fixed size prefix */
+      length = settings.prefix_size < nkey ? settings.prefix_size : nkey;
+    }
 
     hashval = hash(key, length, 0) % PREFIX_HASH_SIZE;
 
-    for (pfs = prefix_stats[hashval]; NULL != pfs; pfs = pfs->next) {
+    for (pfs = stats_holder->prefix_stats[hashval]; NULL != pfs; pfs = 
pfs->next) {
         if (strncmp(pfs->prefix, key, length) == 0)
             return pfs;
     }
@@ -99,11 +81,11 @@ static PREFIX_STATS *stats_prefix_find(const char *key, 
const size_t nkey) {
     pfs->prefix[length] = '\0';      /* because strncpy() sucks */
     pfs->prefix_len = length;
 
-    pfs->next = prefix_stats[hashval];
-    prefix_stats[hashval] = pfs;
+    pfs->next = stats_holder->prefix_stats[hashval];
+    stats_holder->prefix_stats[hashval] = pfs;
 
-    num_prefixes++;
-    total_prefix_size += length;
+    stats_holder->num_prefixes++;
+    stats_holder->total_prefix_size += length;
 
     return pfs;
 }
@@ -111,46 +93,72 @@ static PREFIX_STATS *stats_prefix_find(const char *key, 
const size_t nkey) {
 /*
  * Records a "get" of a key.
  */
-void stats_prefix_record_get(const char *key, const size_t nkey, const bool 
is_hit) {
+void stats_prefix_record_get(struct thread_stats *stats, const char *key, 
const size_t nkey, const bool is_hit, const size_t nbytes) {
     PREFIX_STATS *pfs;
 
-    STATS_LOCK();
-    pfs = stats_prefix_find(key, nkey);
+    pthread_mutex_lock(&stats->mutex);
+    pfs = stats_prefix_find(&stats->prefix_stats_holder, key, nkey);
     if (NULL != pfs) {
         pfs->num_gets++;
         if (is_hit) {
             pfs->num_hits++;
+            pfs->bytes_out += nbytes;
         }
     }
-    STATS_UNLOCK();
+    pthread_mutex_unlock(&stats->mutex);
 }
 
 /*
  * Records a "delete" of a key.
  */
-void stats_prefix_record_delete(const char *key, const size_t nkey) {
+void stats_prefix_record_delete(struct thread_stats *stats, const char *key, 
const size_t nkey) {
     PREFIX_STATS *pfs;
 
-    STATS_LOCK();
-    pfs = stats_prefix_find(key, nkey);
+    pthread_mutex_lock(&stats->mutex);
+    pfs = stats_prefix_find(&stats->prefix_stats_holder, key, nkey);
     if (NULL != pfs) {
         pfs->num_deletes++;
     }
-    STATS_UNLOCK();
+    pthread_mutex_unlock(&stats->mutex);
 }
 
 /*
  * Records a "set" of a key.
  */
-void stats_prefix_record_set(const char *key, const size_t nkey) {
+void stats_prefix_record_set(struct thread_stats *stats, const char *key, 
const size_t nkey, const size_t nbytes) {
     PREFIX_STATS *pfs;
 
-    STATS_LOCK();
-    pfs = stats_prefix_find(key, nkey);
+    pthread_mutex_lock(&stats->mutex);
+    pfs = stats_prefix_find(&stats->prefix_stats_holder, key, nkey);
     if (NULL != pfs) {
         pfs->num_sets++;
+        pfs->bytes_in += nbytes;
     }
-    STATS_UNLOCK();
+    pthread_mutex_unlock(&stats->mutex);
+}
+
+
+/*
+ * Sums the stats_holder content into the main one
+ */
+void stat_prefix_sum(struct _prefix_stats_holder *main_stats_holder, struct 
_prefix_stats_holder *stats_holder)
+{
+  PREFIX_STATS *pfs, *tmp_pfs;
+  int i;
+  for (i = 0; i < PREFIX_HASH_SIZE; i++) {
+      for (pfs = stats_holder->prefix_stats[i]; NULL != pfs; pfs = pfs->next) {
+        /* use the standard prefix_find to find or create corresponding 
aggregated prefix */
+        tmp_pfs = stats_prefix_find(main_stats_holder, pfs->prefix, 
pfs->prefix_len);
+        if(tmp_pfs) {
+          tmp_pfs->num_gets += pfs->num_gets;
+          tmp_pfs->num_sets += pfs->num_sets;
+          tmp_pfs->num_deletes += pfs->num_deletes;
+          tmp_pfs->num_hits += pfs->num_hits;
+          tmp_pfs->bytes_in += pfs->bytes_in;
+          tmp_pfs->bytes_out += pfs->bytes_out;
+        }
+    }
+  }
 }
 
 /*
@@ -158,43 +166,47 @@ void stats_prefix_record_set(const char *key, const 
size_t nkey) {
  */
 /*...@null@*/
 char *stats_prefix_dump(int *length) {
-    const char *format = "PREFIX %s get %llu hit %llu set %llu del %llu\r\n";
+    const char *format = "PREFIX %s get %llu hit %llu set %llu del %llu in 
%llu out %llu\r\n";
     PREFIX_STATS *pfs;
     char *buf;
     int i, pos;
     size_t size = 0, written = 0, total_written = 0;
 
+    /* Create a temporary structure holder */
+    struct _prefix_stats_holder temporary_stats_holder;
+    memset(&temporary_stats_holder, 0, sizeof(struct _prefix_stats_holder));
+
+    /* Aggregate data from all the threads into this temporary structure */
+    detail_stats_aggregate(&temporary_stats_holder);
+
     /*
      * Figure out how big the buffer needs to be. This is the sum of the
      * lengths of the prefixes themselves, plus the size of one copy of
      * the per-prefix output with 20-digit values for all the counts,
      * plus space for the "END" at the end.
      */
-    STATS_LOCK();
-    size = strlen(format) + total_prefix_size +
-           num_prefixes * (strlen(format) - 2 /* %s */
-                           + 4 * (20 - 4)) /* %llu replaced by 20-digit num */
-                           + sizeof("END\r\n");
+    size = strlen(format) + temporary_stats_holder.total_prefix_size +
+           temporary_stats_holder.num_prefixes * (strlen(format) - 2 /* %s */
+           + 6 * (20 - 4)) /* %llu replaced by 20-digit num */
+           + sizeof("END\r\n");
     buf = malloc(size);
     if (NULL == buf) {
         perror("Can't allocate stats response: malloc");
-        STATS_UNLOCK();
         return NULL;
     }
 
     pos = 0;
     for (i = 0; i < PREFIX_HASH_SIZE; i++) {
-        for (pfs = prefix_stats[i]; NULL != pfs; pfs = pfs->next) {
+        for (pfs = temporary_stats_holder.prefix_stats[i]; NULL != pfs; pfs = 
pfs->next) {
             written = snprintf(buf + pos, size-pos, format,
                            pfs->prefix, pfs->num_gets, pfs->num_hits,
-                           pfs->num_sets, pfs->num_deletes);
+                           pfs->num_sets, pfs->num_deletes, pfs->bytes_in, 
pfs->bytes_out);
             pos += written;
             total_written += written;
             assert(total_written < size);
         }
     }
 
-    STATS_UNLOCK();
     memcpy(buf + pos, "END\r\n", 6);
 
     *length = pos + 5;
diff --git a/stats.h b/stats.h
index 4a27ae9..499b6e8 100644
--- a/stats.h
+++ b/stats.h
@@ -1,8 +1,9 @@
 /* stats */
-void stats_prefix_init(void);
-void stats_prefix_clear(void);
-void stats_prefix_record_get(const char *key, const size_t nkey, const bool 
is_hit);
-void stats_prefix_record_delete(const char *key, const size_t nkey);
-void stats_prefix_record_set(const char *key, const size_t nkey);
+/* Those are per thread methods */
+void stats_prefix_clear(struct _prefix_stats_holder *stats_holder);
+void stats_prefix_record_get(struct thread_stats *stats, const char *key, 
const size_t nkey, const bool is_hit, const size_t nbytes);
+void stats_prefix_record_delete(struct thread_stats *stats, const char *key, 
const size_t nkey);
+void stats_prefix_record_set(struct thread_stats *stats, const char *key, 
const size_t nkey, const size_t nbytes);
 /*...@null@*/
 char *stats_prefix_dump(int *length);
+void stat_prefix_sum(struct _prefix_stats_holder *main_stats_holder, struct 
_prefix_stats_holder *stats_holder);
diff --git a/t/stats-detail-fixed-size.t b/t/stats-detail-fixed-size.t
new file mode 100755
index 0000000..f77e1ca
--- /dev/null
+++ b/t/stats-detail-fixed-size.t
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 24;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached('-D _3');
+my $sock = $server->sock;
+my $expire;
+
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "END\r\n", "verified empty stats at start");
+
+print $sock "stats detail on\r\n";
+is(scalar <$sock>, "OK\r\n", "detail collection turned on");
+
+print $sock "set foo:123 0 0 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 0 hit 0 set 1 del 0 in 8 out 0\r\n", 
"details after set");
+is(scalar <$sock>, "END\r\n", "end of details");
+
+mem_get_is($sock, "foo:123", "fooval");
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 1 del 0 in 8 out 8\r\n", 
"details after get with hit");
+is(scalar <$sock>, "END\r\n", "end of details");
+
+mem_get_is($sock, "foo:124", undef);
+
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 2 hit 1 set 1 del 0 in 8 out 8\r\n", 
"details after get without hit");
+is(scalar <$sock>, "END\r\n", "end of details");
+
+print $sock "delete foo:125 0\r\n";
+is(scalar <$sock>, "NOT_FOUND\r\n", "sent delete command");
+
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 2 hit 1 set 1 del 1 in 8 out 8\r\n", 
"details after delete");
+is(scalar <$sock>, "END\r\n", "end of details");
+
+print $sock "stats reset\r\n";
+is(scalar <$sock>, "RESET\r\n", "stats cleared");
+
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "END\r\n", "empty stats after clear");
+
+mem_get_is($sock, "foo:123", "fooval");
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 0 del 0 in 0 out 8\r\n", 
"details after clear and get");
+is(scalar <$sock>, "END\r\n", "end of details");
+
+print $sock "stats detail off\r\n";
+is(scalar <$sock>, "OK\r\n", "detail collection turned off");
+
+mem_get_is($sock, "foo:124", undef);
+
+mem_get_is($sock, "foo:123", "fooval");
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 0 del 0 in 0 out 8\r\n", 
"details after stats turned off");
+is(scalar <$sock>, "END\r\n", "end of details");
diff --git a/t/stats-detail.t b/t/stats-detail.t
old mode 100644
new mode 100755
index 4bb7249..3d9337c
--- a/t/stats-detail.t
+++ b/t/stats-detail.t
@@ -20,25 +20,25 @@ print $sock "set foo:123 0 0 6\r\nfooval\r\n";
 is(scalar <$sock>, "STORED\r\n", "stored foo");
 
 print $sock "stats detail dump\r\n";
-is(scalar <$sock>, "PREFIX foo get 0 hit 0 set 1 del 0\r\n", "details after 
set");
+is(scalar <$sock>, "PREFIX foo get 0 hit 0 set 1 del 0 in 8 out 0\r\n", 
"details after set");
 is(scalar <$sock>, "END\r\n", "end of details");
 
 mem_get_is($sock, "foo:123", "fooval");
 print $sock "stats detail dump\r\n";
-is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 1 del 0\r\n", "details after 
get with hit");
+is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 1 del 0 in 8 out 8\r\n", 
"details after get with hit");
 is(scalar <$sock>, "END\r\n", "end of details");
 
 mem_get_is($sock, "foo:124", undef);
 
 print $sock "stats detail dump\r\n";
-is(scalar <$sock>, "PREFIX foo get 2 hit 1 set 1 del 0\r\n", "details after 
get without hit");
+is(scalar <$sock>, "PREFIX foo get 2 hit 1 set 1 del 0 in 8 out 8\r\n", 
"details after get without hit");
 is(scalar <$sock>, "END\r\n", "end of details");
 
 print $sock "delete foo:125 0\r\n";
 is(scalar <$sock>, "NOT_FOUND\r\n", "sent delete command");
 
 print $sock "stats detail dump\r\n";
-is(scalar <$sock>, "PREFIX foo get 2 hit 1 set 1 del 1\r\n", "details after 
delete");
+is(scalar <$sock>, "PREFIX foo get 2 hit 1 set 1 del 1 in 8 out 8\r\n", 
"details after delete");
 is(scalar <$sock>, "END\r\n", "end of details");
 
 print $sock "stats reset\r\n";
@@ -49,7 +49,7 @@ is(scalar <$sock>, "END\r\n", "empty stats after clear");
 
 mem_get_is($sock, "foo:123", "fooval");
 print $sock "stats detail dump\r\n";
-is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 0 del 0\r\n", "details after 
clear and get");
+is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 0 del 0 in 0 out 8\r\n", 
"details after clear and get");
 is(scalar <$sock>, "END\r\n", "end of details");
 
 print $sock "stats detail off\r\n";
@@ -59,5 +59,5 @@ mem_get_is($sock, "foo:124", undef);
 
 mem_get_is($sock, "foo:123", "fooval");
 print $sock "stats detail dump\r\n";
-is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 0 del 0\r\n", "details after 
stats turned off");
+is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 0 del 0 in 0 out 8\r\n", 
"details after stats turned off");
 is(scalar <$sock>, "END\r\n", "end of details");
diff --git a/thread.c b/thread.c
index 66955cb..ce53788 100644
--- a/thread.c
+++ b/thread.c
@@ -496,6 +496,8 @@ void threadlocal_stats_reset(void) {
             threads[ii].stats.slab_stats[sid].cas_badval = 0;
         }
 
+        stats_prefix_clear(&threads[ii].stats.prefix_stats_holder);
+
         pthread_mutex_unlock(&threads[ii].stats.mutex);
     }
 }
@@ -572,6 +574,16 @@ void slab_stats_aggregate(struct thread_stats *stats, 
struct slab_stats *out) {
     }
 }
 
+void detail_stats_aggregate(struct _prefix_stats_holder* stats_holder)
+{
+  int t;
+  for (t = 0; t < settings.num_threads; ++t) {
+      pthread_mutex_lock(&threads[t].stats.mutex);
+      stat_prefix_sum(stats_holder, &threads[t].stats.prefix_stats_holder);
+      pthread_mutex_unlock(&threads[t].stats.mutex);
+  }
+}
+
 /*
  * Initializes the thread subsystem, creating various worker threads.
  *

Reply via email to