[memcached] sgrimm, r462: Add stats collection based on key prefix...

commits at code.sixapart.com commits at code.sixapart.com
Mon Mar 5 23:38:54 UTC 2007


Add stats collection based on key prefixes, which are assumed to
specify object types.  The delimiter character may be specified with
the -D command-line option. Stats collection may be turned on with the
"stats detail on" command (it is turned on by default if -D is used)
and turned off with "stats detail off". The current stats may be
dumped using "stats detail dump".


U   branches/multithreaded/server/ChangeLog
U   branches/multithreaded/server/Makefile.am
U   branches/multithreaded/server/doc/memcached.1
U   branches/multithreaded/server/memcached.c
U   branches/multithreaded/server/memcached.h
A   branches/multithreaded/server/stats.c
A   branches/multithreaded/server/t/stats-detail.t


Modified: branches/multithreaded/server/ChangeLog
===================================================================
--- branches/multithreaded/server/ChangeLog	2007-03-02 03:44:59 UTC (rev 461)
+++ branches/multithreaded/server/ChangeLog	2007-03-05 23:38:52 UTC (rev 462)
@@ -1,3 +1,10 @@
+2007-03-05
+	* Steven Grimm <sgrimm at facebook.com>: Per-object-type stats collection
+	  support. Specify the object type delimiter with the -D command line
+	  option. Turn stats gathering on and off with "stats detail on" and
+	  "stats detail off". Dump the per-object-type details with
+	  "stats detail dump".
+
 2007-03-01
 	* Steven Grimm <sgrimm at facebook.com>: Fix an off-by-one error in the
 	  multithreaded version's message passing code.

Modified: branches/multithreaded/server/Makefile.am
===================================================================
--- branches/multithreaded/server/Makefile.am	2007-03-02 03:44:59 UTC (rev 461)
+++ branches/multithreaded/server/Makefile.am	2007-03-05 23:38:52 UTC (rev 462)
@@ -1,6 +1,6 @@
 bin_PROGRAMS = memcached memcached-debug
 
-memcached_SOURCES = memcached.c slabs.c items.c memcached.h assoc.c thread.c
+memcached_SOURCES = memcached.c slabs.c items.c memcached.h assoc.c thread.c stats.c
 memcached_debug_SOURCES = $(memcached_SOURCES)
 memcached_CPPFLAGS = -DNDEBUG
 memcached_LDADD = @LIBOBJS@

Modified: branches/multithreaded/server/doc/memcached.1
===================================================================
--- branches/multithreaded/server/doc/memcached.1	2007-03-02 03:44:59 UTC (rev 461)
+++ branches/multithreaded/server/doc/memcached.1	2007-03-05 23:38:52 UTC (rev 462)
@@ -90,6 +90,12 @@
 meaningful if memcached was compiled with thread support enabled. It is 
 typically not useful to set this higher than the number of CPU cores on the
 memcached server.
+.TP
+.B \-D <char>
+Use <char> as the delimiter between key prefixes and IDs. This is used for
+per-prefix stats reporting. The default is ":" (colon). If this option is
+specified, stats collection is turned on automatically; if not, then it may
+be turned on by sending the "stats detail on" command to the server.
 .br
 .SH LICENSE
 The memcached daemon is copyright Danga Interactive and is distributed under 

Modified: branches/multithreaded/server/memcached.c
===================================================================
--- branches/multithreaded/server/memcached.c	2007-03-02 03:44:59 UTC (rev 461)
+++ branches/multithreaded/server/memcached.c	2007-03-05 23:38:52 UTC (rev 462)
@@ -98,6 +98,7 @@
        like 'settings.oldest_live' which act as booleans as well as
        values are now false in boolean context... */
     stats.started = time(0) - 2;
+    stats_prefix_init();
 }
 
 void stats_reset(void) {
@@ -105,6 +106,7 @@
     stats.total_items = stats.total_conns = 0;
     stats.get_cmds = stats.set_cmds = stats.get_hits = stats.get_misses = 0;
     stats.bytes_read = stats.bytes_written = 0;
+    stats_prefix_clear();
     STATS_UNLOCK();
 }
 
@@ -126,6 +128,8 @@
 #else
     settings.num_threads = 1;
 #endif
+    settings.prefix_delimiter = ':';
+    settings.detail_enabled = 0;
 }
 
 /*
@@ -718,6 +722,34 @@
     return ntokens;
 }
 
+inline void process_stats_detail(conn *c, const char *command) {
+    if (strcmp(command, "on") == 0) {
+        settings.detail_enabled = 1;
+        out_string(c, "OK");
+    }
+    else if (strcmp(command, "off") == 0) {
+        settings.detail_enabled = 0;
+        out_string(c, "OK");
+    }
+    else if (strcmp(command, "dump") == 0) {
+        int len;
+        char *stats = stats_prefix_dump(&len);
+        if (NULL != stats) {
+            c->write_and_free = stats;
+            c->wcurr = stats;
+            c->wbytes = len;
+            conn_set_state(c, conn_write);
+            c->write_and_go = conn_read;
+        }
+        else {
+            out_string(c, "SERVER_ERROR");
+        }
+    }
+    else {
+        out_string(c, "CLIENT_ERROR usage: stats detail on|off|dump");
+    }
+}
+
 void process_stat(conn *c, token_t* tokens, size_t ntokens) {
     rel_time_t now = current_time;
     char* command;
@@ -892,6 +924,14 @@
         return;
     }
 
+    if (strcmp(subcommand, "detail")==0) {
+        if (ntokens < 4)
+            process_stats_detail(c, "");  /* outputs the error message */
+        else
+            process_stats_detail(c, tokens[2].value);
+        return;
+    }
+
     if (strcmp(subcommand, "sizes")==0) {
         int bytes = 0;
         char *buf = item_stats_sizes(&bytes);
@@ -946,6 +986,9 @@
             stats.get_cmds++;
             STATS_UNLOCK();
             it = item_get(key, nkey);
+            if (settings.detail_enabled) {
+                stats_prefix_record_get(key, NULL != it);
+            }
             if (it) {
                 if (i >= c->isize) {
                     item **new_list = realloc(c->ilist, sizeof(item *)*c->isize*2);
@@ -1040,7 +1083,11 @@
         out_string(c, "CLIENT_ERROR bad command line format");
         return;
     }
-    
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_set(key);
+    }
+
     if (settings.managed) {
         int bucket = c->bucket;
         if (bucket == -1) {
@@ -1206,6 +1253,10 @@
         }
     }
 
+    if (settings.detail_enabled) {
+        stats_prefix_record_delete(key);
+    }
+
     it = item_get(key, nkey);
     if (it) {
         if (exptime == 0) {
@@ -2297,7 +2348,7 @@
     setbuf(stderr, NULL);
 
     /* process arguments */
-    while ((c = getopt(argc, argv, "bp:s:U:m:Mc:khirvdl:u:P:f:t:")) != -1) {
+    while ((c = getopt(argc, argv, "bp:s:U:m:Mc:khirvdl:u:P:f:t:D:")) != -1) {
         switch (c) {
         case 'U':
             settings.udpport = atoi(optarg);
@@ -2373,6 +2424,14 @@
                 return 1;
             }
             break;
+        case 'D':
+            if (! optarg || ! optarg[0]) {
+                fprintf(stderr, "No delimiter specified\n");
+                return 1;
+            }
+            settings.prefix_delimiter = optarg[0];
+            settings.detail_enabled = 1;
+            break;
         default:
             fprintf(stderr, "Illegal argument \"%c\"\n", c);
             return 1;

Modified: branches/multithreaded/server/memcached.h
===================================================================
--- branches/multithreaded/server/memcached.h	2007-03-02 03:44:59 UTC (rev 461)
+++ branches/multithreaded/server/memcached.h	2007-03-05 23:38:52 UTC (rev 462)
@@ -60,6 +60,8 @@
     double factor;          /* chunk size growth factor */
     int chunk_size;
     int num_threads;        /* number of libevent threads to run */
+    char prefix_delimiter;  /* character that marks a key prefix (for stats) */
+    int detail_enabled;     /* nonzero if we're collecting detailed stats */
 };
 
 extern struct stats stats;
@@ -267,6 +269,12 @@
 /* stats */
 void stats_reset(void);
 void stats_init(void);
+void stats_prefix_init(void);
+void stats_prefix_clear(void);
+void stats_prefix_record_get(char *key, int is_hit);
+void stats_prefix_record_delete(char *key);
+void stats_prefix_record_set(char *key);
+char *stats_prefix_dump(int *length);
 /* defaults */
 void settings_init(void);
 /* associative array */
@@ -275,6 +283,7 @@
 int assoc_insert(item *item);
 void assoc_delete(const char *key, size_t nkey);
 void do_assoc_move_next_bucket(void);
+uint32_t hash(const void *key, size_t len, uint32_t startval);
 
 void item_init(void);
 item *do_item_alloc(char *key, size_t nkey, int flags, rel_time_t exptime, int nbytes);

Added: branches/multithreaded/server/stats.c
===================================================================
--- branches/multithreaded/server/stats.c	2007-03-02 03:44:59 UTC (rev 461)
+++ branches/multithreaded/server/stats.c	2007-03-05 23:38:52 UTC (rev 462)
@@ -0,0 +1,359 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Detailed statistics management. For simple stats like total number of
+ * "get" requests, we use inline code in memcached.c and friends, but when
+ * stats detail mode is activated, the code here records more information.
+ *
+ * Author:
+ *   Steven Grimm <sgrimm at facebook.com>
+ *
+ * $Id$
+ */
+#include "memcached.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.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;
+    int                prefix_len;
+    unsigned long long num_gets;
+    unsigned long long num_sets;
+    unsigned long long num_deletes;
+    unsigned long long 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
+ * assumed to be held when this is called.
+ */
+void stats_prefix_clear() {
+    int i;
+    PREFIX_STATS *cur, *next;
+
+    for (i = 0; i < PREFIX_HASH_SIZE; i++) {
+        for (cur = prefix_stats[i]; cur != NULL; cur = next) {
+            next = cur->next;
+            free(cur->prefix);
+            free(cur);
+        }
+        prefix_stats[i] = NULL;
+    }
+    num_prefixes = 0;
+    total_prefix_size = 0;
+}
+
+/*
+ * Returns the stats structure for a prefix, creating it if it's not already
+ * in the list.
+ */
+static PREFIX_STATS *stats_prefix_find(char *key) {
+    PREFIX_STATS *pfs;
+    int hashval;
+    int length;
+
+    for (length = 0; key[length] != '\0'; length++)
+        if (key[length] == settings.prefix_delimiter)
+            break;
+
+    hashval = hash(key, length, 0) % PREFIX_HASH_SIZE;
+
+    for (pfs = prefix_stats[hashval]; NULL != pfs; pfs = pfs->next) {
+        if (! strncmp(pfs->prefix, key, length))
+            return pfs;
+    }
+
+    pfs = calloc(sizeof(PREFIX_STATS), 1);
+    if (NULL == pfs) {
+        perror("Can't allocate space for stats structure: calloc");
+        return NULL;
+    }
+
+    pfs->prefix = malloc(length + 1);
+    if (NULL == pfs->prefix) {
+        perror("Can't allocate space for copy of prefix: malloc");
+        free(pfs);
+        return NULL;
+    }
+
+    strncpy(pfs->prefix, key, length);
+    pfs->prefix[length] = '\0';      // because strncpy() sucks
+    pfs->prefix_len = length;
+
+    pfs->next = prefix_stats[hashval];
+    prefix_stats[hashval] = pfs;
+
+    num_prefixes++;
+    total_prefix_size += length;
+
+    return pfs;
+}
+
+/*
+ * Records a "get" of a key.
+ */
+void stats_prefix_record_get(char *key, int is_hit) {
+    PREFIX_STATS *pfs;
+
+    STATS_LOCK();
+    pfs = stats_prefix_find(key);
+    if (NULL != pfs) {
+        pfs->num_gets++;
+        if (is_hit) {
+            pfs->num_hits++;
+        }
+    }
+    STATS_UNLOCK();
+}
+
+/*
+ * Records a "delete" of a key.
+ */
+void stats_prefix_record_delete(char *key) {
+    PREFIX_STATS *pfs;
+    
+    STATS_LOCK();
+    pfs = stats_prefix_find(key);
+    if (NULL != pfs) {
+        pfs->num_deletes++;
+    }
+    STATS_UNLOCK();
+}
+
+/*
+ * Records a "set" of a key.
+ */
+void stats_prefix_record_set(char *key) {
+    PREFIX_STATS *pfs;
+    
+    STATS_LOCK();
+    pfs = stats_prefix_find(key);
+    if (NULL != pfs) {
+        pfs->num_sets++;
+    }
+    STATS_UNLOCK();
+}
+
+/*
+ * Returns stats in textual form suitable for writing to a client.
+ */
+char *stats_prefix_dump(int *length) {
+    char *format = "PREFIX %s get %llu hit %llu set %llu del %llu\r\n";
+    PREFIX_STATS *pfs;
+    char *buf;
+    int i, pos;
+    int size;
+
+    /*
+     * 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");
+    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) {
+            pos += sprintf(buf + pos, format, 
+                           pfs->prefix, pfs->num_gets, pfs->num_hits,
+                           pfs->num_sets, pfs->num_deletes);
+        }
+    }
+    
+    STATS_UNLOCK();
+    strcpy(buf + pos, "END\r\n");
+
+    *length = pos + 5;
+    return buf;
+}
+
+
+#ifdef UNIT_TEST
+
+/****************************************************************************
+      To run unit tests, compile with $(CC) -DUNIT_TEST stats.c assoc.o
+      (need assoc.o to get the hash() function).
+****************************************************************************/
+
+struct settings settings;
+
+static char *current_test = "";
+static int test_count = 0;
+static int fail_count = 0;
+
+static void fail(char *what) { printf("\tFAIL: %s\n", what); fflush(stdout); fail_count++; }
+static void test_equals_int(char *what, int a, int b) { test_count++; if (a != b) fail(what); }
+static void test_equals_ptr(char *what, void *a, void *b) { test_count++; if (a != b) fail(what); }
+static void test_equals_str(char *what, const char *a, const char *b) { test_count++; if (strcmp(a, b)) fail(what); }
+static void test_equals_ull(char *what, unsigned long long a, unsigned long long b) { test_count++; if (a != b) fail(what); }
+static void test_notequals_ptr(char *what, void *a, void *b) { test_count++; if (a == b) fail(what); }
+static void test_notnull_ptr(char *what, void *a) { test_count++; if (NULL == a) fail(what); }
+
+static void test_prefix_find() {
+    PREFIX_STATS *pfs1, *pfs2;
+
+    pfs1 = stats_prefix_find("abc");
+    test_notnull_ptr("initial prefix find", pfs1);
+    test_equals_ull("request counts", 0ULL,
+        pfs1->num_gets + pfs1->num_sets + pfs1->num_deletes + pfs1->num_hits);
+    pfs2 = stats_prefix_find("abc");
+    test_equals_ptr("find of same prefix", pfs1, pfs2);
+    pfs2 = stats_prefix_find("abc:");
+    test_equals_ptr("find of same prefix, ignoring delimiter", pfs1, pfs2);
+    pfs2 = stats_prefix_find("abc:d");
+    test_equals_ptr("find of same prefix, ignoring extra chars", pfs1, pfs2);
+    pfs2 = stats_prefix_find("xyz123");
+    test_notequals_ptr("find of different prefix", pfs1, pfs2);
+    pfs2 = stats_prefix_find("ab:");
+    test_notequals_ptr("find of shorter prefix", pfs1, pfs2);
+}
+
+static void test_prefix_record_get() {
+    PREFIX_STATS *pfs;
+
+    stats_prefix_record_get("abc:123", 0);
+    pfs = stats_prefix_find("abc:123");
+    test_equals_ull("get count after get #1", 1, pfs->num_gets);
+    test_equals_ull("hit count after get #1", 0, pfs->num_hits);
+    stats_prefix_record_get("abc:456", 0);
+    test_equals_ull("get count after get #2", 2, pfs->num_gets);
+    test_equals_ull("hit count after get #2", 0, pfs->num_hits);
+    stats_prefix_record_get("abc:456", 1);
+    test_equals_ull("get count after get #3", 3, pfs->num_gets);
+    test_equals_ull("hit count after get #3", 1, pfs->num_hits);
+    stats_prefix_record_get("def:", 1);
+    test_equals_ull("get count after get #4", 3, pfs->num_gets);
+    test_equals_ull("hit count after get #4", 1, pfs->num_hits);
+}
+
+static void test_prefix_record_delete() {
+    PREFIX_STATS *pfs;
+
+    stats_prefix_record_delete("abc:123");
+    pfs = stats_prefix_find("abc:123");
+    test_equals_ull("get count after delete #1", 0, pfs->num_gets);
+    test_equals_ull("hit count after delete #1", 0, pfs->num_hits);
+    test_equals_ull("delete count after delete #1", 1, pfs->num_deletes);
+    test_equals_ull("set count after delete #1", 0, pfs->num_sets);
+    stats_prefix_record_delete("def:");
+    test_equals_ull("delete count after delete #2", 1, pfs->num_deletes);
+}
+
+static void test_prefix_record_set() {
+    PREFIX_STATS *pfs;
+
+    stats_prefix_record_set("abc:123");
+    pfs = stats_prefix_find("abc:123");
+    test_equals_ull("get count after set #1", 0, pfs->num_gets);
+    test_equals_ull("hit count after set #1", 0, pfs->num_hits);
+    test_equals_ull("delete count after set #1", 0, pfs->num_deletes);
+    test_equals_ull("set count after set #1", 1, pfs->num_sets);
+    stats_prefix_record_delete("def:");
+    test_equals_ull("set count after set #2", 1, pfs->num_sets);
+}
+
+static void test_prefix_dump() {
+    int hashval = hash("abc", 3, 0) % PREFIX_HASH_SIZE;
+    char tmp[500];
+    char *expected;
+    int keynum;
+    int length;
+
+    test_equals_str("empty stats", "END\r\n", stats_prefix_dump(&length));
+    test_equals_int("empty stats length", 5, length);
+    stats_prefix_record_set("abc:123");
+    expected = "PREFIX abc get 0 hit 0 set 1 del 0\r\nEND\r\n";
+    test_equals_str("stats after set", expected, stats_prefix_dump(&length));
+    test_equals_int("stats length after set", strlen(expected), length);
+    stats_prefix_record_get("abc:123", 0);
+    expected = "PREFIX abc get 1 hit 0 set 1 del 0\r\nEND\r\n";
+    test_equals_str("stats after get #1", expected, stats_prefix_dump(&length));
+    test_equals_int("stats length after get #1", strlen(expected), length);
+    stats_prefix_record_get("abc:123", 1);
+    expected = "PREFIX abc get 2 hit 1 set 1 del 0\r\nEND\r\n";
+    test_equals_str("stats after get #2", expected, stats_prefix_dump(&length));
+    test_equals_int("stats length after get #2", strlen(expected), length);
+    stats_prefix_record_delete("abc:123");
+    expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\nEND\r\n";
+    test_equals_str("stats after del #1", expected, stats_prefix_dump(&length));
+    test_equals_int("stats length after del #1", strlen(expected), length);
+
+    /* The order of results might change if we switch hash functions. */
+    stats_prefix_record_delete("def:123");
+    expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\n"
+               "PREFIX def get 0 hit 0 set 0 del 1\r\n"
+               "END\r\n";
+    test_equals_str("stats after del #2", expected, stats_prefix_dump(&length));
+    test_equals_int("stats length after del #2", strlen(expected), length);
+
+    /* Find a key that hashes to the same bucket as "abc" */
+    for (keynum = 0; keynum < PREFIX_HASH_SIZE * 100; keynum++) {
+        sprintf(tmp, "%d", keynum);
+        if (hashval == hash(tmp, strlen(tmp), 0) % PREFIX_HASH_SIZE) {
+            break;
+        }
+    }
+    stats_prefix_record_set(tmp);
+    sprintf(tmp, "PREFIX %d get 0 hit 0 set 1 del 0\r\n"
+                 "PREFIX abc get 2 hit 1 set 1 del 1\r\n"
+                 "PREFIX def get 0 hit 0 set 0 del 1\r\n"
+                 "END\r\n", keynum);
+    test_equals_str("stats with two stats in one bucket",
+                    tmp, stats_prefix_dump(&length));
+    test_equals_int("stats length with two stats in one bucket",
+                    strlen(tmp), length);
+}
+
+static void run_test(char *what, void (*func)(void)) {
+    current_test = what;
+    test_count = fail_count = 0;
+    puts(what);
+    fflush(stdout);
+
+    stats_prefix_clear();
+    (func)();
+    printf("\t%d / %d pass\n", (test_count - fail_count), test_count);
+}
+
+/* In case we're compiled in thread mode */
+void mt_stats_lock() { }
+void mt_stats_unlock() { }
+
+main(int argc, char **argv) {
+    stats_prefix_init();
+    settings.prefix_delimiter = ':';
+    run_test("stats_prefix_find", test_prefix_find);
+    run_test("stats_prefix_record_get", test_prefix_record_get);
+    run_test("stats_prefix_record_delete", test_prefix_record_delete);
+    run_test("stats_prefix_record_set", test_prefix_record_set);
+    run_test("stats_prefix_dump", test_prefix_dump);
+}
+
+#endif

Added: branches/multithreaded/server/t/stats-detail.t
===================================================================
--- branches/multithreaded/server/t/stats-detail.t	2007-03-02 03:44:59 UTC (rev 461)
+++ branches/multithreaded/server/t/stats-detail.t	2007-03-05 23:38:52 UTC (rev 462)
@@ -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();
+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\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>, "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>, "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>, "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\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\r\n", "details after stats turned off");
+is(scalar <$sock>, "END\r\n", "end of details");




More information about the memcached-commits mailing list