comparison stats.c @ 0:30782bb1fc04 MEMCACHED_1_2_3

memcached-1.2.3
author Maxim Dounin <mdounin@mdounin.ru>
date Sun, 23 Sep 2007 03:58:34 +0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:30782bb1fc04
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 <sgrimm@facebook.com>
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