Patch: Collect per-object-type stats

mark rkmr.em at gmail.com
Sun Mar 4 17:56:54 UTC 2007


Hi
How do I get the multi-threaded branch? THis page has just one version:

http://danga.com/memcached/download.bml

Is the multi-threaded version to be used for apps that are multithreaded?

thanks
mark

On 3/1/07, Steven Grimm <sgrimm at facebook.com> wrote:
> This is a patch we're running to collect more precise statistics on the
> objects in our caches. In addition to a cachewide "hit" and "miss"
> count, this tracks get/set/hit/miss stats based on key prefixes, where a
> "prefix" is delimited by a character of your choice. So, for example, if
> you use the default delimiter of ":", then "user:12345" and "user:34567"
> are considered to both be of type "user". Stats collection does have a
> (small) CPU impact, so it may be turned on and off at runtime. In the
> multithreaded version of the code, there is usually enough CPU time to
> spare; we leave it turned on.
>
> Tracking stats with per-object-type granularity is valuable because
> different object types may have very different behavior that's
> impossible to discern from global stats; for example, the hit ratio on
> some of our objects is substantially lower than on others.
>
> This is a patch against the multithreaded branch since that's what we're
> running. It should work with minimal modification on the single-threaded
> source base -- pretty much just remove the lock/unlock calls. Note that
> the MT branch can still be compiled without thread support, so you don't
> have to actually use multiple threads to use this patch as is.
>
> Comments welcome, as always. If I don't hear any objections I'll commit
> this to the multithreaded branch in a little while.
>
> -Steve
>
>
>
> Index: stats.c
> ===================================================================
> --- stats.c     (revision 0)
> +++ stats.c     (revision 0)
> @@ -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
> Index: memcached.c
> ===================================================================
> --- memcached.c (revision 460)
> +++ memcached.c (working copy)
> @@ -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;
> Index: memcached.h
> ===================================================================
> --- memcached.h (revision 460)
> +++ memcached.h (working copy)
> @@ -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);
> Index: t/stats-detail.t
> ===================================================================
> --- t/stats-detail.t    (revision 0)
> +++ t/stats-detail.t    (revision 0)
> @@ -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");
> Index: doc/memcached.1
> ===================================================================
> --- doc/memcached.1     (revision 460)
> +++ doc/memcached.1     (working copy)
> @@ -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
> Index: Makefile.am
> ===================================================================
> --- Makefile.am (revision 460)
> +++ Makefile.am (working copy)
> @@ -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@
>
>


More information about the memcached mailing list