xref: /memcached-1.4.29/stats.c (revision f5e4d26d)
1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * Detailed statistics management. For simple stats like total number of
4  * "get" requests, we use inline code in memcached.c and friends, but when
5  * stats detail mode is activated, the code here records more information.
6  *
7  * Author:
8  *   Steven Grimm <[email protected]>
9  *
10  * $Id$
11  */
12 #include "memcached.h"
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <assert.h>
17 
18 /*
19  * Stats are tracked on the basis of key prefixes. This is a simple
20  * fixed-size hash of prefixes; we run the prefixes through the same
21  * CRC function used by the cache hashtable.
22  */
23 typedef struct _prefix_stats PREFIX_STATS;
24 struct _prefix_stats {
25     char         *prefix;
26     size_t        prefix_len;
27     uint64_t      num_gets;
28     uint64_t      num_sets;
29     uint64_t      num_deletes;
30     uint64_t      num_hits;
31     PREFIX_STATS *next;
32 };
33 
34 #define PREFIX_HASH_SIZE 256
35 
36 static PREFIX_STATS *prefix_stats[PREFIX_HASH_SIZE];
37 static int num_prefixes = 0;
38 static int total_prefix_size = 0;
39 
40 void stats_prefix_init() {
41     memset(prefix_stats, 0, sizeof(prefix_stats));
42 }
43 
44 /*
45  * Cleans up all our previously collected stats. NOTE: the stats lock is
46  * assumed to be held when this is called.
47  */
48 void stats_prefix_clear() {
49     int i;
50 
51     for (i = 0; i < PREFIX_HASH_SIZE; i++) {
52         PREFIX_STATS *cur, *next;
53         for (cur = prefix_stats[i]; cur != NULL; cur = next) {
54             next = cur->next;
55             free(cur->prefix);
56             free(cur);
57         }
58         prefix_stats[i] = NULL;
59     }
60     num_prefixes = 0;
61     total_prefix_size = 0;
62 }
63 
64 /*
65  * Returns the stats structure for a prefix, creating it if it's not already
66  * in the list.
67  */
68 /*@null@*/
69 static PREFIX_STATS *stats_prefix_find(const char *key) {
70     PREFIX_STATS *pfs;
71     uint32_t hashval;
72     size_t length;
73 
74     assert(key != NULL);
75 
76     for (length = 0; key[length] != '\0'; length++)
77         if (key[length] == settings.prefix_delimiter)
78             break;
79 
80     hashval = hash(key, length, 0) % PREFIX_HASH_SIZE;
81 
82     for (pfs = prefix_stats[hashval]; NULL != pfs; pfs = pfs->next) {
83         if (strncmp(pfs->prefix, key, length) == 0)
84             return pfs;
85     }
86 
87     pfs = calloc(sizeof(PREFIX_STATS), 1);
88     if (NULL == pfs) {
89         perror("Can't allocate space for stats structure: calloc");
90         return NULL;
91     }
92 
93     pfs->prefix = malloc(length + 1);
94     if (NULL == pfs->prefix) {
95         perror("Can't allocate space for copy of prefix: malloc");
96         free(pfs);
97         return NULL;
98     }
99 
100     strncpy(pfs->prefix, key, length);
101     pfs->prefix[length] = '\0';      /* because strncpy() sucks */
102     pfs->prefix_len = length;
103 
104     pfs->next = prefix_stats[hashval];
105     prefix_stats[hashval] = pfs;
106 
107     num_prefixes++;
108     total_prefix_size += length;
109 
110     return pfs;
111 }
112 
113 /*
114  * Records a "get" of a key.
115  */
116 void stats_prefix_record_get(const char *key, const bool is_hit) {
117     PREFIX_STATS *pfs;
118 
119     STATS_LOCK();
120     pfs = stats_prefix_find(key);
121     if (NULL != pfs) {
122         pfs->num_gets++;
123         if (is_hit) {
124             pfs->num_hits++;
125         }
126     }
127     STATS_UNLOCK();
128 }
129 
130 /*
131  * Records a "delete" of a key.
132  */
133 void stats_prefix_record_delete(const char *key) {
134     PREFIX_STATS *pfs;
135 
136     STATS_LOCK();
137     pfs = stats_prefix_find(key);
138     if (NULL != pfs) {
139         pfs->num_deletes++;
140     }
141     STATS_UNLOCK();
142 }
143 
144 /*
145  * Records a "set" of a key.
146  */
147 void stats_prefix_record_set(const char *key) {
148     PREFIX_STATS *pfs;
149 
150     STATS_LOCK();
151     pfs = stats_prefix_find(key);
152     if (NULL != pfs) {
153         pfs->num_sets++;
154     }
155     STATS_UNLOCK();
156 }
157 
158 /*
159  * Returns stats in textual form suitable for writing to a client.
160  */
161 /*@null@*/
162 char *stats_prefix_dump(int *length) {
163     const char *format = "PREFIX %s get %llu hit %llu set %llu del %llu\r\n";
164     PREFIX_STATS *pfs;
165     char *buf;
166     int i, pos;
167     size_t size;
168 
169     /*
170      * Figure out how big the buffer needs to be. This is the sum of the
171      * lengths of the prefixes themselves, plus the size of one copy of
172      * the per-prefix output with 20-digit values for all the counts,
173      * plus space for the "END" at the end.
174      */
175     STATS_LOCK();
176     size = strlen(format) + total_prefix_size +
177            num_prefixes * (strlen(format) - 2 /* %s */
178                            + 4 * (20 - 4)) /* %llu replaced by 20-digit num */
179                            + sizeof("END\r\n");
180     buf = malloc(size);
181     if (NULL == buf) {
182         perror("Can't allocate stats response: malloc");
183         STATS_UNLOCK();
184         return NULL;
185     }
186 
187     pos = 0;
188     for (i = 0; i < PREFIX_HASH_SIZE; i++) {
189         for (pfs = prefix_stats[i]; NULL != pfs; pfs = pfs->next) {
190             pos += snprintf(buf + pos, size-pos, format,
191                            pfs->prefix, pfs->num_gets, pfs->num_hits,
192                            pfs->num_sets, pfs->num_deletes);
193         }
194     }
195 
196     STATS_UNLOCK();
197     memcpy(buf + pos, "END\r\n", 6);
198 
199     *length = pos + 5;
200     return buf;
201 }
202 
203 
204 #ifdef UNIT_TEST
205 
206 /****************************************************************************
207       To run unit tests, compile with $(CC) -DUNIT_TEST stats.c assoc.o
208       (need assoc.o to get the hash() function).
209 ****************************************************************************/
210 
211 struct settings settings;
212 
213 static char *current_test = "";
214 static int test_count = 0;
215 static int fail_count = 0;
216 
217 static void fail(char *what) { printf("\tFAIL: %s\n", what); fflush(stdout); fail_count++; }
218 static void test_equals_int(char *what, int a, int b) { test_count++; if (a != b) fail(what); }
219 static void test_equals_ptr(char *what, void *a, void *b) { test_count++; if (a != b) fail(what); }
220 static void test_equals_str(char *what, const char *a, const char *b) { test_count++; if (strcmp(a, b)) fail(what); }
221 static void test_equals_ull(char *what, uint64_t a, uint64_t b) { test_count++; if (a != b) fail(what); }
222 static void test_notequals_ptr(char *what, void *a, void *b) { test_count++; if (a == b) fail(what); }
223 static void test_notnull_ptr(char *what, void *a) { test_count++; if (NULL == a) fail(what); }
224 
225 static void test_prefix_find() {
226     PREFIX_STATS *pfs1, *pfs2;
227 
228     pfs1 = stats_prefix_find("abc");
229     test_notnull_ptr("initial prefix find", pfs1);
230     test_equals_ull("request counts", 0ULL,
231         pfs1->num_gets + pfs1->num_sets + pfs1->num_deletes + pfs1->num_hits);
232     pfs2 = stats_prefix_find("abc");
233     test_equals_ptr("find of same prefix", pfs1, pfs2);
234     pfs2 = stats_prefix_find("abc:");
235     test_equals_ptr("find of same prefix, ignoring delimiter", pfs1, pfs2);
236     pfs2 = stats_prefix_find("abc:d");
237     test_equals_ptr("find of same prefix, ignoring extra chars", pfs1, pfs2);
238     pfs2 = stats_prefix_find("xyz123");
239     test_notequals_ptr("find of different prefix", pfs1, pfs2);
240     pfs2 = stats_prefix_find("ab:");
241     test_notequals_ptr("find of shorter prefix", pfs1, pfs2);
242 }
243 
244 static void test_prefix_record_get() {
245     PREFIX_STATS *pfs;
246 
247     stats_prefix_record_get("abc:123", 0);
248     pfs = stats_prefix_find("abc:123");
249     test_equals_ull("get count after get #1", 1, pfs->num_gets);
250     test_equals_ull("hit count after get #1", 0, pfs->num_hits);
251     stats_prefix_record_get("abc:456", 0);
252     test_equals_ull("get count after get #2", 2, pfs->num_gets);
253     test_equals_ull("hit count after get #2", 0, pfs->num_hits);
254     stats_prefix_record_get("abc:456", 1);
255     test_equals_ull("get count after get #3", 3, pfs->num_gets);
256     test_equals_ull("hit count after get #3", 1, pfs->num_hits);
257     stats_prefix_record_get("def:", 1);
258     test_equals_ull("get count after get #4", 3, pfs->num_gets);
259     test_equals_ull("hit count after get #4", 1, pfs->num_hits);
260 }
261 
262 static void test_prefix_record_delete() {
263     PREFIX_STATS *pfs;
264 
265     stats_prefix_record_delete("abc:123");
266     pfs = stats_prefix_find("abc:123");
267     test_equals_ull("get count after delete #1", 0, pfs->num_gets);
268     test_equals_ull("hit count after delete #1", 0, pfs->num_hits);
269     test_equals_ull("delete count after delete #1", 1, pfs->num_deletes);
270     test_equals_ull("set count after delete #1", 0, pfs->num_sets);
271     stats_prefix_record_delete("def:");
272     test_equals_ull("delete count after delete #2", 1, pfs->num_deletes);
273 }
274 
275 static void test_prefix_record_set() {
276     PREFIX_STATS *pfs;
277 
278     stats_prefix_record_set("abc:123");
279     pfs = stats_prefix_find("abc:123");
280     test_equals_ull("get count after set #1", 0, pfs->num_gets);
281     test_equals_ull("hit count after set #1", 0, pfs->num_hits);
282     test_equals_ull("delete count after set #1", 0, pfs->num_deletes);
283     test_equals_ull("set count after set #1", 1, pfs->num_sets);
284     stats_prefix_record_delete("def:");
285     test_equals_ull("set count after set #2", 1, pfs->num_sets);
286 }
287 
288 static void test_prefix_dump() {
289     int hashval = hash("abc", 3, 0) % PREFIX_HASH_SIZE;
290     char tmp[500];
291     char *expected;
292     int keynum;
293     int length;
294 
295     test_equals_str("empty stats", "END\r\n", stats_prefix_dump(&length));
296     test_equals_int("empty stats length", 5, length);
297     stats_prefix_record_set("abc:123");
298     expected = "PREFIX abc get 0 hit 0 set 1 del 0\r\nEND\r\n";
299     test_equals_str("stats after set", expected, stats_prefix_dump(&length));
300     test_equals_int("stats length after set", strlen(expected), length);
301     stats_prefix_record_get("abc:123", 0);
302     expected = "PREFIX abc get 1 hit 0 set 1 del 0\r\nEND\r\n";
303     test_equals_str("stats after get #1", expected, stats_prefix_dump(&length));
304     test_equals_int("stats length after get #1", strlen(expected), length);
305     stats_prefix_record_get("abc:123", 1);
306     expected = "PREFIX abc get 2 hit 1 set 1 del 0\r\nEND\r\n";
307     test_equals_str("stats after get #2", expected, stats_prefix_dump(&length));
308     test_equals_int("stats length after get #2", strlen(expected), length);
309     stats_prefix_record_delete("abc:123");
310     expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\nEND\r\n";
311     test_equals_str("stats after del #1", expected, stats_prefix_dump(&length));
312     test_equals_int("stats length after del #1", strlen(expected), length);
313 
314     /* The order of results might change if we switch hash functions. */
315     stats_prefix_record_delete("def:123");
316     expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\n"
317                "PREFIX def get 0 hit 0 set 0 del 1\r\n"
318                "END\r\n";
319     test_equals_str("stats after del #2", expected, stats_prefix_dump(&length));
320     test_equals_int("stats length after del #2", strlen(expected), length);
321 
322     /* Find a key that hashes to the same bucket as "abc" */
323     for (keynum = 0; keynum < PREFIX_HASH_SIZE * 100; keynum++) {
324         sprintf(tmp, "%d", keynum);
325         if (hashval == hash(tmp, strlen(tmp), 0) % PREFIX_HASH_SIZE) {
326             break;
327         }
328     }
329     stats_prefix_record_set(tmp);
330     sprintf(tmp, "PREFIX %d get 0 hit 0 set 1 del 0\r\n"
331                  "PREFIX abc get 2 hit 1 set 1 del 1\r\n"
332                  "PREFIX def get 0 hit 0 set 0 del 1\r\n"
333                  "END\r\n", keynum);
334     test_equals_str("stats with two stats in one bucket",
335                     tmp, stats_prefix_dump(&length));
336     test_equals_int("stats length with two stats in one bucket",
337                     strlen(tmp), length);
338 }
339 
340 static void run_test(char *what, void (*func)(void)) {
341     current_test = what;
342     test_count = fail_count = 0;
343     puts(what);
344     fflush(stdout);
345 
346     stats_prefix_clear();
347     (func)();
348     printf("\t%d / %d pass\n", (test_count - fail_count), test_count);
349 }
350 
351 /* In case we're compiled in thread mode */
352 void mt_stats_lock() { }
353 void mt_stats_unlock() { }
354 
355 main(int argc, char **argv) {
356     stats_prefix_init();
357     settings.prefix_delimiter = ':';
358     run_test("stats_prefix_find", test_prefix_find);
359     run_test("stats_prefix_record_get", test_prefix_record_get);
360     run_test("stats_prefix_record_delete", test_prefix_record_delete);
361     run_test("stats_prefix_record_set", test_prefix_record_set);
362     run_test("stats_prefix_dump", test_prefix_dump);
363 }
364 
365 #endif
366