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