MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
stats.c
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 #include "config.h"
11 #include "memcached.h"
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <assert.h>
16 
17 /*
18  * Stats are tracked on the basis of key prefixes. This is a simple
19  * fixed-size hash of prefixes; we run the prefixes through the same
20  * CRC function used by the cache hashtable.
21  */
22 typedef struct _prefix_stats PREFIX_STATS;
23 struct _prefix_stats {
24  char *prefix;
25  size_t prefix_len;
26  uint64_t num_gets;
27  uint64_t num_sets;
28  uint64_t num_deletes;
29  uint64_t num_hits;
30  PREFIX_STATS *next;
31 };
32 
33 #define PREFIX_HASH_SIZE 256
34 
35 static PREFIX_STATS *prefix_stats[PREFIX_HASH_SIZE];
36 static int num_prefixes = 0;
37 static int total_prefix_size = 0;
38 
39 void stats_prefix_init() {
40  memset(prefix_stats, 0, sizeof(prefix_stats));
41 }
42 
43 /*
44  * Cleans up all our previously collected stats. NOTE: the stats lock is
45  * assumed to be held when this is called.
46  */
47 void stats_prefix_clear() {
48  int i;
49 
50  for (i = 0; i < PREFIX_HASH_SIZE; i++) {
51  PREFIX_STATS *cur, *next;
52  for (cur = prefix_stats[i]; cur != NULL; cur = next) {
53  next = cur->next;
54  free(cur->prefix);
55  free(cur);
56  }
57  prefix_stats[i] = NULL;
58  }
59  num_prefixes = 0;
60  total_prefix_size = 0;
61 }
62 
63 /*
64  * Returns the stats structure for a prefix, creating it if it's not already
65  * in the list.
66  */
67 /*@null@*/
68 static PREFIX_STATS *stats_prefix_find(const char *key, const size_t nkey) {
69  PREFIX_STATS *pfs;
70  uint32_t hashval;
71  size_t length;
72  bool bailout = true;
73 
74  assert(key != NULL);
75 
76  for (length = 0; length < nkey && key[length] != '\0'; length++) {
77  if (key[length] == settings.prefix_delimiter) {
78  bailout = false;
79  break;
80  }
81  }
82 
83  if (bailout) {
84  return NULL;
85  }
86 
87  hashval = hash(key, length, 0) % PREFIX_HASH_SIZE;
88 
89  for (pfs = prefix_stats[hashval]; NULL != pfs; pfs = pfs->next) {
90  if (strncmp(pfs->prefix, key, length) == 0)
91  return pfs;
92  }
93 
94  pfs = calloc(sizeof(PREFIX_STATS), 1);
95  if (NULL == pfs) {
96  perror("Can't allocate space for stats structure: calloc");
97  return NULL;
98  }
99 
100  pfs->prefix = malloc(length + 1);
101  if (NULL == pfs->prefix) {
102  perror("Can't allocate space for copy of prefix: malloc");
103  free(pfs);
104  return NULL;
105  }
106 
107  strncpy(pfs->prefix, key, length);
108  pfs->prefix[length] = '\0'; /* because strncpy() sucks */
109  pfs->prefix_len = length;
110 
111  pfs->next = prefix_stats[hashval];
112  prefix_stats[hashval] = pfs;
113 
114  num_prefixes++;
115  total_prefix_size += length;
116 
117  return pfs;
118 }
119 
120 /*
121  * Records a "get" of a key.
122  */
123 void stats_prefix_record_get(const char *key, const size_t nkey, const bool is_hit) {
124  PREFIX_STATS *pfs;
125 
126  STATS_LOCK();
127  pfs = stats_prefix_find(key, nkey);
128  if (NULL != pfs) {
129  pfs->num_gets++;
130  if (is_hit) {
131  pfs->num_hits++;
132  }
133  }
134  STATS_UNLOCK();
135 }
136 
137 /*
138  * Records a "delete" of a key.
139  */
140 void stats_prefix_record_delete(const char *key, const size_t nkey) {
141  PREFIX_STATS *pfs;
142 
143  STATS_LOCK();
144  pfs = stats_prefix_find(key, nkey);
145  if (NULL != pfs) {
146  pfs->num_deletes++;
147  }
148  STATS_UNLOCK();
149 }
150 
151 /*
152  * Records a "set" of a key.
153  */
154 void stats_prefix_record_set(const char *key, const size_t nkey) {
155  PREFIX_STATS *pfs;
156 
157  STATS_LOCK();
158  pfs = stats_prefix_find(key, nkey);
159  if (NULL != pfs) {
160  pfs->num_sets++;
161  }
162  STATS_UNLOCK();
163 }
164 
165 /*
166  * Returns stats in textual form suitable for writing to a client.
167  */
168 /*@null@*/
169 char *stats_prefix_dump(int *length) {
170  const char *format = "PREFIX %s get %llu hit %llu set %llu del %llu\r\n";
171  PREFIX_STATS *pfs;
172  char *buf;
173  int i, pos;
174  size_t size = 0, written = 0, total_written = 0;
175 
176  /*
177  * Figure out how big the buffer needs to be. This is the sum of the
178  * lengths of the prefixes themselves, plus the size of one copy of
179  * the per-prefix output with 20-digit values for all the counts,
180  * plus space for the "END" at the end.
181  */
182  STATS_LOCK();
183  size = strlen(format) + total_prefix_size +
184  num_prefixes * (strlen(format) - 2 /* %s */
185  + 4 * (20 - 4)) /* %llu replaced by 20-digit num */
186  + sizeof("END\r\n");
187  buf = malloc(size);
188  if (NULL == buf) {
189  perror("Can't allocate stats response: malloc");
190  STATS_UNLOCK();
191  return NULL;
192  }
193 
194  pos = 0;
195  for (i = 0; i < PREFIX_HASH_SIZE; i++) {
196  for (pfs = prefix_stats[i]; NULL != pfs; pfs = pfs->next) {
197  written = snprintf(buf + pos, size-pos, format,
198  pfs->prefix, pfs->num_gets, pfs->num_hits,
199  pfs->num_sets, pfs->num_deletes);
200  pos += written;
201  total_written += written;
202  assert(total_written < size);
203  }
204  }
205 
206  STATS_UNLOCK();
207  memcpy(buf + pos, "END\r\n", 6);
208 
209  *length = pos + 5;
210  return buf;
211 }
212 
213 
214 #ifdef UNIT_TEST
215 
216 /****************************************************************************
217  To run unit tests, compile with $(CC) -DUNIT_TEST stats.c assoc.o
218  (need assoc.o to get the hash() function).
219 ****************************************************************************/
220 
221 struct settings settings;
222 
223 static char *current_test = "";
224 static int test_count = 0;
225 static int fail_count = 0;
226 
227 static void fail(char *what) { printf("\tFAIL: %s\n", what); fflush(stdout); fail_count++; }
228 static void test_equals_int(char *what, int a, int b) { test_count++; if (a != b) fail(what); }
229 static void test_equals_ptr(char *what, void *a, void *b) { test_count++; if (a != b) fail(what); }
230 static void test_equals_str(char *what, const char *a, const char *b) { test_count++; if (strcmp(a, b)) fail(what); }
231 static void test_equals_ull(char *what, uint64_t a, uint64_t b) { test_count++; if (a != b) fail(what); }
232 static void test_notequals_ptr(char *what, void *a, void *b) { test_count++; if (a == b) fail(what); }
233 static void test_notnull_ptr(char *what, void *a) { test_count++; if (NULL == a) fail(what); }
234 
235 static void test_prefix_find() {
236  PREFIX_STATS *pfs1, *pfs2;
237 
238  pfs1 = stats_prefix_find("abc");
239  test_notnull_ptr("initial prefix find", pfs1);
240  test_equals_ull("request counts", 0ULL,
241  pfs1->num_gets + pfs1->num_sets + pfs1->num_deletes + pfs1->num_hits);
242  pfs2 = stats_prefix_find("abc");
243  test_equals_ptr("find of same prefix", pfs1, pfs2);
244  pfs2 = stats_prefix_find("abc:");
245  test_equals_ptr("find of same prefix, ignoring delimiter", pfs1, pfs2);
246  pfs2 = stats_prefix_find("abc:d");
247  test_equals_ptr("find of same prefix, ignoring extra chars", pfs1, pfs2);
248  pfs2 = stats_prefix_find("xyz123");
249  test_notequals_ptr("find of different prefix", pfs1, pfs2);
250  pfs2 = stats_prefix_find("ab:");
251  test_notequals_ptr("find of shorter prefix", pfs1, pfs2);
252 }
253 
254 static void test_prefix_record_get() {
255  PREFIX_STATS *pfs;
256 
257  stats_prefix_record_get("abc:123", 0);
258  pfs = stats_prefix_find("abc:123");
259  test_equals_ull("get count after get #1", 1, pfs->num_gets);
260  test_equals_ull("hit count after get #1", 0, pfs->num_hits);
261  stats_prefix_record_get("abc:456", 0);
262  test_equals_ull("get count after get #2", 2, pfs->num_gets);
263  test_equals_ull("hit count after get #2", 0, pfs->num_hits);
264  stats_prefix_record_get("abc:456", 1);
265  test_equals_ull("get count after get #3", 3, pfs->num_gets);
266  test_equals_ull("hit count after get #3", 1, pfs->num_hits);
267  stats_prefix_record_get("def:", 1);
268  test_equals_ull("get count after get #4", 3, pfs->num_gets);
269  test_equals_ull("hit count after get #4", 1, pfs->num_hits);
270 }
271 
272 static void test_prefix_record_delete() {
273  PREFIX_STATS *pfs;
274 
275  stats_prefix_record_delete("abc:123");
276  pfs = stats_prefix_find("abc:123");
277  test_equals_ull("get count after delete #1", 0, pfs->num_gets);
278  test_equals_ull("hit count after delete #1", 0, pfs->num_hits);
279  test_equals_ull("delete count after delete #1", 1, pfs->num_deletes);
280  test_equals_ull("set count after delete #1", 0, pfs->num_sets);
281  stats_prefix_record_delete("def:");
282  test_equals_ull("delete count after delete #2", 1, pfs->num_deletes);
283 }
284 
285 static void test_prefix_record_set() {
286  PREFIX_STATS *pfs;
287 
288  stats_prefix_record_set("abc:123");
289  pfs = stats_prefix_find("abc:123");
290  test_equals_ull("get count after set #1", 0, pfs->num_gets);
291  test_equals_ull("hit count after set #1", 0, pfs->num_hits);
292  test_equals_ull("delete count after set #1", 0, pfs->num_deletes);
293  test_equals_ull("set count after set #1", 1, pfs->num_sets);
294  stats_prefix_record_delete("def:");
295  test_equals_ull("set count after set #2", 1, pfs->num_sets);
296 }
297 
298 static void test_prefix_dump() {
299  int hashval = hash("abc", 3, 0) % PREFIX_HASH_SIZE;
300  char tmp[500];
301  char *expected;
302  int keynum;
303  int length;
304 
305  test_equals_str("empty stats", "END\r\n", stats_prefix_dump(&length));
306  test_equals_int("empty stats length", 5, length);
307  stats_prefix_record_set("abc:123");
308  expected = "PREFIX abc get 0 hit 0 set 1 del 0\r\nEND\r\n";
309  test_equals_str("stats after set", expected, stats_prefix_dump(&length));
310  test_equals_int("stats length after set", strlen(expected), length);
311  stats_prefix_record_get("abc:123", 0);
312  expected = "PREFIX abc get 1 hit 0 set 1 del 0\r\nEND\r\n";
313  test_equals_str("stats after get #1", expected, stats_prefix_dump(&length));
314  test_equals_int("stats length after get #1", strlen(expected), length);
315  stats_prefix_record_get("abc:123", 1);
316  expected = "PREFIX abc get 2 hit 1 set 1 del 0\r\nEND\r\n";
317  test_equals_str("stats after get #2", expected, stats_prefix_dump(&length));
318  test_equals_int("stats length after get #2", strlen(expected), length);
319  stats_prefix_record_delete("abc:123");
320  expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\nEND\r\n";
321  test_equals_str("stats after del #1", expected, stats_prefix_dump(&length));
322  test_equals_int("stats length after del #1", strlen(expected), length);
323 
324  /* The order of results might change if we switch hash functions. */
325  stats_prefix_record_delete("def:123");
326  expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\n"
327  "PREFIX def get 0 hit 0 set 0 del 1\r\n"
328  "END\r\n";
329  test_equals_str("stats after del #2", expected, stats_prefix_dump(&length));
330  test_equals_int("stats length after del #2", strlen(expected), length);
331 
332  /* Find a key that hashes to the same bucket as "abc" */
333  for (keynum = 0; keynum < PREFIX_HASH_SIZE * 100; keynum++) {
334  snprintf(tmp, sizeof(tmp), "%d", keynum);
335  if (hashval == hash(tmp, strlen(tmp), 0) % PREFIX_HASH_SIZE) {
336  break;
337  }
338  }
339  stats_prefix_record_set(tmp);
340  snprintf(tmp, sizeof(tmp),
341  "PREFIX %d get 0 hit 0 set 1 del 0\r\n"
342  "PREFIX abc get 2 hit 1 set 1 del 1\r\n"
343  "PREFIX def get 0 hit 0 set 0 del 1\r\n"
344  "END\r\n", keynum);
345  test_equals_str("stats with two stats in one bucket",
346  tmp, stats_prefix_dump(&length));
347  test_equals_int("stats length with two stats in one bucket",
348  strlen(tmp), length);
349 }
350 
351 static void run_test(char *what, void (*func)(void)) {
352  current_test = what;
353  test_count = fail_count = 0;
354  puts(what);
355  fflush(stdout);
356 
357  stats_prefix_clear();
358  (func)();
359  printf("\t%d / %d pass\n", (test_count - fail_count), test_count);
360 }
361 
362 /* In case we're compiled in thread mode */
363 void mt_stats_lock() { }
364 void mt_stats_unlock() { }
365 
366 main(int argc, char **argv) {
367  stats_prefix_init();
368  settings.prefix_delimiter = ':';
369  run_test("stats_prefix_find", test_prefix_find);
370  run_test("stats_prefix_record_get", test_prefix_record_get);
371  run_test("stats_prefix_record_delete", test_prefix_record_delete);
372  run_test("stats_prefix_record_set", test_prefix_record_set);
373  run_test("stats_prefix_dump", test_prefix_dump);
374 }
375 
376 #endif