0
|
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
|