static void test_prefix_dump() { int hashval = mc_hash("abc", 3, 0) % PREFIX_HASH_SIZE; char tmp[500]; char *expected; int keynum; int length; test_equals_str("empty stats", "END\r\n", stats_prefix_dump(&length)); test_equals_int("empty stats length", 5, length); stats_prefix_record_set("abc:123"); expected = "PREFIX abc get 0 hit 0 set 1 del 0\r\nEND\r\n"; test_equals_str("stats after set", expected, stats_prefix_dump(&length)); test_equals_int("stats length after set", strlen(expected), length); stats_prefix_record_get("abc:123", 0); expected = "PREFIX abc get 1 hit 0 set 1 del 0\r\nEND\r\n"; test_equals_str("stats after get #1", expected, stats_prefix_dump(&length)); test_equals_int("stats length after get #1", strlen(expected), length); stats_prefix_record_get("abc:123", 1); expected = "PREFIX abc get 2 hit 1 set 1 del 0\r\nEND\r\n"; test_equals_str("stats after get #2", expected, stats_prefix_dump(&length)); test_equals_int("stats length after get #2", strlen(expected), length); stats_prefix_record_delete("abc:123"); expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\nEND\r\n"; test_equals_str("stats after del #1", expected, stats_prefix_dump(&length)); test_equals_int("stats length after del #1", strlen(expected), length); /* The order of results might change if we switch hash functions. */ stats_prefix_record_delete("def:123"); expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\n" "PREFIX def get 0 hit 0 set 0 del 1\r\n" "END\r\n"; test_equals_str("stats after del #2", expected, stats_prefix_dump(&length)); test_equals_int("stats length after del #2", strlen(expected), length); /* Find a key that hashes to the same bucket as "abc" */ for (keynum = 0; keynum < PREFIX_HASH_SIZE * 100; keynum++) { snprintf(tmp, sizeof(tmp), "%d", keynum); if (hashval == mc_hash(tmp, strlen(tmp), 0) % PREFIX_HASH_SIZE) { break; } } stats_prefix_record_set(tmp); snprintf(tmp, sizeof(tmp), "PREFIX %d get 0 hit 0 set 1 del 0\r\n" "PREFIX abc get 2 hit 1 set 1 del 1\r\n" "PREFIX def get 0 hit 0 set 0 del 1\r\n" "END\r\n", keynum); test_equals_str("stats with two stats in one bucket", tmp, stats_prefix_dump(&length)); test_equals_int("stats length with two stats in one bucket", strlen(tmp), length); }
item *assoc_find(const char *key, const size_t nkey) { uint32_t hv = mc_hash(key, nkey, 0); item *it; unsigned int oldbucket; if (expanding && (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket) { it = old_hashtable[oldbucket]; } else { it = primary_hashtable[hv & hashmask(hashpower)]; } item *ret = NULL; int depth = 0; while (it) { if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == 0)) { ret = it; break; } it = it->h_next; ++depth; } MEMCACHED_ASSOC_FIND(key, nkey, depth); return ret; }
/* Note: this isn't an assoc_update. The key must not already exist to call this */ int assoc_insert(item *it) { uint32_t hv; unsigned int oldbucket; assert(assoc_find(ITEM_key(it), it->nkey) == 0); /* shouldn't have duplicately named things defined */ hv = mc_hash(ITEM_key(it), it->nkey, 0); if (expanding && (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket) { it->h_next = old_hashtable[oldbucket]; old_hashtable[oldbucket] = it; } else { it->h_next = primary_hashtable[hv & hashmask(hashpower)]; primary_hashtable[hv & hashmask(hashpower)] = it; } hash_items++; if (! expanding && hash_items > (hashsize(hashpower) * 3) / 2) { assoc_expand(); } MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey, hash_items); return 1; }
int stats_prefix_delete(const char *prefix, const size_t nprefix) { PREFIX_STATS *curr, *next, *prev; int hidx; int ret = -1; STATS_LOCK(); if (nprefix == 0) { hidx = mc_hash(prefix, nprefix, 0) % PREFIX_HASH_SIZE; prev = NULL; for (curr = prefix_stats[hidx]; curr != NULL; prev = curr, curr = curr->next) { if (curr->prefix_len == 0) break; } if (curr != NULL) { /* found */ if (prev == NULL) prefix_stats[hidx] = curr->next; else prev->next = curr->next; num_prefixes--; total_prefix_size -= strlen(null_prefix_str); free(curr->prefix); free(curr); ret = 0; } } else { /* nprefix > 0 */ // Full scan for sub-prefixies (we would fix it in future) for (hidx = 0; hidx < PREFIX_HASH_SIZE; hidx++) { prev = NULL; for (curr = prefix_stats[hidx]; curr != NULL; curr = next) { next = curr->next; if ((curr->prefix_len >= nprefix && strncmp(curr->prefix, prefix, nprefix) == 0) && (curr->prefix_len == nprefix || *(curr->prefix+nprefix)==settings.prefix_delimiter)) { if (prev == NULL) prefix_stats[hidx] = curr->next; else prev->next = curr->next; num_prefixes--; total_prefix_size -= curr->prefix_len; free(curr->prefix); free(curr); ret = 0; } else { prev = curr; } } } } STATS_UNLOCK(); return ret; }
static void *assoc_maintenance_thread(void *arg) { while (do_run_maintenance_thread) { int ii = 0; /* Lock the cache, and bulk move multiple buckets to the new * hash table. */ pthread_mutex_lock(&cache_lock); for (ii = 0; ii < hash_bulk_move && expanding; ++ii) { item *it, *next; int bucket; for (it = old_hashtable[expand_bucket]; NULL != it; it = next) { next = it->h_next; bucket = mc_hash(ITEM_key(it), it->nkey, 0) & hashmask(hashpower); it->h_next = primary_hashtable[bucket]; primary_hashtable[bucket] = it; } old_hashtable[expand_bucket] = NULL; expand_bucket++; if (expand_bucket == hashsize(hashpower - 1)) { expanding = false; free(old_hashtable); if (settings.verbose > 1) fprintf(stderr, "Hash table expansion done\n"); } } if (!expanding) { /* We are done expanding.. just wait for next invocation */ pthread_cond_wait(&maintenance_cond, &cache_lock); } pthread_mutex_unlock(&cache_lock); } return NULL; }
/*@null@*/ static PREFIX_STATS *stats_prefix_find(const char *key, const size_t nkey) { PREFIX_STATS *pfs; uint32_t hashval; size_t length; char *token = NULL; int i = 0; int prefix_depth = 0; assert(key != NULL); while ((token = memchr(key + i + 1, settings.prefix_delimiter, nkey - i - 1)) != NULL) { i = token - key; prefix_depth++; if (prefix_depth >= PREFIX_MAX_DEPTH) { break; } } if (prefix_depth <= 0) { length = 0; } else { length = i; } hashval = mc_hash(key, length, 0) % PREFIX_HASH_SIZE; for (pfs = prefix_stats[hashval]; NULL != pfs; pfs = pfs->next) { if ((pfs->prefix_len==length) && (length==0 || strncmp(pfs->prefix, key, length)==0)) return pfs; } if (length > 0) { if (!mc_isvalidname(key, length)) { /* Invalid prefix name */ return NULL; } } pfs = calloc(sizeof(PREFIX_STATS), 1); if (NULL == pfs) { perror("Can't allocate space for stats structure: calloc"); return NULL; } pfs->prefix = malloc(length + 1); if (NULL == pfs->prefix) { perror("Can't allocate space for copy of prefix: malloc"); free(pfs); return NULL; } if (length > 0) strncpy(pfs->prefix, key, length); pfs->prefix[length] = '\0'; /* because strncpy() sucks */ pfs->prefix_len = length; pfs->next = prefix_stats[hashval]; prefix_stats[hashval] = pfs; num_prefixes++; if (length > 0) total_prefix_size += length; else /* length == 0 */ total_prefix_size += strlen(null_prefix_str); return pfs; }