1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
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.
8 * Steven Grimm <sgrimm@facebook.com>
10 #include "memcached.h"
17 * Stats are tracked on the basis of key prefixes. This is a simple
18 * fixed-size hash of prefixes; we run the prefixes through the same
19 * CRC function used by the cache hashtable.
21 typedef struct _prefix_stats PREFIX_STATS
;
22 struct _prefix_stats
{
32 #define PREFIX_HASH_SIZE 256
34 static PREFIX_STATS
*prefix_stats
[PREFIX_HASH_SIZE
];
35 static int num_prefixes
= 0;
36 static int total_prefix_size
= 0;
38 void stats_prefix_init() {
39 memset(prefix_stats
, 0, sizeof(prefix_stats
));
43 * Cleans up all our previously collected stats. NOTE: the stats lock is
44 * assumed to be held when this is called.
46 void stats_prefix_clear() {
49 for (i
= 0; i
< PREFIX_HASH_SIZE
; i
++) {
50 PREFIX_STATS
*cur
, *next
;
51 for (cur
= prefix_stats
[i
]; cur
!= NULL
; cur
= next
) {
56 prefix_stats
[i
] = NULL
;
59 total_prefix_size
= 0;
63 * Returns the stats structure for a prefix, creating it if it's not already
67 static PREFIX_STATS
*stats_prefix_find(const char *key
, const size_t nkey
) {
75 for (length
= 0; length
< nkey
&& key
[length
] != '\0'; length
++) {
76 if (key
[length
] == settings
.prefix_delimiter
) {
86 hashval
= hash(key
, length
, 0) % PREFIX_HASH_SIZE
;
88 for (pfs
= prefix_stats
[hashval
]; NULL
!= pfs
; pfs
= pfs
->next
) {
89 if (strncmp(pfs
->prefix
, key
, length
) == 0)
93 pfs
= calloc(sizeof(PREFIX_STATS
), 1);
95 perror("Can't allocate space for stats structure: calloc");
99 pfs
->prefix
= malloc(length
+ 1);
100 if (NULL
== pfs
->prefix
) {
101 perror("Can't allocate space for copy of prefix: malloc");
106 strncpy(pfs
->prefix
, key
, length
);
107 pfs
->prefix
[length
] = '\0'; /* because strncpy() sucks */
108 pfs
->prefix_len
= length
;
110 pfs
->next
= prefix_stats
[hashval
];
111 prefix_stats
[hashval
] = pfs
;
114 total_prefix_size
+= length
;
120 * Records a "get" of a key.
122 void stats_prefix_record_get(const char *key
, const size_t nkey
, const bool is_hit
) {
126 pfs
= stats_prefix_find(key
, nkey
);
137 * Records a "delete" of a key.
139 void stats_prefix_record_delete(const char *key
, const size_t nkey
) {
143 pfs
= stats_prefix_find(key
, nkey
);
151 * Records a "set" of a key.
153 void stats_prefix_record_set(const char *key
, const size_t nkey
) {
157 pfs
= stats_prefix_find(key
, nkey
);
165 * Returns stats in textual form suitable for writing to a client.
168 char *stats_prefix_dump(int *length
) {
169 const char *format
= "PREFIX %s get %llu hit %llu set %llu del %llu\r\n";
173 size_t size
= 0, written
= 0, total_written
= 0;
176 * Figure out how big the buffer needs to be. This is the sum of the
177 * lengths of the prefixes themselves, plus the size of one copy of
178 * the per-prefix output with 20-digit values for all the counts,
179 * plus space for the "END" at the end.
182 size
= strlen(format
) + total_prefix_size
+
183 num_prefixes
* (strlen(format
) - 2 /* %s */
184 + 4 * (20 - 4)) /* %llu replaced by 20-digit num */
188 perror("Can't allocate stats response: malloc");
194 for (i
= 0; i
< PREFIX_HASH_SIZE
; i
++) {
195 for (pfs
= prefix_stats
[i
]; NULL
!= pfs
; pfs
= pfs
->next
) {
196 written
= snprintf(buf
+ pos
, size
-pos
, format
,
197 pfs
->prefix
, pfs
->num_gets
, pfs
->num_hits
,
198 pfs
->num_sets
, pfs
->num_deletes
);
200 total_written
+= written
;
201 assert(total_written
< size
);
206 memcpy(buf
+ pos
, "END\r\n", 6);
215 /****************************************************************************
216 To run unit tests, compile with $(CC) -DUNIT_TEST stats.c assoc.o
217 (need assoc.o to get the hash() function).
218 ****************************************************************************/
220 struct settings settings
;
222 static char *current_test
= "";
223 static int test_count
= 0;
224 static int fail_count
= 0;
226 static void fail(char *what
) { printf("\tFAIL: %s\n", what
); fflush(stdout
); fail_count
++; }
227 static void test_equals_int(char *what
, int a
, int b
) { test_count
++; if (a
!= b
) fail(what
); }
228 static void test_equals_ptr(char *what
, void *a
, void *b
) { test_count
++; if (a
!= b
) fail(what
); }
229 static void test_equals_str(char *what
, const char *a
, const char *b
) { test_count
++; if (strcmp(a
, b
)) fail(what
); }
230 static void test_equals_ull(char *what
, uint64_t a
, uint64_t b
) { test_count
++; if (a
!= b
) fail(what
); }
231 static void test_notequals_ptr(char *what
, void *a
, void *b
) { test_count
++; if (a
== b
) fail(what
); }
232 static void test_notnull_ptr(char *what
, void *a
) { test_count
++; if (NULL
== a
) fail(what
); }
234 static void test_prefix_find() {
235 PREFIX_STATS
*pfs1
, *pfs2
;
237 pfs1
= stats_prefix_find("abc");
238 test_notnull_ptr("initial prefix find", pfs1
);
239 test_equals_ull("request counts", 0ULL,
240 pfs1
->num_gets
+ pfs1
->num_sets
+ pfs1
->num_deletes
+ pfs1
->num_hits
);
241 pfs2
= stats_prefix_find("abc");
242 test_equals_ptr("find of same prefix", pfs1
, pfs2
);
243 pfs2
= stats_prefix_find("abc:");
244 test_equals_ptr("find of same prefix, ignoring delimiter", pfs1
, pfs2
);
245 pfs2
= stats_prefix_find("abc:d");
246 test_equals_ptr("find of same prefix, ignoring extra chars", pfs1
, pfs2
);
247 pfs2
= stats_prefix_find("xyz123");
248 test_notequals_ptr("find of different prefix", pfs1
, pfs2
);
249 pfs2
= stats_prefix_find("ab:");
250 test_notequals_ptr("find of shorter prefix", pfs1
, pfs2
);
253 static void test_prefix_record_get() {
256 stats_prefix_record_get("abc:123", 0);
257 pfs
= stats_prefix_find("abc:123");
258 test_equals_ull("get count after get #1", 1, pfs
->num_gets
);
259 test_equals_ull("hit count after get #1", 0, pfs
->num_hits
);
260 stats_prefix_record_get("abc:456", 0);
261 test_equals_ull("get count after get #2", 2, pfs
->num_gets
);
262 test_equals_ull("hit count after get #2", 0, pfs
->num_hits
);
263 stats_prefix_record_get("abc:456", 1);
264 test_equals_ull("get count after get #3", 3, pfs
->num_gets
);
265 test_equals_ull("hit count after get #3", 1, pfs
->num_hits
);
266 stats_prefix_record_get("def:", 1);
267 test_equals_ull("get count after get #4", 3, pfs
->num_gets
);
268 test_equals_ull("hit count after get #4", 1, pfs
->num_hits
);
271 static void test_prefix_record_delete() {
274 stats_prefix_record_delete("abc:123");
275 pfs
= stats_prefix_find("abc:123");
276 test_equals_ull("get count after delete #1", 0, pfs
->num_gets
);
277 test_equals_ull("hit count after delete #1", 0, pfs
->num_hits
);
278 test_equals_ull("delete count after delete #1", 1, pfs
->num_deletes
);
279 test_equals_ull("set count after delete #1", 0, pfs
->num_sets
);
280 stats_prefix_record_delete("def:");
281 test_equals_ull("delete count after delete #2", 1, pfs
->num_deletes
);
284 static void test_prefix_record_set() {
287 stats_prefix_record_set("abc:123");
288 pfs
= stats_prefix_find("abc:123");
289 test_equals_ull("get count after set #1", 0, pfs
->num_gets
);
290 test_equals_ull("hit count after set #1", 0, pfs
->num_hits
);
291 test_equals_ull("delete count after set #1", 0, pfs
->num_deletes
);
292 test_equals_ull("set count after set #1", 1, pfs
->num_sets
);
293 stats_prefix_record_delete("def:");
294 test_equals_ull("set count after set #2", 1, pfs
->num_sets
);
297 static void test_prefix_dump() {
298 int hashval
= hash("abc", 3, 0) % PREFIX_HASH_SIZE
;
304 test_equals_str("empty stats", "END\r\n", stats_prefix_dump(&length
));
305 test_equals_int("empty stats length", 5, length
);
306 stats_prefix_record_set("abc:123");
307 expected
= "PREFIX abc get 0 hit 0 set 1 del 0\r\nEND\r\n";
308 test_equals_str("stats after set", expected
, stats_prefix_dump(&length
));
309 test_equals_int("stats length after set", strlen(expected
), length
);
310 stats_prefix_record_get("abc:123", 0);
311 expected
= "PREFIX abc get 1 hit 0 set 1 del 0\r\nEND\r\n";
312 test_equals_str("stats after get #1", expected
, stats_prefix_dump(&length
));
313 test_equals_int("stats length after get #1", strlen(expected
), length
);
314 stats_prefix_record_get("abc:123", 1);
315 expected
= "PREFIX abc get 2 hit 1 set 1 del 0\r\nEND\r\n";
316 test_equals_str("stats after get #2", expected
, stats_prefix_dump(&length
));
317 test_equals_int("stats length after get #2", strlen(expected
), length
);
318 stats_prefix_record_delete("abc:123");
319 expected
= "PREFIX abc get 2 hit 1 set 1 del 1\r\nEND\r\n";
320 test_equals_str("stats after del #1", expected
, stats_prefix_dump(&length
));
321 test_equals_int("stats length after del #1", strlen(expected
), length
);
323 /* The order of results might change if we switch hash functions. */
324 stats_prefix_record_delete("def:123");
325 expected
= "PREFIX abc get 2 hit 1 set 1 del 1\r\n"
326 "PREFIX def get 0 hit 0 set 0 del 1\r\n"
328 test_equals_str("stats after del #2", expected
, stats_prefix_dump(&length
));
329 test_equals_int("stats length after del #2", strlen(expected
), length
);
331 /* Find a key that hashes to the same bucket as "abc" */
332 for (keynum
= 0; keynum
< PREFIX_HASH_SIZE
* 100; keynum
++) {
333 snprintf(tmp
, sizeof(tmp
), "%d", keynum
);
334 if (hashval
== hash(tmp
, strlen(tmp
), 0) % PREFIX_HASH_SIZE
) {
338 stats_prefix_record_set(tmp
);
339 snprintf(tmp
, sizeof(tmp
),
340 "PREFIX %d get 0 hit 0 set 1 del 0\r\n"
341 "PREFIX abc get 2 hit 1 set 1 del 1\r\n"
342 "PREFIX def get 0 hit 0 set 0 del 1\r\n"
344 test_equals_str("stats with two stats in one bucket",
345 tmp
, stats_prefix_dump(&length
));
346 test_equals_int("stats length with two stats in one bucket",
347 strlen(tmp
), length
);
350 static void run_test(char *what
, void (*func
)(void)) {
352 test_count
= fail_count
= 0;
356 stats_prefix_clear();
358 printf("\t%d / %d pass\n", (test_count
- fail_count
), test_count
);
361 /* In case we're compiled in thread mode */
362 void mt_stats_lock() { }
363 void mt_stats_unlock() { }
365 main(int argc
, char **argv
) {
367 settings
.prefix_delimiter
= ':';
368 run_test("stats_prefix_find", test_prefix_find
);
369 run_test("stats_prefix_record_get", test_prefix_record_get
);
370 run_test("stats_prefix_record_delete", test_prefix_record_delete
);
371 run_test("stats_prefix_record_set", test_prefix_record_set
);
372 run_test("stats_prefix_dump", test_prefix_dump
);