Mercurial > hg > memcached
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 |