summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author郑超 <[email protected]>2024-02-29 06:43:54 +0000
committer郑超 <[email protected]>2024-02-29 06:43:54 +0000
commit6b49d228c93be4335aa95cd70a16c3517496554d (patch)
tree127bbddc3b74bf10e0dd1dfa00f588eba0e0f4af
parenta75b7b6fec27f5067e19b08810516f4db2c96ac8 (diff)
Bloom Filter and Token bucket refill period customization.v4.1.0
-rw-r--r--CRDT/CMakeLists.txt15
-rw-r--r--CRDT/ap_bloom.c785
-rw-r--r--CRDT/ap_bloom.h78
-rw-r--r--CRDT/basic_crdt_gtest.cpp (renamed from CRDT/crdt_base_gtest.cpp)666
-rw-r--r--CRDT/bulk_token_bucket.c103
-rw-r--r--CRDT/bulk_token_bucket.h30
-rw-r--r--CRDT/crdt_utils.h4
-rw-r--r--CRDT/fair_token_bucket.c82
-rw-r--r--CRDT/fair_token_bucket.h25
-rw-r--r--CRDT/oc_token_bucket.c49
-rw-r--r--CRDT/oc_token_bucket.h29
-rw-r--r--CRDT/probabilistic_crdt_gtest.cpp1067
-rw-r--r--CRDT/st_hyperloglog.c578
-rw-r--r--CRDT/st_hyperloglog.h24
-rw-r--r--CRDT/tb_crdt_gtest.cpp (renamed from CRDT/crdt_tb_gtest.cpp)822
-rw-r--r--CRDT/token_bucket_common.c19
-rw-r--r--CRDT/token_bucket_common.h7
-rw-r--r--docs/command_toc.md81
-rw-r--r--docs/commands.md781
-rw-r--r--docs/commands/bloom_filter.md103
-rw-r--r--docs/commands/cluster.md20
-rw-r--r--docs/commands/generic.md92
-rw-r--r--docs/commands/hash.md117
-rw-r--r--docs/commands/set.md77
-rw-r--r--docs/commands/string_and_integer.md72
-rw-r--r--docs/commands/token_bucket.md219
-rw-r--r--docs/commands/trouble_shooting.md105
-rw-r--r--docs/crdt.md58
-rw-r--r--docs/imgs/thread_model.svg2
-rw-r--r--include/swarmkv/swarmkv.h8
-rw-r--r--readme.md4
-rw-r--r--src/CMakeLists.txt6
-rw-r--r--src/swarmkv.c43
-rw-r--r--src/swarmkv_api.c42
-rw-r--r--src/swarmkv_common.c48
-rw-r--r--src/swarmkv_common.h4
-rw-r--r--src/swarmkv_error.h3
-rw-r--r--src/swarmkv_keyspace.c5
-rw-r--r--src/swarmkv_store.c9
-rw-r--r--src/swarmkv_store.h3
-rw-r--r--src/t_bloom_filter.c227
-rw-r--r--src/t_bloom_filter.h9
-rw-r--r--src/t_token_bucket.c107
-rw-r--r--test/swarmkv_gtest.cpp445
-rw-r--r--test/test_utils.h3
45 files changed, 4720 insertions, 2356 deletions
diff --git a/CRDT/CMakeLists.txt b/CRDT/CMakeLists.txt
index 991b5b5..412fc7e 100644
--- a/CRDT/CMakeLists.txt
+++ b/CRDT/CMakeLists.txt
@@ -1,19 +1,24 @@
add_definitions(-D_GNU_SOURCE)
add_definitions(-fPIC)
-add_library(CRDT lww_register.c pn_counter.c or_map.c or_set.c cm_sketch.c st_hyperloglog.c
+add_library(CRDT lww_register.c pn_counter.c or_map.c or_set.c cm_sketch.c st_hyperloglog.c ap_bloom.c
g_array.c token_bucket_common.c oc_token_bucket.c fair_token_bucket.c bulk_token_bucket.c)
include_directories(${PROJECT_SOURCE_DIR}/deps/mpack
${PROJECT_SOURCE_DIR}/deps/uthash
${PROJECT_SOURCE_DIR}/deps/xxhash)
-add_executable(CRDT_base_gtest crdt_base_gtest.cpp
+add_executable(basic_CRDT_gtest basic_crdt_gtest.cpp
${PROJECT_SOURCE_DIR}/deps/mpack/mpack.c
${PROJECT_SOURCE_DIR}/deps/xxhash/xxhash.c)
-target_link_libraries(CRDT_base_gtest CRDT gtest-static uuid)
+target_link_libraries(basic_CRDT_gtest CRDT gtest-static uuid)
-add_executable(CRDT_tb_gtest crdt_tb_gtest.cpp
+add_executable(tb_CRDT_gtest tb_crdt_gtest.cpp
${PROJECT_SOURCE_DIR}/deps/mpack/mpack.c
${PROJECT_SOURCE_DIR}/deps/xxhash/xxhash.c)
-target_link_libraries(CRDT_tb_gtest CRDT gtest-static uuid) \ No newline at end of file
+target_link_libraries(tb_CRDT_gtest CRDT gtest-static uuid)
+
+add_executable(probabilistic_CRDT_gtest probabilistic_crdt_gtest.cpp
+ ${PROJECT_SOURCE_DIR}/deps/mpack/mpack.c
+ ${PROJECT_SOURCE_DIR}/deps/xxhash/xxhash.c)
+target_link_libraries(probabilistic_CRDT_gtest CRDT gtest-static uuid) \ No newline at end of file
diff --git a/CRDT/ap_bloom.c b/CRDT/ap_bloom.c
new file mode 100644
index 0000000..a7356f6
--- /dev/null
+++ b/CRDT/ap_bloom.c
@@ -0,0 +1,785 @@
+#include "ap_bloom.h"
+#include "crdt_utils.h"
+#include "xxhash.h"
+#include "utlist.h"
+#include "utarray.h"
+
+#include <math.h> // log, ceil
+#include <stdio.h> // printf
+#include <assert.h> // assert
+#include <stdlib.h> // calloc
+#include <string.h> // memset
+#include <unistd.h> // write
+#include <stdint.h> //uint64_t
+#include <stdbool.h>
+/* source
+ * https://github.com/RedisBloom/RedisBloom/blob/AgePartitionedBF/contrib/agingBloom.c
+*/
+
+#define SLICE_EXPANSION 2
+#define FILL_RATIO_THRESHOLD 0.1
+#define MIN_CAPACITY 1000
+// Use double hashing for fast hashing,
+// reference: Kirsch, Adam, and Michael Mitzenmacher. "Less hashing, same performance: Building a better bloom filter."
+// Algorithms–ESA 2006: 14th Annual European Symposium, Zurich, Switzerland, September 11-13, 2006. Proceedings 14. Springer Berlin Heidelberg, 2006.
+// https://www.eecs.harvard.edu/~michaelm/postscripts/rsa2008.pdf
+struct bloom_hashval
+{
+ uint64_t a;
+ uint64_t b;
+};
+struct bloom_hashval bloom_calc_hash(const void *buffer, int len)
+{
+ struct bloom_hashval rv;
+ rv.a=XXH3_64bits_withSeed(buffer, len, 0x9747b28c);
+ rv.b=XXH3_64bits_withSeed(buffer, len, rv.a);
+ return rv;
+}
+
+
+//The test_bit_set_bit() code snap is from RedisBloom (BSD License).
+//Source https://github.com/RedisBloom/RedisBloom/blob/master/deps/bloom/bloom.c
+#define MODE_READ 0
+#define MODE_WRITE 1
+
+//return 1 if the bit is already set, 0 if it was not set
+inline static int test_bit_set_bit(unsigned char *buf, uint64_t x, int mode) {
+ uint64_t byte = x >> 3;
+ uint8_t mask = 1 << (x % 8);
+ uint8_t c = buf[byte]; // expensive memory access
+
+ if (c & mask) {
+ return 1;
+ } else {
+ if (mode == MODE_WRITE) {
+ buf[byte] = c | mask;
+ }
+ return 0;
+ }
+}
+
+struct ap_slice
+{
+ int hash_index;
+ int popcount; //the number of 1-bits in the slice
+ int slice_size; //in bytes
+ struct timeval last_insert;
+ struct timeval first_insert;
+ unsigned char * data;
+ struct ap_slice * next;
+};
+static struct ap_slice *ap_slice_new(int slice_size, int hash_index)
+{
+ struct ap_slice *slice = ALLOC(struct ap_slice, 1);
+ slice->slice_size = slice_size;
+ assert(slice_size % sizeof(unsigned long long) == 0);
+ //ap_slice_chain_deserialize may alloc slice_size=0
+ if(slice_size > 0) slice->data = ALLOC(unsigned char, slice_size);
+ slice->hash_index = hash_index;
+ slice->popcount = 0;
+ return slice;
+}
+static void ap_slice_free(struct ap_slice *slice)
+{
+ free(slice->data);
+ slice->data = NULL;
+ free(slice);
+}
+void ap_slice_chain_info(const struct ap_slice *head, double *fill_ratio, int *slice_num)
+{
+ const struct ap_slice *slice=NULL;
+ *fill_ratio=0;
+ *slice_num=0;
+ LL_FOREACH(head, slice)
+ {
+ *fill_ratio = MAX( *fill_ratio, (double)slice->popcount / (slice->slice_size<<3));
+ (*slice_num) ++;
+ if(slice->next)
+ {
+ assert(slice->slice_size / slice->next->slice_size == SLICE_EXPANSION);
+ }
+ }
+ if(*slice_num > 1)
+ {
+ assert(*fill_ratio >= FILL_RATIO_THRESHOLD);
+ }
+ return;
+}
+void ap_slice_sanity(const struct ap_slice *head)
+{
+ const struct ap_slice *slice=NULL;
+ LL_FOREACH(head, slice)
+ {
+ if(slice->next)
+ {
+ assert(slice->slice_size / slice->next->slice_size == SLICE_EXPANSION);
+ }
+ assert(slice->first_insert.tv_sec >0 || slice->popcount == 0);
+ }
+}
+static int ap_slice_cmp(const struct ap_slice *a, const struct ap_slice *b)
+{
+ //bigger size first
+ return b->slice_size - a->slice_size;
+}
+static struct ap_slice * ap_slice_duplicate(const struct ap_slice *slice)
+{
+ struct ap_slice *new_slice = ap_slice_new(slice->slice_size, slice->hash_index);
+ memcpy(new_slice, slice, offsetof(struct ap_slice, data));
+ memcpy(new_slice->data, slice->data, slice->slice_size);
+ return new_slice;
+}
+#define HASH_TO_OFFSET(hash, slice) ((hash.a + slice->hash_index * hash.b) % (slice->slice_size<<3))
+void ap_slice_add_hash(struct ap_slice *slice, struct bloom_hashval hash, struct timeval now)
+{
+ int index = HASH_TO_OFFSET(hash, slice);
+ int added = !test_bit_set_bit(slice->data, index, MODE_WRITE);
+
+ slice->last_insert = now;
+ if(slice->popcount == 0)
+ {
+ slice->first_insert = now;
+ }
+ slice->popcount += added;
+ return;
+}
+int ap_slice_check_hash(const struct ap_slice *slice, struct bloom_hashval hash)
+{
+ //int index = (hash.a + slice->hash_index * hash.b) % (slice->slice_size<<3);
+ int index = HASH_TO_OFFSET(hash, slice);
+ return test_bit_set_bit(slice->data, index, MODE_READ);
+}
+struct ap_slice_event
+{
+ struct timeval timestamp;
+ int slice_size;
+ int hash_index;
+ int event;//1: start, -1: end
+};
+int slice_event_cmp(const void *a, const void *b)
+{
+ const struct ap_slice_event *ea = (struct ap_slice_event*)a;
+ const struct ap_slice_event *eb = (struct ap_slice_event*)b;
+ if(timercmp(&ea->timestamp, &eb->timestamp, <))
+ {
+ return -1;
+ }
+ if(timercmp(&ea->timestamp, &eb->timestamp, >))
+ {
+ return 1;
+ }
+ return eb->event - ea->event;
+}
+UT_icd slice_event_icd = {sizeof(struct ap_slice_event), NULL, NULL, NULL};
+struct ap_state
+{
+ int consecutive_matches;
+ int visited_slices;
+ int hash_num;
+ UT_array slice_time_events;
+};
+void ap_state_init(struct ap_state *state, int hash_num)
+{
+ state->consecutive_matches=0;
+ state->visited_slices=0;
+ state->hash_num=hash_num;
+ utarray_init(&state->slice_time_events, &slice_event_icd);
+ utarray_reserve(&state->slice_time_events, hash_num*2);
+}
+void ap_state_clear(struct ap_state *state)
+{
+ state->consecutive_matches=0;
+ state->visited_slices=0;
+ utarray_clear(&state->slice_time_events);
+}
+void ap_state_done(struct ap_state *state)
+{
+ utarray_done(&state->slice_time_events);
+}
+int ap_state_is_match(struct ap_state *state)
+{
+ int counter=0;
+ struct ap_slice_event *ev=NULL;
+ if(state->consecutive_matches >= state->hash_num)
+ {
+ if(state->visited_slices==state->consecutive_matches)//fastpath for no slice expansion
+ {
+ return 1;
+ }
+ utarray_sort(&state->slice_time_events, slice_event_cmp);
+ while((ev=(struct ap_slice_event*)utarray_next(&state->slice_time_events, ev)))
+ {
+ counter += ev->event;
+ if(counter == state->hash_num)
+ {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+static void ap_slice_chain_check_hash(const struct ap_slice *head, struct bloom_hashval hash, struct ap_state *state)
+{
+ //In a stackable (scalable) Bloom filter, checking for membership now involves inspecting each layer for presence.
+ const struct ap_slice *slice=NULL;
+ struct ap_slice_event ev;
+ int found=0;
+ LL_FOREACH(head, slice)
+ {
+ if(ap_slice_check_hash(slice, hash))
+ {
+ ev.timestamp = slice->first_insert;
+ ev.event = 1;
+ ev.slice_size=slice->slice_size;
+ ev.hash_index=slice->hash_index;
+ utarray_push_back(&state->slice_time_events, &ev);
+ ev.timestamp = slice->last_insert;
+ ev.event = -1;
+ ev.slice_size = slice->slice_size;
+ ev.hash_index = slice->hash_index;
+ utarray_push_back(&state->slice_time_events, &ev);
+ found=1;
+ }
+ state->visited_slices++;
+ }
+ if(found)
+ {
+ state->consecutive_matches++;
+ }
+ else
+ {
+ ap_state_clear(state);
+ }
+ return;
+}
+
+ /* Return:
+ * -------
+ * 0 - element was not present and was added
+ * 1 - element (or a collision) had already been added previously
+ */
+static void ap_slice_chain_add_hash(struct ap_slice **head, struct bloom_hashval hash, struct timeval now)
+{
+ //add new slice if the current slice is full
+ if( (double) (*head)->popcount / ((*head)->slice_size<<3) > FILL_RATIO_THRESHOLD)
+ {
+ struct ap_slice *new_slice = ap_slice_new((*head)->slice_size * SLICE_EXPANSION, (*head)->hash_index);
+ LL_PREPEND((*head), new_slice);
+ }
+ //Add it to the current (head) slice.
+ ap_slice_add_hash(*head, hash, now);
+ return;
+}
+void ap_slice_chain_first_insert_time(const struct ap_slice *head, struct timeval *first_insert)
+{
+ const struct ap_slice *slice=NULL;
+ *first_insert = head->first_insert;
+ LL_FOREACH(head, slice)
+ {
+ if(timercmp(&slice->first_insert, first_insert, <))
+ {
+ *first_insert = slice->first_insert;
+ }
+ }
+ return;
+}
+static void ap_slice_chain_free(struct ap_slice *head)
+{
+ struct ap_slice *slice=NULL, *tmp=NULL;
+ LL_FOREACH_SAFE(head, slice, tmp)
+ {
+ LL_DELETE(head, slice);
+ ap_slice_free(slice);
+ }
+}
+static struct ap_slice *ap_slice_chain_duplicate(const struct ap_slice *src)
+{
+ struct ap_slice *new_head=NULL;
+ const struct ap_slice *slice=NULL;
+ LL_FOREACH(src, slice)
+ {
+ struct ap_slice * new_slice=ap_slice_duplicate(slice);
+ LL_APPEND(new_head, new_slice);
+ }
+ return new_head;
+}
+static size_t ap_slice_chain_mem_size(const struct ap_slice *head)
+{
+ size_t sz=0;
+ const struct ap_slice *slice=NULL;
+ LL_FOREACH(head, slice)
+ {
+ sz += sizeof(struct ap_slice);
+ sz += slice->slice_size;
+ }
+ return sz;
+}
+struct slice_chain_header
+{
+ int chain_sequence;
+ int slice_number;
+ size_t chain_size;
+};
+static size_t ap_slice_chain_serialize_size(const struct ap_slice *head)
+{
+ size_t sz=0;
+ const struct ap_slice *slice=NULL;
+ sz += sizeof(struct slice_chain_header);
+ LL_FOREACH(head, slice)
+ {
+ sz += offsetof(struct ap_slice, data);
+ sz += slice->slice_size;
+ }
+ return sz;
+}
+static size_t ap_slice_chain_serialize(const struct ap_slice *head, int chain_sequence, char *buffer, size_t buffer_sz)
+{
+ size_t offset=0;
+
+ struct slice_chain_header *header=(struct slice_chain_header*)(buffer+offset);
+ header->chain_sequence=chain_sequence;
+ header->chain_size=0;
+ header->slice_number=0;
+ offset += sizeof(struct slice_chain_header);
+ const struct ap_slice *slice=NULL;
+ LL_FOREACH(head, slice)
+ {
+ memcpy(buffer+offset, slice, offsetof(struct ap_slice, data));
+ header->chain_size += offsetof(struct ap_slice, data);
+ offset += offsetof(struct ap_slice, data);
+ memcpy(buffer+offset, slice->data, slice->slice_size);
+ offset += slice->slice_size;
+ header->chain_size += slice->slice_size;
+ header->slice_number++;
+ if(slice->next)
+ {
+ assert(slice->slice_size / slice->next->slice_size == SLICE_EXPANSION);
+ }
+ }
+ assert(offset<=buffer_sz);
+ return offset;
+}
+static struct ap_slice *ap_slice_chain_deserialize(const char *buffer, size_t buffer_sz)
+{
+ struct slice_chain_header *header=(struct slice_chain_header*)buffer;
+ size_t offset=sizeof(struct slice_chain_header);
+ struct ap_slice *head=NULL;
+ for(size_t i=0; i<header->slice_number; i++)
+ {
+ struct ap_slice *slice = ap_slice_new(0, 0);
+ memcpy(slice, buffer+offset, offsetof(struct ap_slice, data));
+ offset += offsetof(struct ap_slice, data);
+ slice->data = (unsigned char*) malloc(slice->slice_size);
+ memcpy(slice->data, buffer+offset, slice->slice_size);
+ offset += slice->slice_size;
+ LL_APPEND(head, slice);
+ if(head->next)
+ {
+ assert(head->slice_size / head->next->slice_size == SLICE_EXPANSION);
+ }
+ }
+ return head;
+}
+static void ap_slice_merge(struct ap_slice *dst, const struct ap_slice *src)
+{
+ assert(dst->hash_index == src->hash_index);
+ assert(dst->slice_size == src->slice_size);
+ if(src->popcount == 0)
+ {
+ return;
+ }
+ if(dst->popcount == 0)
+ {
+ memcpy(dst, src, offsetof(struct ap_slice, data));
+ memcpy(dst->data, src->data, src->slice_size);
+ return;
+ }
+ int popcnt=0;
+ for(int i=0; i<dst->slice_size/sizeof(unsigned long long); i++)
+ {
+ ((unsigned long long*)dst->data)[i] |= ((unsigned long long*)src->data)[i];
+ popcnt += __builtin_popcountll( ((unsigned long long*)dst->data)[i]);
+ }
+ dst->popcount = popcnt;
+ if(timercmp(&(dst->last_insert), &(src->last_insert), <))
+ {
+ dst->last_insert = src->last_insert;
+ }
+ if(timercmp(&(dst->first_insert), &(src->first_insert), >))
+ {
+ dst->first_insert = src->first_insert;
+ }
+ return;
+}
+static void ap_slice_chain_merge(struct ap_slice **dst_head, const struct ap_slice *src_head)
+{
+ const struct ap_slice *src_slice=NULL;
+ int merged=0;
+
+ ap_slice_sanity(*dst_head);
+ ap_slice_sanity(src_head);
+
+ LL_FOREACH(src_head, src_slice)
+ {
+ struct ap_slice *dst_slice=NULL;
+ merged=0;
+ LL_FOREACH(*dst_head, dst_slice)
+ {
+ if(src_slice->slice_size == dst_slice->slice_size)
+ {
+ ap_slice_merge(dst_slice, src_slice);
+ merged=1;
+ break;
+ }
+ }
+ if(!merged)
+ {
+ struct ap_slice *new_slice = ap_slice_duplicate(src_slice);
+ LL_APPEND(*dst_head, new_slice);
+ }
+ }
+ LL_SORT(*dst_head, ap_slice_cmp);
+ ap_slice_sanity(*dst_head);
+}
+struct AP_configuration
+{
+ double error;
+ long long capacity;
+ long long time_window_ms;
+ long long time_slice_num;
+ struct timeval last_cfg;
+};
+struct AP_bloom
+{
+ //high level variables determined by caller
+ struct AP_configuration cfg;
+
+ //low level variables caculated by the call configure
+ int hash_num; //k
+ int chain_num; //hash_num+time_slice_num
+ int default_slice_size; // in bytes
+
+ //runtime variables
+ int cursor;
+ struct timeval last_slide;
+ struct ap_slice **heads; //hash_num+timeframe_num
+};
+#define DBL_EPSILON 2.2204460492503131e-16
+bool definitelyLessThan(float a, float b)
+{
+ return (b - a) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * DBL_EPSILON);
+}
+struct AP_bloom *AP_bloom_new(struct timeval now, double error_rate, long long capacity, long long time_window_ms, long long time_slice_num)
+{
+ if (time_slice_num < 0)
+ {
+ return NULL;
+ }
+ struct AP_bloom *bloom = ALLOC(struct AP_bloom, 1);
+ bloom->cfg.time_slice_num = time_slice_num;
+ bloom->hash_num = ceil(log2(1/error_rate));
+ if(capacity < MIN_CAPACITY)
+ {
+ capacity = MIN_CAPACITY;
+ }
+ bloom->cfg.error = error_rate;
+ bloom->cfg.capacity = capacity;
+ bloom->cfg.time_window_ms = time_window_ms;
+ bloom->cfg.last_cfg = now;
+ bloom->cfg.time_slice_num = time_slice_num;
+
+ bloom->last_slide = now;
+ bloom->cursor = 0;
+ bloom->default_slice_size = ceil(capacity/log(2));
+ bloom->default_slice_size = ceil((double)bloom->default_slice_size/64)*64/8;
+
+ bloom->chain_num = bloom->hash_num + bloom->cfg.time_slice_num;
+ bloom->heads = ALLOC(struct ap_slice*, bloom->chain_num);
+ for(int i=0; i<bloom->chain_num; i++)
+ {
+ bloom->heads[i] = ap_slice_new(bloom->default_slice_size, i % bloom->chain_num);
+ }
+
+ return bloom;
+}
+
+static void slide_time(struct AP_bloom *bloom, struct timeval now)
+{
+ int chain_num = bloom->chain_num;
+ long long slide_time_frame_us=bloom->cfg.time_slice_num?(bloom->cfg.time_window_ms*1000/bloom->cfg.time_slice_num):INT64_MAX;
+ long long slide_us=timeval_delta_us(bloom->last_slide, now);
+ long long elapse_us=timeval_delta_us(bloom->cfg.last_cfg, now);
+ long long epoches=elapse_us/slide_time_frame_us;
+
+ if(slide_us < slide_time_frame_us)
+ {
+ return;
+ }
+ slide_us -= slide_us % slide_time_frame_us;
+ int n_slide = slide_us/slide_time_frame_us;
+
+ if(n_slide < bloom->cfg.time_slice_num)
+ {
+ for(int i=0; i<n_slide; i++)
+ {
+ int reset_idx = (bloom->cursor + bloom->hash_num) % chain_num;
+ struct ap_slice *slice = ap_slice_new(bloom->default_slice_size, bloom->heads[reset_idx]->hash_index);
+ ap_slice_chain_free(bloom->heads[reset_idx]);
+ bloom->heads[reset_idx] = slice;
+ bloom->cursor = (bloom->cursor + 1) % chain_num;
+ }
+ }
+ else
+ {
+ for(int i=0; i<chain_num; i++)
+ {
+ struct ap_slice *new_slice=ap_slice_new(bloom->default_slice_size, bloom->heads[i]->hash_index);
+ ap_slice_chain_free(bloom->heads[i]);
+ bloom->heads[i] = new_slice;
+ }
+ bloom->cursor = (bloom->cursor + n_slide) % chain_num;
+ }
+ struct timeval slide_time;
+ slide_time.tv_sec = slide_us/1000/1000;
+ slide_time.tv_usec = slide_us%(1000*1000);
+ timeradd(&bloom->last_slide, &slide_time, &bloom->last_slide);
+
+ assert(bloom->cursor == (epoches % chain_num) || bloom->cursor == (epoches % chain_num + 1) % chain_num);
+ return;
+}
+
+void AP_bloom_add(struct AP_bloom *bloom, struct timeval now, const char *buffer, int len)
+{
+ struct bloom_hashval hash = bloom_calc_hash(buffer, len);
+ if(bloom->cfg.time_window_ms)
+ {
+ slide_time(bloom, now);
+ }
+ for(int i=0; i<bloom->hash_num; i++)
+ {
+ int idx=(bloom->cursor+i) % (bloom->chain_num);
+ ap_slice_chain_add_hash(&(bloom->heads[idx]), hash, now);
+ }
+ return;
+}
+
+int AP_bloom_check(const struct AP_bloom *bloom, struct timeval now, const char *buffer, int len)
+{
+ if(timeval_delta_ms(bloom->cfg.last_cfg, now) < 0)
+ {
+ return 0;
+ }
+ struct bloom_hashval hash = bloom_calc_hash(buffer, len);
+ int chain_num = bloom->chain_num;
+
+ struct ap_state state;
+ ap_state_init(&state, bloom->hash_num);
+ for(int i = 0; i < bloom->hash_num+chain_num; i++)
+ {
+ long long delta_us = timeval_delta_us(bloom->heads[i%chain_num]->last_insert, now);
+ if(bloom->cfg.time_window_ms && delta_us > bloom->cfg.time_window_ms*1000)
+ {
+ ap_state_clear(&state);
+ continue;
+ }
+ ap_slice_chain_check_hash(bloom->heads[i%chain_num], hash, &state);
+ if(ap_state_is_match(&state))
+ {
+ ap_state_done(&state);
+ return 1;
+ }
+ }
+ ap_state_done(&state);
+ return 0;
+}
+void AP_bloom_free(struct AP_bloom *bloom)
+{
+ for(int i=0; i<bloom->chain_num; i++)
+ {
+ ap_slice_chain_free(bloom->heads[i]);
+ bloom->heads[i]=NULL;
+ }
+ free(bloom->heads);
+ bloom->heads=NULL;
+ free(bloom);
+}
+size_t AP_bloom_mem_size(const struct AP_bloom *bloom)
+{
+ size_t sz=0;
+ sz += sizeof(struct AP_bloom);
+ sz += sizeof(struct ap_slice*)*bloom->chain_num;
+ for(int i=0; i<bloom->chain_num; i++)
+ {
+ sz += ap_slice_chain_mem_size(bloom->heads[i]);
+ }
+ return sz;
+}
+size_t AP_bloom_serialize_size(const struct AP_bloom *bloom)
+{
+ size_t sz=0;
+ sz += offsetof(struct AP_bloom, heads);
+ for(int i=0; i<bloom->chain_num; i++)
+ {
+ sz+=ap_slice_chain_serialize_size(bloom->heads[i]);
+ }
+ return sz;
+}
+void AP_bloom_serialize(const struct AP_bloom *bloom, char **blob, size_t *blob_sz)
+{
+ size_t sz=AP_bloom_serialize_size(bloom);
+ size_t offset=0;
+ char *buffer = (char*) malloc(sz);
+ memcpy(buffer, bloom, offsetof(struct AP_bloom, heads));
+ offset += offsetof(struct AP_bloom, heads);
+ for(int i=0; i<bloom->chain_num; i++)
+ {
+ offset += ap_slice_chain_serialize(bloom->heads[i], i, buffer+offset, sz-offset);
+ }
+ assert(offset==sz);
+ *blob_sz=sz;
+ *blob=buffer;
+}
+struct AP_bloom * AP_bloom_deserialize(const char *blob, size_t blob_sz)
+{
+ struct AP_bloom *bloom=ALLOC(struct AP_bloom, 1);
+ size_t offset=0;
+ memcpy(bloom, blob+offset, offsetof(struct AP_bloom, heads));
+ offset += offsetof(struct AP_bloom, heads);
+ bloom->heads=ALLOC(struct ap_slice*, bloom->chain_num);
+ for(int i=0; i<bloom->chain_num; i++)
+ {
+ bloom->heads[i]=ap_slice_chain_deserialize(blob+offset, blob_sz-offset);
+ offset += ap_slice_chain_serialize_size(bloom->heads[i]);
+ }
+ assert(offset==blob_sz);
+ return bloom;
+}
+struct AP_bloom *AP_bloom_replicate(uuid_t uuid, const char *blob, size_t blob_sz)
+{
+ struct AP_bloom *bloom=AP_bloom_deserialize(blob, blob_sz);
+ return bloom;
+}
+void AP_bloom_merge(struct AP_bloom *dst, const struct AP_bloom *src)
+{
+ if(memcmp(&dst->cfg, &src->cfg, sizeof(dst->cfg)))
+ {
+ if(timercmp(&(dst->cfg.last_cfg), &(src->cfg.last_cfg), <))
+ {
+ for(int i=0; i<dst->chain_num; i++)
+ {
+ ap_slice_chain_free(dst->heads[i]);
+ }
+
+ memcpy(dst, src, offsetof(struct AP_bloom, heads));
+ free(dst->heads);
+ dst->heads = ALLOC(struct ap_slice*, dst->chain_num);
+ for(int i=0; i<dst->chain_num; i++)
+ {
+ dst->heads[i] = ap_slice_chain_duplicate(src->heads[i]);
+ }
+ }
+ return;
+ }
+ if(timercmp(&(dst->last_slide), &(src->last_slide), <))
+ {
+ slide_time(dst, src->last_slide);
+ }
+ assert(src->chain_num == dst->chain_num);
+ long long slide_time_frame_us = dst->cfg.time_window_ms?(dst->cfg.time_window_ms*1000/dst->cfg.time_slice_num):0;
+ for(int i=0; i<src->chain_num; i++)
+ {
+ struct timeval src_first_insert, dst_first_insert;
+ ap_slice_chain_first_insert_time(src->heads[i], &src_first_insert);
+ ap_slice_chain_first_insert_time(dst->heads[i], &dst_first_insert);
+ long long delta_us = timeval_delta_us(src_first_insert, dst_first_insert);
+ if(delta_us>slide_time_frame_us)
+ {
+ continue;
+ }
+ ap_slice_chain_merge(&(dst->heads[i]), src->heads[i]);
+ }
+ return;
+}
+void AP_bloom_merge_blob(struct AP_bloom *dst, const char *blob, size_t blob_sz)
+{
+ struct AP_bloom *src=AP_bloom_deserialize(blob, blob_sz);
+ AP_bloom_merge(dst, src);
+ AP_bloom_free(src);
+}
+long long approximate_item_num(const struct AP_bloom *bloom)
+{
+ //https://en.wikipedia.org/wiki/Bloom_filter#Approximating_the_number_of_items_in_a_Bloom_filter
+ //element_num = - (m/k) * ln(1 - popcnt/m)
+ //m is the length (size) of the filter, k is the number of hash functions, and X is the number of bits set to one.
+ long long element_num=0;
+ long long m=0;
+ long long X=0;
+ for(int i=0; i<bloom->chain_num; i++)
+ {
+ const struct ap_slice *slice=NULL;
+ LL_FOREACH(bloom->heads[i], slice)
+ {
+ m += slice->slice_size;
+ X += slice->popcount;
+ }
+ }
+ m=m<<3;//byte size to bit size
+ element_num=ceil(-(m/bloom->chain_num)*log(1-(double)X/m));
+ return element_num;
+}
+long long AP_bloom_cardinality(const struct AP_bloom *bloom)
+{
+ return approximate_item_num(bloom);
+}
+void AP_bloom_info(const struct AP_bloom *bloom, struct AP_bloom_info *info)
+{
+ //The actual error rate is more complexity.
+ //https://mathoverflow.net/questions/288357/number-of-binary-arrays-of-length-n-with-k-consecutive-1s
+ info->error = bloom->cfg.error;
+ info->capacity = bloom->cfg.capacity;
+ info->time_window_ms = bloom->cfg.time_window_ms;
+ info->hash_num = bloom->hash_num;
+ info->time_slice_num = bloom->cfg.time_slice_num;
+ info->total_slice_number = 0;
+ info->max_expand_times = 0;
+ info->fill_ratio = 0;
+ struct timeval first_insert[bloom->chain_num];
+ struct timeval oldest_item=bloom->last_slide;
+ for(int i=0; i<bloom->chain_num; i++)
+ {
+ struct ap_slice *slice=NULL;
+ int slice_num=0;
+ double fill_ratio = 0;
+ ap_slice_chain_info(bloom->heads[i], &fill_ratio, &slice_num);
+ LL_COUNT(bloom->heads[i], slice, slice_num);
+ info->total_slice_number += slice_num;
+ info->max_expand_times = MAX(info->max_expand_times, slice_num);
+ info->fill_ratio = MAX(info->fill_ratio, fill_ratio);
+ ap_slice_chain_first_insert_time(bloom->heads[i], &first_insert[i]);
+ }
+
+ for(int i=0; i<bloom->chain_num+bloom->hash_num; i++)
+ {
+ struct timeval per_window_oldest_item;
+ per_window_oldest_item.tv_sec=0;
+ per_window_oldest_item.tv_usec=0;
+
+ for(int j=0; j<bloom->hash_num; j++)
+ {
+ int idx = (i+j)%bloom->chain_num;
+ //time of per window oldest item is MAX(first_insert[i], ... , first_insert[i+hash_num-1]).
+ if(timercmp(&first_insert[idx], &per_window_oldest_item, >))
+ {
+ per_window_oldest_item = first_insert[idx];
+ }
+ }
+ //time of global oldest item is MIN(per_window_oldest_item).
+ if(timercmp(&per_window_oldest_item, &oldest_item, <))
+ {
+ oldest_item = per_window_oldest_item;
+ }
+ }
+ info->approximate_item_num = approximate_item_num(bloom);
+ info->oldest_item_time = oldest_item;
+ return;
+} \ No newline at end of file
diff --git a/CRDT/ap_bloom.h b/CRDT/ap_bloom.h
new file mode 100644
index 0000000..c40b5b9
--- /dev/null
+++ b/CRDT/ap_bloom.h
@@ -0,0 +1,78 @@
+/*
+ * A CRDT version of Age-Partitioned Bloom-Filer.
+ * Author: [email protected]
+ * Based on:
+ * [1] Ariel Shtul, Carlos Baquero, and Paulo Sérgio Almeida. "Age-partitioned bloom filters." arXiv preprint arXiv:2001.03147 (2020).
+ * [2] Ana Rodrigues, Ariel Shtul, Carlos Baquero, and Paulo Sérgio Almeida. "Time-limited Bloom Filter." Proceedings of the 38th ACM/SIGAPP Symposium on Applied Computing. 2023.
+ */
+#pragma once
+
+#include <stddef.h>
+#include <sys/time.h>
+#include <uuid/uuid.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct AP_bloom;
+/** ***************************************************************************
+ * Create a bloom filter that can keep items within a sliding time window.
+ *
+ * Parameters:
+ * -----------
+ * now - The current time.
+ * capacity - The expected number of items which will be inserted. At least 1000. If the actual number of items exceeds the capacity, the bloom filter will expand.
+ * error_rate - Probability of collision (as long as the capacity is not exceeded).
+ * time_window_ms - The duration of the time window in milliseconds. Items in time range (now-time_window_ms, now] are kept. If time_window_ms is 0, the time window is disabled.
+ * time_slice_num - The number of time slices within the window, which sets the granularity of timeouts to time_window_ms/time_slice_num.
+ *
+ * Return:
+ * -------
+ * Pointer to the created bloom filter - on success
+ * NULL - on failure
+ *
+ */
+struct AP_bloom *AP_bloom_new(struct timeval now, double error_rate, long long capacity, long long time_window_ms, long long time_slice_num);
+void AP_bloom_free(struct AP_bloom *bloom);
+struct AP_bloom_info
+{
+ double error;
+ long long capacity;
+ long long time_window_ms;
+ long long time_slice_num;
+ long long hash_num;
+ long long total_slice_number; //At least hash_num+time_slice_num, and increased after expansion.
+ long long max_expand_times;
+ long long approximate_item_num;
+ double fill_ratio;
+ struct timeval oldest_item_time;
+};
+void AP_bloom_info(const struct AP_bloom *bloom, struct AP_bloom_info *info);
+
+void AP_bloom_add(struct AP_bloom *bloom, struct timeval now, const char *buffer, int len);
+
+ /* Return:
+ * -------
+ * 0 - element is not present
+ * 1 - element is present (or false positive due to collision)
+ */
+int AP_bloom_check(const struct AP_bloom *bloom, struct timeval now, const char *buffer, int len);
+
+
+//Approximate the cardinality of the bloom filter.
+long long AP_bloom_cardinality(const struct AP_bloom *bloom);
+
+struct AP_bloom *AP_bloom_deserialize(const char *blob, size_t blob_sz);
+void AP_bloom_serialize(const struct AP_bloom *bloom, char **blob, size_t *blob_sz);
+void AP_bloom_merge(struct AP_bloom *dst, const struct AP_bloom *src);
+void AP_bloom_merge_blob(struct AP_bloom *dst, const char *blob, size_t blob_sz);
+//The parameter 'uuid' is included for consistency with other CRDT types and is not used.
+struct AP_bloom *AP_bloom_replicate(uuid_t uuid, const char *blob, size_t blob_sz);
+size_t AP_bloom_serialized_size(const struct AP_bloom *bloom);
+size_t AP_bloom_mem_size(const struct AP_bloom *bloom);
+
+#ifdef __cplusplus
+}
+#endif \ No newline at end of file
diff --git a/CRDT/crdt_base_gtest.cpp b/CRDT/basic_crdt_gtest.cpp
index 0b9bf94..1688137 100644
--- a/CRDT/crdt_base_gtest.cpp
+++ b/CRDT/basic_crdt_gtest.cpp
@@ -2,11 +2,8 @@
#include "pn_counter.h"
#include "or_set.h"
#include "or_map.h"
-
-#include "crdt_utils.h"
-#include "cm_sketch.h"
-#include "st_hyperloglog.h"
#include "g_array.h"
+#include "crdt_utils.h"
#include <gtest/gtest.h>
#include <unistd.h> //usleep
@@ -1177,453 +1174,7 @@ TEST(ORMap, Replica32)
}
}
-TEST(CMSketch, Basic)
-{
- uuid_t uuid;
- uuid_generate(uuid);
- struct CM_sketch *cms=CM_sketch_new(uuid);
- int ret=0, n_added=0;
- for(int i=0; i<10; i++)
- {
- ret=CM_sketch_add_n(cms, (char *)&i, sizeof(i), i+1);
- n_added += i+1;
- }
- for(int i=0; i<10; i++)
- {
- ret=CM_sketch_query(cms, (char *)&i, sizeof(i));
- EXPECT_EQ(ret, i+1);
- }
- for(int i=0; i<10; i++)
- {
- ret=CM_sketch_remove_n(cms, (char *)&i, sizeof(i), i);
- n_added -= i;
- }
- for(int i=0; i<10; i++)
- {
- ret=CM_sketch_query(cms, (char *)&i, sizeof(i));
- //EXPECT_EQ(ret, 1);
- }
- struct CM_sketch_info info;
- CM_sketch_info(cms, &info);
- EXPECT_EQ(info.n_element, n_added);
- printf("error_rate: %f confidence: %f\n", info.error_rate, info.confidence);
- CM_sketch_free(cms);
-}
-TEST(CMSketch, I5K)
-{
- uuid_t uuid;
- uuid_generate(uuid);
- struct CM_sketch *cms=CM_sketch_new(uuid);
- long long n_item=5000, ret=0;
- long long base=10000, total_add=0;
- for(long long i=0; i<n_item; i++)
- {
- ret=CM_sketch_add_n(cms, (char *)&i, sizeof(i), base+i);
- total_add+=(base+i);
- }
- struct CM_sketch_info info;
- CM_sketch_info(cms, &info);
- long long pass=0;
- for(long long i=0; i<n_item; i++)
- {
- ret=CM_sketch_query(cms, (char *)&i, sizeof(i));
- //The formal expectation is total_add*info.error_rate
- if(abs(ret-(i+base))< (i+base)*info.error_rate)
- {
- pass++;
- }
- }
- EXPECT_NEAR(pass, n_item, n_item*(1-info.confidence));
- CM_sketch_free(cms);
-}
-static void CMS_sync(struct CM_sketch *list[], size_t n)
-{
- char *blob=NULL;
- size_t blob_sz=0;
-
- for(size_t i=0; i<n; i++)
- {
- CM_sketch_serialize(list[i], &blob, &blob_sz);
- for(size_t j=0; j<n; j++)
- {
- if(j==i) continue;
- CM_sketch_merge_blob(list[j], blob, blob_sz);
- }
- free(blob);
- blob=NULL;
- }
- return;
-}
-TEST(CMSketch, Merge)
-{
- size_t replica_number=2, round=10;
- long long key=1234;
- struct CM_sketch *cms[replica_number];
- uuid_t uuid;
- for(size_t i=0; i<replica_number; i++)
- {
- uuid_generate(uuid);
- cms[i]=CM_sketch_new(uuid);
- }
- for(size_t i=0; i<round; i++)
- {
- CM_sketch_add_n(cms[i%replica_number], (char*) &key, sizeof(key), 1);
- }
- CMS_sync(cms, replica_number);
- int ret=0;
- for(size_t i=0; i<replica_number; i++)
- {
- ret=CM_sketch_query(cms[i], (char *)&key, sizeof(key));
- EXPECT_EQ(ret, round);
- }
- for(size_t i=0; i<replica_number; i++)
- {
- CM_sketch_free(cms[i]);
- }
-}
-TEST(CMSketch, Idempotent)
-{
- size_t replica_number=8, round=10000, i=0;
- struct CM_sketch *cms[replica_number];
- uuid_t uuid;
-
- for(i=0; i<replica_number; i++)
- {
- uuid_generate(uuid);
- cms[i]=CM_sketch_new(uuid);
- }
- int n_added=0;
- for(i=0; i<round; i++)
- {
- CM_sketch_add_n(cms[i%replica_number], (char*) &i, sizeof(i), i);
- n_added+=i;
- }
- CMS_sync(cms, replica_number);
- CMS_sync(cms, replica_number);
- int ret=0;
- size_t success=0;
- struct CM_sketch_info info;
- CM_sketch_info(cms[0], &info);
-
- for(i=0; i<round; i++)
- {
- ret=CM_sketch_query(cms[(i+1)%replica_number], (char*) &i, sizeof(i));
- if((double)ret<((double)i+info.error_rate*n_added))
- {
- success++;
- }
- }
- EXPECT_GE((double)success/round, info.confidence);
- for(i=0; i<replica_number; i++)
- {
- CM_sketch_free(cms[i]);
- }
-}
-long long st_hll_test(unsigned char precision, int actual_count)
-{
- int count=0;
- int key=random();
- struct timeval nouse;
- struct ST_hyperloglog *h=ST_hyperloglog_new(precision, 0, nouse);
- for(int i=0; i<actual_count; i++)
- {
- key++;
- ST_hyperloglog_add(h, (const char *)&key, sizeof(key), nouse);
- }
- count=ST_hyperloglog_count(h);
- ST_hyperloglog_free(h);
- return count;
-}
-
-TEST(STHyperLogLog, Basic)
-{
- double error;
- long long est_count=0;
- long long count=10000;
- unsigned char precision=6;
-
-
- est_count=st_hll_test(precision, count);
- error=ST_hyperloglog_error_for_precision(precision);
- EXPECT_NEAR(est_count, count, error*count);
-
- precision=9;
- count=10000;
- est_count=st_hll_test(precision, count);
- error=ST_hyperloglog_error_for_precision(precision);
- EXPECT_NEAR(est_count, count, error*count);
-
-}
-struct st_hll_case
-{
- unsigned char precision;
- int time_window_s;
- int ideal_count;
- int est_count;
- int n_replica;
-};
-int st_hll_case_print(const struct st_hll_case *st_case, int n_case)
-{
- int success=0;
- double real_error=0, est_error=0;
- printf("prcs\twin\tideal\test\test_err\treal_err\n");
- for(int j=0; j<n_case; j++)
- {
- est_error=ST_hyperloglog_error_for_precision(st_case[j].precision);
- real_error=(double)abs(st_case[j].est_count-st_case[j].ideal_count)/st_case[j].ideal_count;
- printf("%d\t%d\t%d\t%d\t%f\t%f\n", st_case[j].precision,
- st_case[j].time_window_s,
- st_case[j].ideal_count,
- st_case[j].est_count,
- est_error,
- real_error);
- if(real_error <= MAX(0.1, est_error))
- {
- success++;
- }
- }
- return success;
-}
-int st_hll_test_sliding_window(const struct st_hll_case *mycase)
-{
- unsigned char precision=mycase->precision;
- int n_replica=mycase->n_replica;
- int actual_window_count=mycase->ideal_count;
- int time_window_s=mycase->time_window_s;
- int count=0, add_per_step=0;
-
- struct timeval start, step, now;
- gettimeofday(&start, NULL);
- memcpy(&now, &start, sizeof(now));
- struct ST_hyperloglog *h[n_replica];
- for(int i=0; i<n_replica; i++)
- {
- h[i]=ST_hyperloglog_new(precision, time_window_s, start);
- }
-
- int key=1319823, j=0;
- size_t n_add=0;
- if(time_window_s>0)
- {
- double per_second_count=(double)actual_window_count/time_window_s;
- if(per_second_count>1000*1000)
- {
- step.tv_sec=0;
- step.tv_usec=1;
- add_per_step=per_second_count/1000/1000;
- }
- else
- {
- step.tv_sec=0;
- step.tv_usec=(suseconds_t)1000*1000/per_second_count;
- add_per_step=1;
- }
- while(now.tv_sec-start.tv_sec<time_window_s*10)
- {
- timeradd(&now, &step, &now);
- for(int i=0; i<add_per_step; i++)
- {
- j=random()%n_replica;
- key++;
- ST_hyperloglog_add(h[j], (const char *)&key, sizeof(key), now);
- n_add++;
- }
- }
- }
- else
- {
- for(int i=0; i<actual_window_count; i++)
- {
- j=random()%n_replica;
- key++;
- ST_hyperloglog_add(h[j], (const char *)&key, sizeof(key), now);
- n_add++;
- }
- }
- char *blob=NULL;
- size_t blob_sz=0;
- for(int i=1; i<n_replica; i++)
- {
- ST_hyperloglog_serialize(h[i], &blob, &blob_sz);
- ST_hyperloglog_merge_blob(h[0], blob, blob_sz);
- free(blob);
- blob=NULL;
- }
- count=ST_hyperloglog_count(h[0]);
-
- for(int i=0; i<n_replica; i++)
- {
- ST_hyperloglog_free(h[i]);
- }
- return count;
-}
-TEST(STHyperLogLog, NoSliding)
-{
- struct st_hll_case st_case[128];
- int i=0;
- for(int j=6; j<19; j++)
- {
- st_case[i].precision=j;
- st_case[i].time_window_s=0;
- st_case[i].ideal_count=100*100;
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- i++;
- }
- int success=st_hll_case_print(st_case, i);
- //allow 2 case failed.
- EXPECT_GE(success+2, i);
-}
-TEST(STHyperLogLog, VariousPrecision)
-{
- int n_case=HLL_MAX_PRECISION-HLL_MIN_PRECISION+1;
- struct st_hll_case st_case[n_case];
- for(int i=0; i<n_case; i++)
- {
- st_case[i].precision=HLL_MIN_PRECISION+i;
- st_case[i].time_window_s=5;
- st_case[i].ideal_count=70000;
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- }
- int success=st_hll_case_print(st_case, n_case);
- EXPECT_GE(success+3, n_case);
-}
-TEST(STHyperLogLog, VariousCount)
-{
- int n_case=15;
- struct st_hll_case st_case[n_case];
- int i=0;
- for(i=0; i<n_case; i++)
- {
- st_case[i].precision=15;
- st_case[i].time_window_s=300;
- st_case[i].ideal_count=100*(2<<i);
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- }
- int success=st_hll_case_print(st_case, n_case);
- EXPECT_GE(success+3, n_case);
-}
-TEST(STHyperLogLog, VariousWindow)
-{
- int n_case=10;
- struct st_hll_case st_case[n_case];
- int i=0;
- for(i=0; i<n_case; i++)
- {
- st_case[i].precision=15;
- st_case[i].time_window_s=5*(i*i+1);
- st_case[i].ideal_count=10000;
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- }
- int success=st_hll_case_print(st_case, n_case);
- EXPECT_EQ(success, n_case);
-}
-TEST(STHyperLogLog, VariousWindow70k)
-{
- int n_case=10;
- struct st_hll_case st_case[n_case];
- int i=0;
- for(i=0; i<n_case; i++)
- {
- st_case[i].precision=15;
- st_case[i].time_window_s=5*(i*i+1);
- st_case[i].ideal_count=70000;
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- }
- int success=st_hll_case_print(st_case, n_case);
- //70000 using linear estimation, which is the worst case for precision 15.
- //Much better at precision 9.
- //EXPECT_EQ(success, n_case);
- for(i=0; i<n_case; i++)
- {
- st_case[i].precision=9;
- st_case[i].time_window_s=5*(i*i+1);
- st_case[i].ideal_count=70000;
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- }
- success=st_hll_case_print(st_case, n_case);
- EXPECT_EQ(success, n_case);
-}
-TEST(STHyperLogLog, Debug)
-{
- struct st_hll_case st_case[64];
- int i=0;
-
- st_case[i].precision=15;
- st_case[i].time_window_s=0;
- st_case[i].ideal_count=70000;
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- i++;
-
- st_case[i].precision=15;
- st_case[i].time_window_s=300;
- st_case[i].ideal_count=70000;
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- i++;
-
- st_case[i].precision=14;
- st_case[i].time_window_s=0;
- st_case[i].ideal_count=40000;
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- i++;
-
- st_case[i].precision=14;
- st_case[i].time_window_s=300;
- st_case[i].ideal_count=40000;
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- i++;
-
- st_case[i].precision=15;
- st_case[i].time_window_s=300;
- st_case[i].ideal_count=10000;
- st_case[i].n_replica=1;
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- i++;
-
- int success=st_hll_case_print(st_case, i);
- EXPECT_EQ(success+1, i);
-}
-TEST(STHyperLogLog, Replicas)
-{
- struct st_hll_case st_case[128];
- int i=0;
- st_case[i].precision=9;
- st_case[i].time_window_s=1;
- st_case[i].ideal_count=1000;
- st_case[i].n_replica=2;
-
- st_case[i].est_count=st_hll_test_sliding_window(st_case+i);
- double est_error=ST_hyperloglog_error_for_precision(st_case[i].precision);
- EXPECT_NEAR(st_case[i].est_count, st_case[i].ideal_count, est_error*st_case[i].ideal_count);
- i++;
-}
-TEST(STHyperLogLog, Serialize)
-{
- struct timeval start;
- gettimeofday(&start, NULL);
- struct ST_hyperloglog *h=ST_hyperloglog_new(9, 5, start);
- size_t sz=ST_hyperloglog_serialized_size(h);
- char *blob=NULL;
- size_t blob_sz=0;
- ST_hyperloglog_serialize(h, &blob, &blob_sz);
- EXPECT_EQ(sz, blob_sz);
- struct ST_hyperloglog *h2=ST_hyperloglog_deserialize(blob, blob_sz);
- free(blob);
- sz=ST_hyperloglog_serialized_size(h2);
- EXPECT_EQ(sz, blob_sz);
- ST_hyperloglog_free(h);
- ST_hyperloglog_free(h2);
-}
void g_array_sync(struct g_array **replicas, size_t n_replica)
{
char *blob=NULL;
@@ -1640,221 +1191,7 @@ void g_array_sync(struct g_array **replicas, size_t n_replica)
}
return;
}
-TEST(STHyperLogLog, Reconfigure)
-{
- struct timeval start, step, now;
- gettimeofday(&start, NULL);
- memcpy(&now, &start, sizeof(now));
- int n_replica=2;
- struct ST_hyperloglog *h[n_replica];
- int time_window_s=10;
- unsigned char precision=6;
- for(int i=0; i<n_replica; i++)
- {
- h[i]=ST_hyperloglog_new(precision, time_window_s, start);
- }
- int key=1319823, j=0;
- int n_add=0;
- int add_per_step=100;
- step.tv_sec=0;
- step.tv_usec=1000;
- int item_per_second=add_per_step*1000*1000/step.tv_usec;
- while(now.tv_sec-start.tv_sec<time_window_s*5)
- {
- timeradd(&now, &step, &now);
- for(int i=0; i<add_per_step; i++)
- {
- j=random()%n_replica;
- key++;
- ST_hyperloglog_add(h[j], (const char *)&key, sizeof(key), now);
- n_add++;
- }
- }
- for(int i=0; i<n_replica; i++)
- {
- for(int j=0; j<n_replica; j++)
- {
- if(i==j) continue;
- ST_hyperloglog_merge(h[i], h[j]);
- }
- }
- double hll_count=0, error=0;
- hll_count=ST_hyperloglog_count(h[0]);
- error=ST_hyperloglog_error_for_precision(precision);
- EXPECT_NEAR(hll_count, item_per_second*time_window_s, error*item_per_second*time_window_s);
-
-
- time_window_s=20;
- precision=9;
- ST_hyperloglog_configure(h[0], precision, time_window_s, now);
- for(int i=0; i<n_replica; i++)
- {
- for(int j=0; j<n_replica; j++)
- {
- if(i==j) continue;
- ST_hyperloglog_merge(h[i], h[j]);
- }
- }
- memcpy(&start, &now, sizeof(start));
- n_add=0;
- while(now.tv_sec-start.tv_sec<time_window_s*5)
- {
- timeradd(&now, &step, &now);
- for(int i=0; i<add_per_step; i++)
- {
- j=random()%n_replica;
- key++;
- ST_hyperloglog_add(h[j], (const char *)&key, sizeof(key), now);
- n_add++;
- }
- }
- for(int i=0; i<n_replica; i++)
- {
- for(int j=0; j<n_replica; j++)
- {
- if(i==j) continue;
- ST_hyperloglog_merge(h[i], h[j]);
- }
- }
- hll_count=ST_hyperloglog_count(h[0]);
- error=ST_hyperloglog_error_for_precision(precision);
- EXPECT_NEAR(hll_count, item_per_second*time_window_s, error*item_per_second*time_window_s);
-
- for(int i=0; i<n_replica; i++)
- {
- ST_hyperloglog_free(h[i]);
- }
-}
-TEST(STHyperLogLog, EventualConsistency)
-{
- struct timeval start, step, now;
- gettimeofday(&start, NULL);
- memcpy(&now, &start, sizeof(now));
- int n_replica=2;
- struct ST_hyperloglog *h[n_replica];
- int time_window_s=10;
- unsigned char precision=6;
- for(int i=0; i<n_replica; i++)
- {
- h[i]=ST_hyperloglog_new(precision, time_window_s, start);
- }
- int key=1319823, j=0;
- int n_add=0;
- int add_per_step=100;
- step.tv_sec=0;
- step.tv_usec=1000;
- int item_per_second=add_per_step*1000*1000/step.tv_usec;
- while(now.tv_sec-start.tv_sec<time_window_s*5)
- {
- timeradd(&now, &step, &now);
- for(int i=0; i<add_per_step; i++)
- {
- j=1+random()%(n_replica-1);
- key++;
- ST_hyperloglog_add(h[j], (const char *)&key, sizeof(key), now);
- n_add++;
- }
- }
- for(int i=0; i<n_replica; i++)
- {
- for(int j=0; j<n_replica; j++)
- {
- if(i==j) continue;
- ST_hyperloglog_merge(h[i], h[j]);
- }
- }
- double hll_count=0, error=0;
- hll_count=ST_hyperloglog_count(h[0]);
- error=ST_hyperloglog_error_for_precision(precision);
- EXPECT_NEAR(hll_count, item_per_second*time_window_s, error*item_per_second*time_window_s);
- for(int i=0; i<n_replica; i++)
- {
- ST_hyperloglog_free(h[i]);
- }
-}
-TEST(STHyperLogLog, Merge)
-{
- struct timeval start, now;
- gettimeofday(&start, NULL);
- memcpy(&now, &start, sizeof(now));
- int n_replica=2;
- struct ST_hyperloglog *h[n_replica];
- int time_window_s=10;
- unsigned char precision=6;
- for(int i=0; i<n_replica; i++)
- {
- h[i]=ST_hyperloglog_new(precision, time_window_s, start);
- start.tv_sec+=time_window_s+1;
- }
- for(int i=0; i<n_replica; i++)
- {
- for(int j=0; j<n_replica; j++)
- {
- if(i==j) continue;
- ST_hyperloglog_merge(h[i], h[j]);
- }
- }
- for(int i=0; i<n_replica; i++)
- {
- ST_hyperloglog_free(h[i]);
- }
-}
-TEST(STHyperLogLog, Step)
-{
- struct timeval start, step, now;
- gettimeofday(&start, NULL);
- memcpy(&now, &start, sizeof(now));
- step.tv_sec=0;
- step.tv_usec=10000;
- int time_window_s=32;
- unsigned char precision=8;
- struct ST_hyperloglog *h=ST_hyperloglog_new(precision, time_window_s, start);
- int key=1;
- int add_per_step=1;
- int item_per_second=add_per_step*1000*1000/step.tv_usec;
- while(now.tv_sec-start.tv_sec<time_window_s*10)
- {
- timeradd(&now, &step, &now);
- for(int i=0; i<add_per_step; i++)
- {
- key++;
- ST_hyperloglog_add(h, (const char *)&key, sizeof(key), now);
- }
- }
- double hll_count=0;
- double error=ST_hyperloglog_error_for_precision(precision);
- hll_count=ST_hyperloglog_count(h);
- printf("time_window: %d, count: %d, error: %.2f \n", time_window_s, item_per_second*time_window_s, error);
- for(int i=0; i<time_window_s; i++)
- {
- ST_hyperloglog_step(h, now);
- hll_count=ST_hyperloglog_count(h);
- printf("t+%d, estimate: %.2f\n", i, hll_count);
- now.tv_sec++;
- //EXPECT_NEAR(hll_count, item_per_second*(time_window_s-i), error*item_per_second*(time_window_s-i));
- }
- ST_hyperloglog_free(h);
- return;
-/*
-
- hll_count=ST_hyperloglog_count(h);
- EXPECT_NEAR(hll_count, item_per_second*time_window_s, error*item_per_second*time_window_s);
-
- now.tv_sec += time_window_s/12;
- ST_hyperloglog_step(h, now);
- hll_count=ST_hyperloglog_count(h);
- EXPECT_NEAR(hll_count, item_per_second*time_window_s/4, error*item_per_second*time_window_s/2);
-
-
- now.tv_sec += time_window_s/12;
- ST_hyperloglog_step(h, now);
- hll_count=ST_hyperloglog_count(h);
- EXPECT_NEAR(hll_count, 0, error*item_per_second*time_window_s);
-
- ST_hyperloglog_free(h);
- */
-}
TEST(GArray, Basic)
{
uuid_t uuid;
@@ -1996,6 +1333,7 @@ TEST(GArray, Merge)
g_array_free(a[i]);
}
}
+
int main(int argc, char ** argv)
{
int ret=0;
diff --git a/CRDT/bulk_token_bucket.c b/CRDT/bulk_token_bucket.c
index b16e118..5d9c1ca 100644
--- a/CRDT/bulk_token_bucket.c
+++ b/CRDT/bulk_token_bucket.c
@@ -11,12 +11,14 @@
#include <assert.h>
#define PERTURB_INTERVAL_MAX_MS 8000
-
+#define BTB_ST_HLL_PRECISION 7
+#define BTB_ST_HLL_WINDOW_MIN_MS 1000
struct btb_configuration
{
- long long CIR; //Committed Information Rate
- long long CBS; //Committed Burst Size
+ long long rate;
+ long long period;
+ long long capacity;
long long refill_interval_ms;
long long bucket_num;
struct timeval last_cfg;
@@ -32,7 +34,7 @@ struct bulk_token_bucket
struct btb_configuration cfg;
struct timeval start;
long long perturb;
- struct timeval perturb_timestamp;
+ struct timeval last_perturb;
struct ST_hyperloglog *hll; //counting active bucket id with a sliding window style
struct g_array *consumed;
@@ -40,27 +42,29 @@ struct bulk_token_bucket
};
static double collision_probability(long long bucket_num, long long n_key)
{
+ if(n_key==0) return 0;
//https://en.wikipedia.org/wiki/Birthday_problem#Number_of_people_with_a_shared_birthday
double p_non_collsion=1-pow((double)(bucket_num-1)/bucket_num, n_key-1);
return p_non_collsion;
}
-struct bulk_token_bucket *bulk_token_bucket_new(uuid_t my_id, struct timeval now, long long CIR, long long CBS, long long bucket_num)
+struct bulk_token_bucket *bulk_token_bucket_new(uuid_t my_id, struct timeval now, long long rate, long long period, long long capacity, long long bucket_num)
{
struct bulk_token_bucket *btb=ALLOC(struct bulk_token_bucket, 1);
- btb->cfg.CIR=CIR;
- btb->cfg.CBS=CBS;
- btb->cfg.bucket_num=bucket_num;
- btb->cfg.refill_interval_ms=10;
- memcpy(&btb->cfg.last_cfg, &now, sizeof(btb->cfg.last_cfg));
-
- btb->hll=ST_hyperloglog_new(9, 5, now);
- memcpy(&btb->start, &now, sizeof(btb->start));
+ if(rate<0 || period<0 || capacity<0 || bucket_num<0) return NULL;
+ btb->cfg.rate = rate;
+ btb->cfg.period = period;
+ btb->cfg.capacity = capacity;
+ btb->cfg.bucket_num = bucket_num;
+ btb->cfg.refill_interval_ms = 10;
+ btb->cfg.last_cfg = now;
+ long long sthll_time_window_ms=MAX(BTB_ST_HLL_WINDOW_MIN_MS, period*1000);
+ btb->hll=ST_hyperloglog_new(BTB_ST_HLL_PRECISION, sthll_time_window_ms, now);
+ btb->start = now;
btb->consumed=g_array_new(my_id, bucket_num);
btb->refilled=ALLOC(struct refill_mark, bucket_num);
btb->perturb=1;
- memcpy(&btb->perturb_timestamp, &now, sizeof(btb->perturb_timestamp));
- memcpy(&btb->start, &now, sizeof(btb->start));
+ btb->last_perturb = now;
return btb;
}
void bulk_token_bucket_free(struct bulk_token_bucket *btb)
@@ -75,14 +79,15 @@ long long bulk_token_bucket_consume(struct bulk_token_bucket *btb, struct timeva
{
long long delta_time_ms=0, bucket_idx=0, consumed=0, refilled=0;
long long now_ms=timeval_delta_ms(btb->start, now);
- delta_time_ms=timeval_delta_ms(btb->perturb_timestamp, now);
+ delta_time_ms=timeval_delta_ms(btb->last_perturb, now);
ST_hyperloglog_add(btb->hll, key, keylen, now);
//Perturb every CBS/CIR seconds, so that buckets not used after last perturb have been refilled to CBS.
if(!btb->cfg.bucket_num) return 0;
- if(btb->cfg.CIR && delta_time_ms > MIN(1000*btb->cfg.CBS/btb->cfg.CIR, PERTURB_INTERVAL_MAX_MS))
+ long long perturb_threshold_ms=btb->cfg.period*1000*btb->cfg.capacity/MAX(btb->cfg.rate, 1);
+ if(btb->cfg.rate && delta_time_ms > MIN(perturb_threshold_ms, PERTURB_INTERVAL_MAX_MS))
{
btb->perturb++;
- memcpy(&btb->perturb_timestamp, &now, sizeof(btb->perturb_timestamp));
+ memcpy(&btb->last_perturb, &now, sizeof(btb->last_perturb));
}
bucket_idx=XXH3_64bits_withSeed(key, keylen, btb->perturb)%btb->cfg.bucket_num;
@@ -91,7 +96,7 @@ long long bulk_token_bucket_consume(struct bulk_token_bucket *btb, struct timeva
assert(refilled>=0);
assert(consumed>=0);
- long long perturb_ms=timeval_delta_ms(btb->start, btb->perturb_timestamp);
+ long long perturb_ms=timeval_delta_ms(btb->start, btb->last_perturb);
//If current bucket hasn't been refilled since latest perturb.
//find the previous bucket that the key used.
@@ -107,7 +112,7 @@ long long bulk_token_bucket_consume(struct bulk_token_bucket *btb, struct timeva
pre_consumed=g_array_get(btb->consumed, pre_bucket_idx);
pre_refilled=btb->refilled[pre_bucket_idx].refilled;
long long pre_available=0, pre_new_refilled=0;
- pre_available=tb_available(btb->cfg.CIR, btb->cfg.CBS,
+ pre_available=tb_available(btb->cfg.rate, btb->cfg.period, btb->cfg.capacity,
pre_consumed, pre_refilled,
now_ms - btb->refilled[pre_bucket_idx].refill_ms, btb->cfg.refill_interval_ms,
&pre_new_refilled);
@@ -120,15 +125,15 @@ long long bulk_token_bucket_consume(struct bulk_token_bucket *btb, struct timeva
long long new_refilled=0, global_available=0;
int infinite_flag=0;
- if(btb->cfg.CBS==0 && btb->cfg.CIR==0)
+ if(btb->cfg.period==0)
{
infinite_flag=1;
new_refilled += tokens;
}
else
{
- long long delta_ms= now_ms - btb->refilled[bucket_idx].refill_ms;
- global_available=tb_available(btb->cfg.CIR, btb->cfg.CBS, consumed, refilled, delta_ms, btb->cfg.refill_interval_ms, &new_refilled);
+ long long delta_ms = now_ms - btb->refilled[bucket_idx].refill_ms;
+ global_available = tb_available(btb->cfg.rate, btb->cfg.period, btb->cfg.capacity, consumed, refilled, delta_ms, btb->cfg.refill_interval_ms, &new_refilled);
}
size_t n_replica=MAX(1, g_array_replicas(btb->consumed));
@@ -139,7 +144,7 @@ long long bulk_token_bucket_consume(struct bulk_token_bucket *btb, struct timeva
}
else
{
- assigned=tb_consume_reserve_based(btb->cfg.CIR, global_available, n_replica, cmd, tokens);
+ assigned=tb_consume_reserve_based(btb->cfg.rate, btb->cfg.period, global_available, btb->cfg.capacity, n_replica, cmd, tokens);
}
if(new_refilled!=refilled)
@@ -151,12 +156,15 @@ long long bulk_token_bucket_consume(struct bulk_token_bucket *btb, struct timeva
if(assigned) g_array_incrby(btb->consumed, bucket_idx, assigned);
return assigned;
}
-void bulk_token_bucket_configure(struct bulk_token_bucket *btb, struct timeval now, long long CIR, long long CBS, long long bucket_num)
+void bulk_token_bucket_configure(struct bulk_token_bucket *btb, struct timeval now, long long rate, long long period, long long capacity, long long bucket_num)
{
- btb->cfg.CIR=CIR;
- btb->cfg.CBS=CBS;
+ btb->cfg.rate = rate;
+ btb->cfg.period = period;
+ btb->cfg.capacity = capacity;
btb->cfg.bucket_num=bucket_num;
- memcpy(&btb->cfg.last_cfg, &now, sizeof(btb->cfg.last_cfg));
+ btb->cfg.last_cfg = now;
+ long long sthll_time_window_ms=MAX(BTB_ST_HLL_WINDOW_MIN_MS, period*1000);
+ ST_hyperloglog_configure(btb->hll, BTB_ST_HLL_PRECISION, sthll_time_window_ms, now);
g_array_resize(btb->consumed, bucket_num);
btb->refilled=realloc(btb->refilled, btb->cfg.bucket_num*sizeof(struct refill_mark));
memset(btb->refilled, 0, btb->cfg.bucket_num*sizeof(struct refill_mark));
@@ -164,26 +172,26 @@ void bulk_token_bucket_configure(struct bulk_token_bucket *btb, struct timeval n
}
void bulk_token_bucket_info(const struct bulk_token_bucket *btb, struct timeval now, struct bulk_token_bucket_info *info)
{
- info->CIR=btb->cfg.CIR;
- info->CBS=btb->cfg.CBS;
- info->bucket_number=btb->cfg.bucket_num;
- info->replicas=g_array_replicas(btb->consumed);
- ST_hyperloglog_step(btb->hll, now);
- info->estimate_keys=ST_hyperloglog_count(btb->hll);
- info->collision_rate=collision_probability(info->bucket_number, info->estimate_keys);
+ info->rate = btb->cfg.rate;
+ info->period = btb->cfg.period;
+ info->capacity = btb->cfg.capacity;
+ info->bucket_number = btb->cfg.bucket_num;
+ info->replicas = g_array_replicas(btb->consumed);
+ info->approximate_keys=ST_hyperloglog_count(btb->hll, now);
+ info->collision_rate=collision_probability(info->bucket_number, info->approximate_keys);
return;
}
long long bulk_token_bucket_read_available(const struct bulk_token_bucket *btb, struct timeval now, const char *key, size_t keylen)
{
long long consumed=0, refilled=0, available=0, new_refilled=0;
int bucket_idx=0;
- bucket_idx=XXH3_64bits_withSeed(key, keylen, btb->perturb)%btb->cfg.bucket_num;
- consumed=g_array_get(btb->consumed, bucket_idx);
- refilled=btb->refilled[bucket_idx].refilled;
- long long now_ms=timeval_delta_ms(btb->start, now);
- long long delta_ms= now_ms - btb->refilled[bucket_idx].refill_ms;
- available=tb_available(btb->cfg.CIR, btb->cfg.CBS, consumed, refilled, delta_ms, btb->cfg.refill_interval_ms, &new_refilled);
- if(btb->cfg.CBS==0 && btb->cfg.CIR==0) available=INT64_MAX;
+ bucket_idx = XXH3_64bits_withSeed(key, keylen, btb->perturb)%btb->cfg.bucket_num;
+ consumed = g_array_get(btb->consumed, bucket_idx);
+ refilled = btb->refilled[bucket_idx].refilled;
+ long long now_ms = timeval_delta_ms(btb->start, now);
+ long long delta_ms = now_ms - btb->refilled[bucket_idx].refill_ms;
+ available=tb_available(btb->cfg.rate, btb->cfg.period, btb->cfg.capacity, consumed, refilled, delta_ms, btb->cfg.refill_interval_ms, &new_refilled);
+ if(btb->cfg.rate==0 && btb->cfg.capacity==0) available=INT64_MAX;
return available;
}
const size_t BULK_TOKEN_BUCKET_HEADER_SIZE=offsetof(struct bulk_token_bucket, hll);
@@ -194,7 +202,7 @@ void bulk_token_bucket_serialize(const struct bulk_token_bucket *btb, char **blo
sz += ST_hyperloglog_serialized_size(btb->hll);
sz += g_array_serialized_size(btb->consumed);
sz += btb->cfg.bucket_num*sizeof(struct refill_mark);
-
+
char *buffer = ALLOC(char, sz);
memcpy(buffer+offset, btb, BULK_TOKEN_BUCKET_HEADER_SIZE);
offset+=BULK_TOKEN_BUCKET_HEADER_SIZE;
@@ -229,6 +237,7 @@ struct bulk_token_bucket *bulk_token_bucket_deserialize(const char *blob, size_t
memcpy(btb, blob+offset, BULK_TOKEN_BUCKET_HEADER_SIZE);
offset+=BULK_TOKEN_BUCKET_HEADER_SIZE;
btb->hll=ST_hyperloglog_deserialize(blob+offset, blob_sz-offset);
+
offset+=ST_hyperloglog_serialized_size(btb->hll);
btb->consumed=g_array_deserialize(blob+offset, blob_sz-offset);
offset+=g_array_serialized_size(btb->consumed);
@@ -243,15 +252,17 @@ void bulk_token_bucket_merge(struct bulk_token_bucket *dst, const struct bulk_to
{
if(timercmp(&(dst->cfg.last_cfg), &(src->cfg.last_cfg), <))//Last-Write-Wins
{
- bulk_token_bucket_configure(dst, src->cfg.last_cfg, src->cfg.CIR, src->cfg.CBS, src->cfg.bucket_num);
+ bulk_token_bucket_configure(dst, src->cfg.last_cfg, src->cfg.rate, src->cfg.period, src->cfg.capacity, src->cfg.bucket_num);
}
- if(timercmp(&(dst->perturb_timestamp), &(src->perturb_timestamp), <))//Last-Write-Wins
+ if(timercmp(&(dst->last_perturb), &(src->last_perturb), <))//Last-Write-Wins
{
dst->perturb=src->perturb;
- memcpy(&dst->perturb_timestamp, &src->perturb_timestamp, sizeof(dst->perturb_timestamp));
+ memcpy(&dst->last_perturb, &src->last_perturb, sizeof(dst->last_perturb));
}
+
//Stop to proceed when src is older than dst, and has a different bucket_num.
if(dst->cfg.bucket_num != src->cfg.bucket_num) return;
+
ST_hyperloglog_merge(dst->hll, src->hll);
g_array_merge(dst->consumed, src->consumed);
for(long long i=0; i<dst->cfg.bucket_num; i++)
diff --git a/CRDT/bulk_token_bucket.h b/CRDT/bulk_token_bucket.h
index 5300b89..2010596 100644
--- a/CRDT/bulk_token_bucket.h
+++ b/CRDT/bulk_token_bucket.h
@@ -12,18 +12,38 @@
extern "C"
{
#endif
-struct bulk_token_bucket *bulk_token_bucket_new(uuid_t my_id, struct timeval now, long long CIR, long long CBS, long long bucket_num);
+struct bulk_token_bucket;
+/** ***************************************************************************
+ * Create a bulk token bucket that can handle multiple token buckets.
+ *
+ * Parameters:
+ * -----------
+ * uuid - A UUID generated by the uuid_generate() function in the libuuid library.
+ * now - The current time.
+ * rate - The number of tokens to be refilled in each period.
+ * period - The time period in second. If period is 0, the bucket generate infinite tokens.
+ * capacity - The capacity of the token bucket.
+ * bucket_num - The number of token buckets.
+ *
+ * Return:
+ * -------
+ * Pointer to the created token bucket - on success
+ * NULL - on failure
+ *
+ */
+struct bulk_token_bucket *bulk_token_bucket_new(uuid_t my_id, struct timeval now, long long refill, long long period, long long capacity, long long bucket_num);
void bulk_token_bucket_free(struct bulk_token_bucket *btb);
-void bulk_token_bucket_configure(struct bulk_token_bucket *btb, struct timeval now, long long CIR, long long CBS, long long bucket_num);
+void bulk_token_bucket_configure(struct bulk_token_bucket *btb, struct timeval now, long long refill, long long period, long long capacity, long long bucket_num);
long long bulk_token_bucket_consume(struct bulk_token_bucket *btb, struct timeval now, const char *key, size_t key_len, enum tb_consume_type cmd, long long tokens);
long long bulk_token_bucket_read_available(const struct bulk_token_bucket *btb, struct timeval now, const char *key, size_t keylen);
struct bulk_token_bucket_info
{
- long long CIR;
- long long CBS;
+ long long rate;
+ long long period;
+ long long capacity;
long long bucket_number;
- long long estimate_keys;
+ long long approximate_keys;
long long replicas;
double collision_rate;
};
diff --git a/CRDT/crdt_utils.h b/CRDT/crdt_utils.h
index 269eeb9..25e6576 100644
--- a/CRDT/crdt_utils.h
+++ b/CRDT/crdt_utils.h
@@ -25,8 +25,8 @@
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#endif
-#define timeval_delta_ms(start, end) ((end.tv_sec-start.tv_sec)*1000 + (end.tv_usec-start.tv_usec)/1000)
-#define timeval_delta_us(start, end) ((end.tv_sec-start.tv_sec)*1000*1000 + (end.tv_usec-start.tv_usec))
+#define timeval_delta_ms(start, end) (((end).tv_sec-(start).tv_sec)*1000 + ((end).tv_usec-(start).tv_usec)/1000)
+#define timeval_delta_us(start, end) (((end).tv_sec-(start).tv_sec)*1000*1000 + ((end).tv_usec-(start).tv_usec))
#define timeval_to_ms(t) ((t).tv_sec*1000+(t).tv_usec/1000)
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
diff --git a/CRDT/fair_token_bucket.c b/CRDT/fair_token_bucket.c
index 1dc3d09..a3d6ef0 100644
--- a/CRDT/fair_token_bucket.c
+++ b/CRDT/fair_token_bucket.c
@@ -10,13 +10,15 @@
#include <stdint.h>
-#define PERTUB_INTERVAL_MS 100
-
+#define PERTUB_INTERVAL_MS 10
+#define STHLL_WINDOW_MS 1000
+//#define DEBUG
struct fair_token_bucket
{
/* Sync Variables*/
long long divisor;
+ long long perturb_interval_ms;
struct timeval last_cfg;
struct ST_hyperloglog *hll[FAIR_TB_WEIGHT_MAX]; //counting active keys
struct OC_token_bucket *bucket;
@@ -31,23 +33,31 @@ struct fair_token_bucket
long long debug_last_defict;
long long debug_last_allocated;
long long debug_consume_call;
+#ifdef DEBUG
+ long long debug_deficit_array[256];
+#endif
};
-struct fair_token_bucket *fair_token_bucket_new(uuid_t uuid, struct timeval now, long long CIR, long long CBS, long long divisor)
+struct fair_token_bucket *fair_token_bucket_new(uuid_t uuid, struct timeval now, long long rate, long long period, long long capacity, long long divisor)
{
struct fair_token_bucket *ftb=ALLOC(struct fair_token_bucket, 1);
ftb->divisor=divisor;
+ ftb->perturb_interval_ms=PERTUB_INTERVAL_MS;
for(int i=0; i<FAIR_TB_WEIGHT_MAX; i++)
{
- ftb->hll[i]=ST_hyperloglog_new(9, 5, now);
+ ftb->hll[i]=ST_hyperloglog_new(7, STHLL_WINDOW_MS, now);
}
ftb->sfq=ALLOC(long long, divisor);
- ftb->bucket=OC_token_bucket_new(uuid, now, CIR, CBS);
- ftb->per_weight_quantum=CIR;
+ ftb->bucket=OC_token_bucket_new(uuid, now, rate, period, capacity);
+ ftb->per_weight_quantum=rate;
memcpy(&ftb->last_cfg, &now, sizeof(ftb->last_cfg));
return ftb;
}
-
+void fair_token_bucket_set_pertub_interval(struct fair_token_bucket *ftb, long long perturb_interval_ms)
+{
+ ftb->perturb_interval_ms=perturb_interval_ms;
+ return;
+}
void fair_token_bucket_free(struct fair_token_bucket *ftb)
{
for(int i=0; i<FAIR_TB_WEIGHT_MAX; i++)
@@ -65,56 +75,72 @@ static void ftb_quantum_estimation(struct fair_token_bucket *ftb, struct timeval
long long total_weight=0, n_active_key=0, count=0;
for(int i=0; i<FAIR_TB_WEIGHT_MAX; i++)
{
- ST_hyperloglog_step(ftb->hll[i], now);
- count = ST_hyperloglog_count(ftb->hll[i]);
+ ST_hyperloglog_step(ftb->hll[i], now);//avoid STHLL deep copy
+ count = ST_hyperloglog_count(ftb->hll[i], now);
total_weight += (i+1)*count;
n_active_key += count;
}
+ assert(n_active_key>=0);
struct OC_token_bucket_info info;
OC_token_bucket_info(ftb->bucket, now, &info);
- long long local_available=tb_local_available(info.CIR, info.available, info.number_of_replica);
-
- if(info.CIR==0)//Infinite Tokens
+
+ //long long local_available=tb_local_available(info.rate, info.period, info.available, info.number_of_replica);
+ long long local_available=tb_local_available(info.rate, info.period, info.available, info.capacity, info.number_of_replica);
+ if(info.period==0)//Infinite Tokens
{
- ftb->per_weight_quantum=0;
+ ftb->per_weight_quantum = INT64_MAX;
}
else
{
- ftb->per_weight_quantum=local_available/MAX(1, total_weight);
+ ftb->per_weight_quantum = local_available/MAX(1, total_weight);
}
- ftb->total_weight=total_weight;
+ ftb->total_weight=total_weight;
ftb->n_active_key=n_active_key;
}
static void ftb_perturb(struct fair_token_bucket *ftb, struct timeval now)
{
+#ifdef DEBUG
+ printf("%p\t%lld\t%lld", ftb, ftb->per_weight_quantum, ftb->n_active_key);
+ for(int i=1; i<6; i++)
+ {
+ printf("\t%lld", ftb->debug_deficit_array[i]);
+ assert(ftb->debug_deficit_array[i] <= ftb->per_weight_quantum);
+ }
+ printf("\r\n");
+ memset(ftb->debug_deficit_array, 0, sizeof(ftb->debug_deficit_array));
+#endif
+
ftb_quantum_estimation(ftb, now);
memset(ftb->sfq, 0, sizeof(long long)*ftb->divisor);
ftb->perturb_seed++;
+ memcpy(&ftb->last_pertub, &now, sizeof(ftb->last_pertub));
}
long long fair_token_bucket_consume(struct fair_token_bucket *ftb, struct timeval now, const char *key, size_t keylen, long long weight, enum tb_consume_type cmd, long long tokens)
{
ftb->debug_consume_call++;
if(weight>FAIR_TB_WEIGHT_MAX || weight<=0)
return -1;
+
ST_hyperloglog_add(ftb->hll[weight-1], key, keylen, now);
long long delta_time_ms=timeval_delta_ms(ftb->last_pertub, now);
- if(unlikely(delta_time_ms>=PERTUB_INTERVAL_MS))
+ if(unlikely(delta_time_ms>=ftb->perturb_interval_ms) )//|| ftb->consumed_of_current_perturb > ftb->consumed_of_last_perturb
{
ftb_perturb(ftb, now);
- memcpy(&ftb->last_pertub, &now, sizeof(ftb->last_pertub));
}
int sfq_idx=0;
sfq_idx=XXH3_64bits_withSeed(key, keylen, ftb->perturb_seed)%ftb->divisor;
+
long long deficit_est=0;
deficit_est=ftb->sfq[sfq_idx];
deficit_est/=MAX(1, ftb->n_active_key/ftb->divisor);
ftb->debug_last_defict=deficit_est;
- if(tokens + deficit_est > ftb->per_weight_quantum*weight && ftb->per_weight_quantum>0)
+ if(cmd == TB_CONSUME_FLEXIBLE) tokens=MIN(tokens, ftb->per_weight_quantum*weight-deficit_est);
+ if(tokens + deficit_est > ftb->per_weight_quantum*weight)// && ftb->per_weight_quantum>0)
{
ftb->debug_last_allocated=-1;
return 0;
@@ -122,17 +148,21 @@ long long fair_token_bucket_consume(struct fair_token_bucket *ftb, struct timeva
long long allocated_tokens=OC_token_bucket_consume(ftb->bucket, now, cmd, tokens);
if(allocated_tokens)
{
- ftb->sfq[sfq_idx]+=allocated_tokens;
+ ftb->sfq[sfq_idx] += allocated_tokens;
}
+#ifdef DEBUG
ftb->debug_last_allocated=allocated_tokens;
+ ftb->debug_deficit_array[*((int*)key)]+=allocated_tokens;
+ assert(ftb->sfq[sfq_idx] <= ftb->per_weight_quantum*weight);
+#endif
return allocated_tokens;
}
-void fair_token_bucket_configure(struct fair_token_bucket *ftb, struct timeval now, long long CIR, long long CBS, long long divisor)
+void fair_token_bucket_configure(struct fair_token_bucket *ftb, struct timeval now, long long refill, long long period, long long capacity, long long divisor)
{
ftb->divisor=divisor;
ftb->sfq=(long long*)realloc(ftb->sfq, sizeof(long long)*ftb->divisor);
memcpy(&ftb->last_cfg, &now, sizeof(ftb->last_cfg));
- OC_token_bucket_configure(ftb->bucket, now, CIR, CBS);
+ OC_token_bucket_configure(ftb->bucket, now, refill, period, capacity);
ftb_perturb(ftb, now);
return;
}
@@ -140,10 +170,10 @@ void fair_token_bucket_info(const struct fair_token_bucket *ftb, struct timeval
{
for(int i=0; i<FAIR_TB_WEIGHT_MAX; i++)
{
- ST_hyperloglog_step(ftb->hll[i], now);
- info->active_key_number += ST_hyperloglog_count(ftb->hll[i]);
+ info->active_key_number += ST_hyperloglog_count(ftb->hll[i], now);
}
info->divisor = ftb->divisor;
+ info->perturb_interval_ms = ftb->perturb_interval_ms;
OC_token_bucket_info(ftb->bucket, now, &info->bucket_info);
return;
}
@@ -152,6 +182,7 @@ struct ftb_header
long long magic;
long long payload_sz;
long long divisor;
+ long long perturb_interval_ms;
struct timeval last_cfg;
};
size_t fair_token_bucket_serialized_size(const struct fair_token_bucket *ftb)
@@ -173,6 +204,7 @@ void fair_token_bucket_serialize(const struct fair_token_bucket *ftb, char **blo
hdr.magic=0x5210;
hdr.payload_sz=sz;
hdr.divisor=ftb->divisor;
+ hdr.perturb_interval_ms=ftb->perturb_interval_ms;
memcpy(&hdr.last_cfg, &ftb->last_cfg, sizeof(hdr.last_cfg));
char *buffer=ALLOC(char, sz);
memcpy(buffer+offset, &hdr, sizeof(hdr));
@@ -204,7 +236,8 @@ struct fair_token_bucket *fair_token_bucket_deserialize(const char *blob, size_t
assert(hdr.magic==0x5210);
assert(hdr.payload_sz<=blob_sz);
struct fair_token_bucket *ftb=ALLOC(struct fair_token_bucket, 1);
- ftb->divisor=hdr.divisor;
+ ftb->divisor = hdr.divisor;
+ ftb->perturb_interval_ms = hdr.perturb_interval_ms;
memcpy(&ftb->last_cfg, &hdr.last_cfg, sizeof(ftb->last_cfg));
for(int i=0; i<FAIR_TB_WEIGHT_MAX; i++)
{
@@ -244,7 +277,6 @@ struct fair_token_bucket *fair_token_bucket_replicate(uuid_t uuid, const char *b
OC_token_bucket_change_uuid(ftb->bucket, uuid);
ftb->sfq=ALLOC(long long, ftb->divisor);
return ftb;
-
}
size_t fair_token_bucket_mem_size(const struct fair_token_bucket *ftb)
{
diff --git a/CRDT/fair_token_bucket.h b/CRDT/fair_token_bucket.h
index 9b57fac..8335dce 100644
--- a/CRDT/fair_token_bucket.h
+++ b/CRDT/fair_token_bucket.h
@@ -5,7 +5,7 @@
* - Resources are allocated in order of increasing demand
* - No source gets a resource share larger than its demand
* - Sources with unsatisfied demands get an equal share of the resource
-* Notes: The Fair Token Bucket CRDT doesn't guarrantee max-min fairness when a key is distributed to multiple nodes.
+* Notes: The Fair Token Bucket CRDT doesn't guarrantee max-min fairness when a key request tokens from multiple replicas.
*/
@@ -21,15 +21,34 @@ extern "C"
#endif
#define FAIR_TB_WEIGHT_MAX 20
struct fair_token_bucket;
-struct fair_token_bucket *fair_token_bucket_new(uuid_t uuid, struct timeval now, long long CIR, long long CBS, long long divisor);
+/** ***************************************************************************
+ * Create a fair token bucket that can share tokens with multiple consumers.
+ *
+ * Parameters:
+ * -----------
+ * uuid - A UUID generated by the uuid_generate() function in the libuuid library.
+ * now - The current time.
+ * rate - The number of tokens to be refilled in each period.
+ * period - The time period in second. If period is 0, the bucket generate infinite tokens.
+ * capacity - The capacity of the token bucket.
+ * divisor - The length of SFQ, which is used to count consumer's deficits.
+ *
+ * Return:
+ * -------
+ * Pointer to the created token bucket - on success
+ * NULL - on failure
+ */
+struct fair_token_bucket *fair_token_bucket_new(uuid_t uuid, struct timeval now, long long rate, long long period, long long capacity, long long divisor);
void fair_token_bucket_free(struct fair_token_bucket *ftb);
long long fair_token_bucket_consume(struct fair_token_bucket *ftb, struct timeval now, const char *key, size_t keylen, long long weight, enum tb_consume_type cmd, long long tokens);
-void fair_token_bucket_configure(struct fair_token_bucket *ftb, struct timeval now, long long CIR, long long CBS, long long divisor);
+void fair_token_bucket_configure(struct fair_token_bucket *ftb, struct timeval now, long long rate, long long period, long long capacity, long long divisor);
+void fair_token_bucket_set_pertub_interval(struct fair_token_bucket *ftb, long long perturb_interval_ms);
struct fair_token_bucket_info
{
struct OC_token_bucket_info bucket_info;
long long active_key_number;
long long divisor;
+ long long perturb_interval_ms;
};
void fair_token_bucket_info(const struct fair_token_bucket *ftb, struct timeval now, struct fair_token_bucket_info *info);
diff --git a/CRDT/oc_token_bucket.c b/CRDT/oc_token_bucket.c
index 429dd33..814049a 100644
--- a/CRDT/oc_token_bucket.c
+++ b/CRDT/oc_token_bucket.c
@@ -6,11 +6,12 @@
#include <assert.h>
#include <string.h>
-#define OCTB_REFILL_INTERVAL_MS_DEFAULT 10
+#define OCTB_REFILL_INTERVAL_MS_DEFAULT 1
struct OC_configuration
{
- long long CIR; //Committed Information Rate
- long long CBS; //Committed Burst Size
+ long long capacity;
+ long long rate; //generated tokens per period
+ long long period; //Seconds
long long refill_interval_ms;
struct timeval last_cfg;
};
@@ -26,32 +27,33 @@ struct OC_token_bucket
/* Local variables */
int no_reserve;
};
-const size_t OCTB_BLOB_HDR_SIZE= offsetof(struct OC_token_bucket, consumed);
-struct OC_token_bucket *OC_token_bucket_new(uuid_t my_id, struct timeval now, long long CIR, long long CBS)
+const size_t OCTB_BLOB_HDR_SIZE = offsetof(struct OC_token_bucket, consumed);
+struct OC_token_bucket *OC_token_bucket_new(uuid_t my_id, struct timeval now, long long rate, long long period, long long capacity)
{
- struct OC_token_bucket *bucket=ALLOC(struct OC_token_bucket, 1);
+ struct OC_token_bucket *bucket = ALLOC(struct OC_token_bucket, 1);
bucket->consumed=PN_counter_new(my_id);
//memcpy(&bucket->cfg.last_cfg, &now, sizeof(bucket->cfg.last_cfg));
- bucket->cfg.CIR=CIR;
- bucket->cfg.CBS=CBS;
- bucket->cfg.refill_interval_ms=OCTB_REFILL_INTERVAL_MS_DEFAULT;
- bucket->refill_time_ms=timeval_to_ms(now);
- bucket->no_reserve=0;
- bucket->refilled=bucket->cfg.CBS;
+ bucket->cfg.capacity = capacity;
+ bucket->cfg.rate = rate;
+ bucket->cfg.period = period;
+ bucket->cfg.refill_interval_ms = OCTB_REFILL_INTERVAL_MS_DEFAULT;
+ bucket->refill_time_ms = timeval_to_ms(now);
+ bucket->no_reserve = 0;
+ bucket->refilled = bucket->cfg.capacity;
return bucket;
}
-
void OC_token_bucket_free(struct OC_token_bucket *bucket)
{
PN_counter_free(bucket->consumed);
free(bucket);
}
-void OC_token_bucket_configure(struct OC_token_bucket *bucket, struct timeval now, long long CIR, long long CBS)
+void OC_token_bucket_configure(struct OC_token_bucket *bucket, struct timeval now, long long rate, long long period, long long capacity)
{
- memcpy(&bucket->cfg.last_cfg, &now, sizeof(bucket->cfg.last_cfg));
- if(CIR>=0) bucket->cfg.CIR=CIR;
- if(CBS>=0) bucket->cfg.CBS=CBS;
+ bucket->cfg.last_cfg = now;
+ if(rate >= 0) bucket->cfg.rate = rate;
+ if(capacity >= 0) bucket->cfg.capacity = capacity;
+ if(period >= 0) bucket->cfg.period = period;
}
void OC_token_bucket_set_no_reserve(struct OC_token_bucket *bucket)
{
@@ -67,11 +69,11 @@ long long OC_token_bucket_consume(struct OC_token_bucket *bucket, struct timeval
assert(consumed>=0);
long long new_refilled=0;
- long long available=tb_available(bucket->cfg.CIR, bucket->cfg.CBS, consumed,
+ long long available=tb_available(bucket->cfg.rate, bucket->cfg.period, bucket->cfg.capacity, consumed,
refilled, delta_time_ms, bucket->cfg.refill_interval_ms, &new_refilled);
int infinite_flag=0;
- if(bucket->cfg.CBS==0 && bucket->cfg.CIR==0)
+ if(bucket->cfg.period==0)
{
infinite_flag=1;
new_refilled += tokens;
@@ -92,7 +94,7 @@ long long OC_token_bucket_consume(struct OC_token_bucket *bucket, struct timeval
else
{
if(bucket->no_reserve) n_replica=1;
- allocated=tb_consume_reserve_based(bucket->cfg.CIR, available, n_replica, cmd, tokens);
+ allocated=tb_consume_reserve_based(bucket->cfg.rate, bucket->cfg.period, available, bucket->cfg.capacity, n_replica, cmd, tokens);
}
if(allocated>0)
{
@@ -107,11 +109,12 @@ void OC_token_bucket_info(struct OC_token_bucket *bucket, struct timeval now, st
{
long long delta_time_ms=timeval_to_ms(now)-bucket->refill_time_ms;
- info->CIR=bucket->cfg.CIR;
- info->CBS=bucket->cfg.CBS;
+ info->rate=bucket->cfg.rate;
+ info->period=bucket->cfg.period;
+ info->capacity=bucket->cfg.capacity;
info->refill_interval_ms=bucket->cfg.refill_interval_ms;
info->consumed=PN_counter_get(bucket->consumed);
- info->available=tb_available(bucket->cfg.CIR, bucket->cfg.CBS, info->consumed,
+ info->available=tb_available(bucket->cfg.rate, bucket->cfg.period, bucket->cfg.capacity, info->consumed,
bucket->refilled, delta_time_ms, bucket->cfg.refill_interval_ms, &info->refilled);
info->number_of_replica=PN_counter_replica_num(bucket->consumed);
return;
diff --git a/CRDT/oc_token_bucket.h b/CRDT/oc_token_bucket.h
index 8830fcd..5886df9 100644
--- a/CRDT/oc_token_bucket.h
+++ b/CRDT/oc_token_bucket.h
@@ -5,7 +5,6 @@
*/
#pragma once
#include "token_bucket_common.h"
-#include <stddef.h>
#include <sys/time.h>
#include <uuid/uuid.h>
#ifdef __cplusplus
@@ -14,15 +13,31 @@ extern "C"
#endif
struct OC_token_bucket;
-// CIR: Committed Information Rate
-// CBS: Committed Burst Size
-struct OC_token_bucket *OC_token_bucket_new(uuid_t my_id, struct timeval now, long long CIR, long long CBS);
+/** ***************************************************************************
+ * Create a token bucket.
+ *
+ * Parameters:
+ * -----------
+ * uuid - A UUID generated by the uuid_generate() function in the libuuid library.
+ * now - The current time.
+ * rate - The number of tokens to be refilled in each period.
+ * period - The time period in second. If period is 0, the bucket generate infinite tokens.
+ * capacity - The capacity of the token bucket.
+ *
+ * Return:
+ * -------
+ * Pointer to the created token bucket - on success
+ * NULL - on failure
+ *
+ */
+struct OC_token_bucket *OC_token_bucket_new(uuid_t uuid, struct timeval now, long long rate, long long period, long long capacity);
-void OC_token_bucket_configure(struct OC_token_bucket *bucket, struct timeval now, long long CIR, long long CBS);
+void OC_token_bucket_configure(struct OC_token_bucket *bucket, struct timeval now, long long rate, long long period, long long capacity);
struct OC_token_bucket_info
{
- long long CIR;
- long long CBS;
+ long long rate;
+ long long period;
+ long long capacity;
long long refill_interval_ms;
long long consumed;
long long refilled;
diff --git a/CRDT/probabilistic_crdt_gtest.cpp b/CRDT/probabilistic_crdt_gtest.cpp
new file mode 100644
index 0000000..9c8bf14
--- /dev/null
+++ b/CRDT/probabilistic_crdt_gtest.cpp
@@ -0,0 +1,1067 @@
+#include "crdt_utils.h"
+#include "cm_sketch.h"
+#include "st_hyperloglog.h"
+#include "ap_bloom.h"
+
+#include <gtest/gtest.h>
+#include <unistd.h> //usleep
+#include <uuid/uuid.h>
+#include <math.h>
+TEST(CMSketch, Basic)
+{
+ uuid_t uuid;
+ uuid_generate(uuid);
+ struct CM_sketch *cms=CM_sketch_new(uuid);
+ int ret=0, n_added=0;
+ for(int i=0; i<10; i++)
+ {
+ ret=CM_sketch_add_n(cms, (char *)&i, sizeof(i), i+1);
+ n_added += i+1;
+ }
+ for(int i=0; i<10; i++)
+ {
+ ret=CM_sketch_query(cms, (char *)&i, sizeof(i));
+ EXPECT_EQ(ret, i+1);
+ }
+ for(int i=0; i<10; i++)
+ {
+ ret=CM_sketch_remove_n(cms, (char *)&i, sizeof(i), i);
+ n_added -= i;
+ }
+ for(int i=0; i<10; i++)
+ {
+ ret=CM_sketch_query(cms, (char *)&i, sizeof(i));
+ //EXPECT_EQ(ret, 1);
+ }
+ struct CM_sketch_info info;
+ CM_sketch_info(cms, &info);
+ EXPECT_EQ(info.n_element, n_added);
+ printf("error_rate: %f confidence: %f\n", info.error_rate, info.confidence);
+ CM_sketch_free(cms);
+}
+TEST(CMSketch, I5K)
+{
+ uuid_t uuid;
+ uuid_generate(uuid);
+ struct CM_sketch *cms=CM_sketch_new(uuid);
+ long long n_item=5000, ret=0;
+ long long base=10000, total_add=0;
+ for(long long i=0; i<n_item; i++)
+ {
+ ret=CM_sketch_add_n(cms, (char *)&i, sizeof(i), base+i);
+ total_add+=(base+i);
+ }
+ struct CM_sketch_info info;
+ CM_sketch_info(cms, &info);
+ long long pass=0;
+ for(long long i=0; i<n_item; i++)
+ {
+ ret=CM_sketch_query(cms, (char *)&i, sizeof(i));
+ //The formal expectation is total_add*info.error_rate
+ if(abs(ret-(i+base))< (i+base)*info.error_rate)
+ {
+ pass++;
+ }
+ }
+ EXPECT_NEAR(pass, n_item, n_item*(1-info.confidence));
+ CM_sketch_free(cms);
+}
+static void CMS_sync(struct CM_sketch *list[], size_t n)
+{
+ char *blob=NULL;
+ size_t blob_sz=0;
+
+ for(size_t i=0; i<n; i++)
+ {
+ CM_sketch_serialize(list[i], &blob, &blob_sz);
+ for(size_t j=0; j<n; j++)
+ {
+ if(j==i) continue;
+ CM_sketch_merge_blob(list[j], blob, blob_sz);
+ }
+ free(blob);
+ blob=NULL;
+ }
+ return;
+}
+TEST(CMSketch, Merge)
+{
+ size_t replica_number=2, round=10;
+ long long key=1234;
+ struct CM_sketch *cms[replica_number];
+ uuid_t uuid;
+ for(size_t i=0; i<replica_number; i++)
+ {
+ uuid_generate(uuid);
+ cms[i]=CM_sketch_new(uuid);
+ }
+ for(size_t i=0; i<round; i++)
+ {
+ CM_sketch_add_n(cms[i%replica_number], (char*) &key, sizeof(key), 1);
+ }
+ CMS_sync(cms, replica_number);
+ int ret=0;
+ for(size_t i=0; i<replica_number; i++)
+ {
+ ret=CM_sketch_query(cms[i], (char *)&key, sizeof(key));
+ EXPECT_EQ(ret, round);
+ }
+ for(size_t i=0; i<replica_number; i++)
+ {
+ CM_sketch_free(cms[i]);
+ }
+}
+TEST(CMSketch, Idempotent)
+{
+ size_t replica_number=8, round=10000, i=0;
+ struct CM_sketch *cms[replica_number];
+ uuid_t uuid;
+
+ for(i=0; i<replica_number; i++)
+ {
+ uuid_generate(uuid);
+ cms[i]=CM_sketch_new(uuid);
+ }
+ int n_added=0;
+ for(i=0; i<round; i++)
+ {
+ CM_sketch_add_n(cms[i%replica_number], (char*) &i, sizeof(i), i);
+ n_added+=i;
+ }
+ CMS_sync(cms, replica_number);
+ CMS_sync(cms, replica_number);
+ int ret=0;
+ size_t success=0;
+ struct CM_sketch_info info;
+ CM_sketch_info(cms[0], &info);
+
+ for(i=0; i<round; i++)
+ {
+ ret=CM_sketch_query(cms[(i+1)%replica_number], (char*) &i, sizeof(i));
+ if((double)ret<((double)i+info.error_rate*n_added))
+ {
+ success++;
+ }
+ }
+ EXPECT_GE((double)success/round, info.confidence);
+ for(i=0; i<replica_number; i++)
+ {
+ CM_sketch_free(cms[i]);
+ }
+}
+void st_hll_sync(struct ST_hyperloglog *list[], size_t n)
+{
+ char *blob=NULL;
+ size_t blob_sz=0;
+ if(n<2) return;
+ for(size_t i=0; i<n; i++)
+ {
+ ST_hyperloglog_serialize(list[i], &blob, &blob_sz);
+ for(size_t j=0; j<n; j++)
+ {
+ if(j==i) continue;
+ ST_hyperloglog_merge_blob(list[j], blob, blob_sz);
+ }
+ free(blob);
+ blob=NULL;
+ }
+ return;
+}
+long long st_hll_test(unsigned char precision, int actual_count)
+{
+ int count=0;
+ int key=random();
+ struct timeval nouse;
+ struct ST_hyperloglog *h=ST_hyperloglog_new(precision, 0, nouse);
+ for(int i=0; i<actual_count; i++)
+ {
+ key++;
+ ST_hyperloglog_add(h, (const char *)&key, sizeof(key), nouse);
+ }
+ count=ST_hyperloglog_count(h, nouse);
+ ST_hyperloglog_free(h);
+ return count;
+}
+
+TEST(STHyperLogLog, Basic)
+{
+ double error;
+ long long est_count=0;
+ long long count=10000;
+ unsigned char precision=6;
+
+
+ est_count=st_hll_test(precision, count);
+ error=ST_hyperloglog_error_for_precision(precision);
+ EXPECT_NEAR(est_count, count, error*count);
+
+ precision=9;
+ count=10000;
+ est_count=st_hll_test(precision, count);
+ error=ST_hyperloglog_error_for_precision(precision);
+ EXPECT_NEAR(est_count, count, error*count);
+
+}
+TEST(STHyperLogLog, Duplicate)
+{
+ struct ST_hyperloglog *h1=NULL, *h2=NULL;
+ struct timeval start, now, step;
+ gettimeofday(&start, NULL);
+ long long time_window_ms=1000;
+ h1=ST_hyperloglog_new(9, time_window_ms, start);
+ int n_add=0;
+ now=start;
+ step.tv_sec=0;
+ step.tv_usec=1000;
+ while(timeval_delta_ms(start, now)<time_window_ms*10)
+ {
+ ST_hyperloglog_add(h1, (const char *)&n_add, sizeof(n_add), now);
+ n_add++;
+ timeradd(&now, &step, &now);
+ }
+ double est1, est2;
+ est1=ST_hyperloglog_count(h1, now);
+
+ h2=ST_hyperloglog_duplicate(h1);
+ est2=ST_hyperloglog_count(h2, now);
+ EXPECT_EQ(est1, est2);
+
+ ST_hyperloglog_merge(h2, h1);
+ est2=ST_hyperloglog_count(h2, now);
+ EXPECT_EQ(est1, est2);
+
+ ST_hyperloglog_free(h1);
+ ST_hyperloglog_free(h2);
+}
+struct st_hll_case
+{
+ unsigned char precision;
+ long long time_window_ms;
+ long long ideal_count;
+ long long est_count;
+ long long sync_interval_ms;
+ int n_replica;
+};
+void st_hll_case_init(struct st_hll_case *mycase)
+{
+ memset(mycase, 0, sizeof(struct st_hll_case));
+ mycase->precision=9;
+ mycase->time_window_ms=5*1000;
+ mycase->ideal_count=10000;
+ mycase->n_replica=1;
+ mycase->sync_interval_ms=10;
+ mycase->est_count=0;
+}
+int st_hll_case_print(const struct st_hll_case *st_case, int n_case)
+{
+ int success=0;
+ double real_error=0, est_error=0;
+ printf("prcs\twin\tideal\test\test_err\treal_err\n");
+ for(int j=0; j<n_case; j++)
+ {
+ est_error=ST_hyperloglog_error_for_precision(st_case[j].precision);
+ real_error=(double)abs(st_case[j].est_count-st_case[j].ideal_count)/st_case[j].ideal_count;
+ printf("%d\t%lld\t%lld\t%lld\t%f\t%f\n", st_case[j].precision,
+ st_case[j].time_window_ms,
+ st_case[j].ideal_count,
+ st_case[j].est_count,
+ est_error,
+ real_error);
+ if(real_error <= MAX(0.1, est_error))
+ {
+ success++;
+ }
+ }
+ return success;
+}
+long long st_hll_test(struct st_hll_case *mycase)
+{
+ unsigned char precision=mycase->precision;
+ int n_replica=mycase->n_replica;
+
+ int count=0, per_step_count=0;
+ mycase->sync_interval_ms=MAX(1, mycase->sync_interval_ms);
+ mycase->n_replica=MAX(1, mycase->n_replica);
+ struct timeval start, step, now;
+ struct timeval last_sync;
+ gettimeofday(&start, NULL);
+ now=start;
+ last_sync=start;
+
+ struct ST_hyperloglog *h[n_replica];
+ for(int i=0; i<n_replica; i++)
+ {
+ h[i]=ST_hyperloglog_new(precision, mycase->time_window_ms, start);
+ }
+
+ int key=1319823, j=0;
+ int n_add=0;
+
+ long long fixed_time_window_ms=10*1000;
+ long long test_duration_ms=mycase->time_window_ms?(mycase->time_window_ms*10):fixed_time_window_ms;
+ double per_second_count=(double)mycase->ideal_count*1000/(mycase->time_window_ms?mycase->time_window_ms:test_duration_ms);
+ if(per_second_count>1000*1000)
+ {
+ step.tv_sec=0;
+ step.tv_usec=1;
+ per_step_count=per_second_count/1000/1000;
+ }
+ else
+ {
+ step.tv_sec=0;
+ step.tv_usec=(suseconds_t)1000*1000/per_second_count;
+ per_step_count=1;
+ }
+ while(timeval_delta_ms(start, now) < test_duration_ms)
+ {
+ for(int i=0; i<per_step_count; i++)
+ {
+ j=random()%n_replica;
+ key++;
+ ST_hyperloglog_add(h[j], (const char *)&key, sizeof(key), now);
+ n_add++;
+ }
+ if(timeval_delta_ms(last_sync, now)>mycase->sync_interval_ms)
+ {
+ st_hll_sync(h, n_replica);
+ last_sync=now;
+ //double est=ST_hyperloglog_count(h[0], now);
+ //printf("t+%d, idea: %.3f estimate: %.3f\n", (int)(now.tv_sec-start.tv_sec), per_second_count*time_window_s, est);
+ }
+ timeradd(&now, &step, &now);
+ }
+
+
+ st_hll_sync(h, n_replica);
+ count=ST_hyperloglog_count(h[0], now);
+
+ for(int i=0; i<n_replica; i++)
+ {
+ ST_hyperloglog_free(h[i]);
+ }
+ return count;
+}
+TEST(STHyperLogLog, NoSliding)
+{
+ struct st_hll_case st_case[128];
+ int i=0;
+ for(int j=6; j<19; j++)
+ {
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=j;
+ st_case[i].time_window_ms=0;
+ st_case[i].ideal_count=100*100;
+ st_case[i].n_replica=1;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ i++;
+ }
+ int success=st_hll_case_print(st_case, i);
+ //allow 2 case failed.
+ EXPECT_GE(success+2, i);
+}
+TEST(STHyperLogLog, VariousPrecision)
+{
+ int n_case=HLL_MAX_PRECISION-HLL_MIN_PRECISION+1;
+ struct st_hll_case st_case[n_case];
+ for(int i=0; i<n_case; i++)
+ {
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=HLL_MIN_PRECISION+i;
+ st_case[i].time_window_ms=5*1000;
+ st_case[i].ideal_count=70000;
+ st_case[i].n_replica=1;
+ st_case[i].sync_interval_ms=10;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ }
+ int success=st_hll_case_print(st_case, n_case);
+ EXPECT_GE(success+4, n_case);
+
+}
+TEST(STHyperLogLog, VariousCount)
+{
+ int n_case=15;
+ struct st_hll_case st_case[n_case];
+ int i=0;
+ for(i=0; i<n_case; i++)
+ {
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=15;
+ st_case[i].time_window_ms=300*1000;
+ st_case[i].ideal_count=100*(2<<i);
+ st_case[i].n_replica=1;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ }
+ int success=st_hll_case_print(st_case, n_case);
+ EXPECT_GE(success+3, n_case);
+}
+TEST(STHyperLogLog, VariousWindow)
+{
+ int n_case=10;
+ struct st_hll_case st_case[n_case];
+ int i=0;
+ for(i=0; i<n_case; i++)
+ {
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=15;
+ st_case[i].time_window_ms=5*1000*(i*i+1);
+ st_case[i].ideal_count=10000;
+ st_case[i].n_replica=1;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ }
+ int success=st_hll_case_print(st_case, n_case);
+ EXPECT_EQ(success, n_case);
+}
+TEST(STHyperLogLog, VariousWindow70k)
+{
+ int n_case=10;
+ struct st_hll_case st_case[n_case];
+ int i=0;
+ for(i=0; i<n_case; i++)
+ {
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=15;
+ st_case[i].time_window_ms=10*(256<<i);
+ st_case[i].ideal_count=70000;
+ st_case[i].n_replica=1;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ }
+ int success=st_hll_case_print(st_case, n_case);
+ //70000 using linear estimation, which is the worst case for precision 15.
+ //Much better at precision 9.
+ //EXPECT_EQ(success, n_case);
+ for(i=0; i<n_case; i++)
+ {
+ st_case[i].precision=9;
+ st_case[i].time_window_ms=10*(256<<i);
+ st_case[i].ideal_count=70000;
+ st_case[i].n_replica=1;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ }
+ success=st_hll_case_print(st_case, n_case);
+ EXPECT_EQ(success, n_case);
+}
+TEST(STHyperLogLog, Debug)
+{
+ struct st_hll_case st_case[64];
+ int i=0;
+
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=15;
+ st_case[i].time_window_ms=0;
+ st_case[i].ideal_count=70000;
+ st_case[i].n_replica=1;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ i++;
+
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=15;
+ st_case[i].time_window_ms=300;
+ st_case[i].ideal_count=70000;
+ st_case[i].n_replica=1;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ i++;
+
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=14;
+ st_case[i].time_window_ms=0;
+ st_case[i].ideal_count=40000;
+ st_case[i].n_replica=1;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ i++;
+
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=14;
+ st_case[i].time_window_ms=300;
+ st_case[i].ideal_count=40000;
+ st_case[i].n_replica=1;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ i++;
+
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=15;
+ st_case[i].time_window_ms=300;
+ st_case[i].ideal_count=10000;
+ st_case[i].n_replica=1;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ i++;
+
+ int success=st_hll_case_print(st_case, i);
+ EXPECT_EQ(success+1, i);
+}
+TEST(STHyperLogLog, Replicas)
+{
+
+ int i=0, n_case=8;
+ struct st_hll_case st_case[n_case];
+ for(i=0; i<n_case; i++)
+ {
+ st_hll_case_init(st_case+i);
+ st_case[i].precision=9;
+ st_case[i].time_window_ms=1000*(i+1);
+ st_case[i].ideal_count=8000;//2*1000*(1<<i);
+ st_case[i].n_replica=1+i;
+ st_case[i].sync_interval_ms=2;
+ st_case[i].est_count=st_hll_test(st_case+i);
+ }
+ int success=st_hll_case_print(st_case, n_case);
+ EXPECT_EQ(success, n_case);
+}
+TEST(STHyperLogLog, Serialize)
+{
+ struct timeval start;
+ gettimeofday(&start, NULL);
+ struct ST_hyperloglog *h=ST_hyperloglog_new(9, 5*1000, start);
+ size_t sz=ST_hyperloglog_serialized_size(h);
+ char *blob=NULL;
+ size_t blob_sz=0;
+ ST_hyperloglog_serialize(h, &blob, &blob_sz);
+ EXPECT_EQ(sz, blob_sz);
+ struct ST_hyperloglog *h2=ST_hyperloglog_deserialize(blob, blob_sz);
+ free(blob);
+ sz=ST_hyperloglog_serialized_size(h2);
+ EXPECT_EQ(sz, blob_sz);
+ ST_hyperloglog_free(h);
+ ST_hyperloglog_free(h2);
+}
+TEST(STHyperLogLog, Reconfigure)
+{
+ struct timeval start, step, now;
+ gettimeofday(&start, NULL);
+ start.tv_usec=0;
+ int n_replica=2;
+ struct ST_hyperloglog *h[n_replica];
+ int time_window_s=10;
+ unsigned char precision=6;
+ for(int i=0; i<n_replica; i++)
+ {
+ h[i]=ST_hyperloglog_new(precision, time_window_s*1000, start);
+ }
+ srand(173);
+ int key=1319823, j=0;
+ int n_add=0;
+ int per_step_count=100;
+ step.tv_sec=0;
+ step.tv_usec=1000;
+ int item_per_second=per_step_count*1000*1000/step.tv_usec;
+ now=start;
+ while(now.tv_sec-start.tv_sec<time_window_s*5)
+ {
+ timeradd(&now, &step, &now);
+ for(int i=0; i<per_step_count; i++)
+ {
+ j=random()%n_replica;
+ key++;
+ ST_hyperloglog_add(h[j], (const char *)&key, sizeof(key), now);
+ n_add++;
+ }
+ }
+ st_hll_sync(h, n_replica);
+ double hll_count=0, error=0, slack=1.2;
+ hll_count=ST_hyperloglog_count(h[0], now);
+ error=ST_hyperloglog_error_for_precision(precision);
+ EXPECT_NEAR(hll_count, item_per_second*time_window_s, error*item_per_second*time_window_s*slack);
+
+ time_window_s=20;
+ precision=13;
+ ST_hyperloglog_configure(h[0], precision, time_window_s*1000, now);
+ st_hll_sync(h, n_replica);
+ start=now;
+ n_add=0;
+ while(now.tv_sec-start.tv_sec<time_window_s*5)
+ {
+ timeradd(&now, &step, &now);
+ for(int i=0; i<per_step_count; i++)
+ {
+ j=random()%n_replica;
+ key++;
+ ST_hyperloglog_add(h[j], (const char *)&key, sizeof(key), now);
+ n_add++;
+ }
+ }
+ st_hll_sync(h, n_replica);
+ hll_count=ST_hyperloglog_count(h[0], now);
+ error=ST_hyperloglog_error_for_precision(precision);
+ EXPECT_NEAR(hll_count, item_per_second*time_window_s, error*item_per_second*time_window_s*slack);
+
+ for(int i=0; i<n_replica; i++)
+ {
+ ST_hyperloglog_free(h[i]);
+ }
+}
+TEST(STHyperLogLog, EventualConsistency)
+{
+ struct timeval start, step, now;
+ gettimeofday(&start, NULL);
+ memcpy(&now, &start, sizeof(now));
+ int n_replica=2;
+ struct ST_hyperloglog *h[n_replica];
+ int time_window_s=10;
+ unsigned char precision=6;
+ for(int i=0; i<n_replica; i++)
+ {
+ h[i]=ST_hyperloglog_new(precision, time_window_s*1000, start);
+ }
+ int key=1319823, j=0;
+ int n_add=0;
+ int per_step_count=100;
+ step.tv_sec=0;
+ step.tv_usec=1000;
+ int item_per_second=per_step_count*1000*1000/step.tv_usec;
+ while(now.tv_sec-start.tv_sec<time_window_s*5)
+ {
+ timeradd(&now, &step, &now);
+ for(int i=0; i<per_step_count; i++)
+ {
+ j=1+random()%(n_replica-1);
+ key++;
+ ST_hyperloglog_add(h[j], (const char *)&key, sizeof(key), now);
+ n_add++;
+ }
+ }
+ st_hll_sync(h, n_replica);
+ double hll_count=0, error=0;
+ hll_count=ST_hyperloglog_count(h[0], now);
+ error=ST_hyperloglog_error_for_precision(precision);
+ EXPECT_NEAR(hll_count, item_per_second*time_window_s, error*item_per_second*time_window_s*1.5);
+
+ for(int i=0; i<n_replica; i++)
+ {
+ ST_hyperloglog_free(h[i]);
+ }
+}
+TEST(STHyperLogLog, Merge)
+{
+ struct timeval start, now;
+ gettimeofday(&start, NULL);
+ memcpy(&now, &start, sizeof(now));
+ int n_replica=2;
+ struct ST_hyperloglog *h[n_replica];
+ int time_window_s=10;
+ unsigned char precision=6;
+ for(int i=0; i<n_replica; i++)
+ {
+ h[i]=ST_hyperloglog_new(precision, time_window_s*1000, start);
+ start.tv_sec+=time_window_s+1;
+ }
+ st_hll_sync(h, n_replica);
+ for(int i=0; i<n_replica; i++)
+ {
+ ST_hyperloglog_free(h[i]);
+ }
+}
+TEST(STHyperLogLog, Step)
+{
+ struct timeval start, step, now;
+ gettimeofday(&start, NULL);
+ memcpy(&now, &start, sizeof(now));
+ step.tv_sec=0;
+ step.tv_usec=10000;
+ int time_window_s=32;
+ unsigned char precision=8;
+ struct ST_hyperloglog *h=ST_hyperloglog_new(precision, time_window_s*1000, start);
+ int key=1;
+ int per_step_count=1;
+ int item_per_second=per_step_count*1000*1000/step.tv_usec;
+ while(now.tv_sec-start.tv_sec<time_window_s*10)
+ {
+ timeradd(&now, &step, &now);
+ for(int i=0; i<per_step_count; i++)
+ {
+ key++;
+ ST_hyperloglog_add(h, (const char *)&key, sizeof(key), now);
+ }
+ }
+ double hll_count=0;
+ double error=ST_hyperloglog_error_for_precision(precision);
+ hll_count=ST_hyperloglog_count(h, now);
+ printf("time_window: %d, count: %d, error: %.2f \n", time_window_s, item_per_second*time_window_s, error);
+ for(int i=0; i<time_window_s; i++)
+ {
+ ST_hyperloglog_step(h, now);
+ hll_count=ST_hyperloglog_count(h, now);
+ printf("t+%d, estimate: %.2f\n", i, hll_count);
+ now.tv_sec++;
+ //EXPECT_NEAR(hll_count, item_per_second*(time_window_s-i), error*item_per_second*(time_window_s-i));
+ }
+ ST_hyperloglog_free(h);
+ return;
+}
+
+void ap_bloom_sync(struct AP_bloom *list[], size_t n)
+{
+ char *blob=NULL;
+ size_t blob_sz=0;
+ if(n<2) return;
+ for(size_t i=0; i<n; i++)
+ {
+ AP_bloom_serialize(list[i], &blob, &blob_sz);
+ for(size_t j=0; j<n; j++)
+ {
+ if(j==i) continue;
+ AP_bloom_merge_blob(list[j], blob, blob_sz);
+ }
+ free(blob);
+ blob=NULL;
+ }
+ return;
+}
+
+struct ap_bloom_case
+{
+ int n_replica;
+ long long time_window_ms;
+ long long time_slice_num;
+ long long ideal_capacity;
+ double ideal_error_rate;
+ double crowdness;
+};
+void ap_bloom_test(struct ap_bloom_case *mycase)
+{
+ struct timeval start, now, step;
+ gettimeofday(&start, NULL);
+ start.tv_sec=1708094293;
+ start.tv_usec=319085;
+ struct AP_bloom *bloom[mycase->n_replica];
+ for(int i=0; i<mycase->n_replica; i++)
+ {
+ bloom[i]=AP_bloom_new(start, mycase->ideal_error_rate, mycase->ideal_capacity, mycase->time_window_ms, mycase->time_slice_num);
+ }
+ srand(171);
+ double per_second_count=(double)mycase->ideal_capacity * 1000 * mycase->crowdness/mycase->time_window_ms;
+ int per_step_count=0;
+ per_second_count=MAX(per_second_count, 1);
+ if(per_second_count>1000*1000)
+ {
+ step.tv_sec=0;
+ step.tv_usec=1;
+ per_step_count=per_second_count/1000/1000;
+ }
+ else
+ {
+ step.tv_sec=0;
+ step.tv_usec=(suseconds_t)1000*1000/per_second_count;
+ per_step_count=1;
+ }
+
+ now=start;
+ int key=1319823, started_key=key, most_recent_forgot_key=key;
+ int added=0;
+ struct timeval last_sync, most_recent_forgot_time;
+ last_sync=now;
+ long long sync_interval_ms = 100;
+ long long test_duration_us = mycase->time_window_ms*1000*10;
+ while(timeval_delta_us(start, now) < test_duration_us)
+ {
+ for(int i=0; i<per_step_count; i++)
+ {
+ int j=random()%mycase->n_replica;
+ if(test_duration_us - timeval_delta_us(start, now) >= mycase->time_window_ms*1000)
+ {
+ most_recent_forgot_key=key;
+ most_recent_forgot_time=now;
+ }
+ AP_bloom_add(bloom[j], now, (char *)&key, sizeof(key));
+ added++;
+ key++;
+ }
+ if(timeval_delta_ms(now, last_sync) > sync_interval_ms)
+ {
+ ap_bloom_sync(bloom, mycase->n_replica);
+ last_sync=now;
+ }
+ timeradd(&now, &step, &now);
+ }
+ ap_bloom_sync(bloom, mycase->n_replica);
+ struct AP_bloom_info info;
+ AP_bloom_info(bloom[0], &info);
+
+ long long true_positive=0;
+ int ret=0, checked=0;
+ for(int i=most_recent_forgot_key+1; i<key; i++)
+ {
+ int j=random()%mycase->n_replica;
+ ret = AP_bloom_check(bloom[j], now, (char *)&i, sizeof(i));
+ true_positive += ret;
+ assert(ret == 1);
+ checked++;
+ }
+ EXPECT_EQ(true_positive, checked);
+ long long false_positive=0;
+ checked=0;
+ for(int i=started_key; i<=most_recent_forgot_key; i++)
+ {
+ int j=random()%mycase->n_replica;
+ ret = AP_bloom_check(bloom[j], now, (char *)&i, sizeof(i));
+ //assert(ret == 0);
+ false_positive += ret;
+ checked++;
+ }
+ long long key_in_slice_plus_one = (key - most_recent_forgot_key + 1)/mycase->time_slice_num;
+ //long long slack = per_second_count*mycase->time_window_ms/1000/mycase->time_slice_num;
+ false_positive = MAX(0, false_positive-key_in_slice_plus_one);
+ double fp_rate=(double)false_positive/(per_second_count*mycase->time_window_ms);
+ //EXPECT_NEAR(fp_rate, mycase->ideal_error_rate, mycase->ideal_error_rate/2);
+ //EXPECT_NEAR(tp_rate, 1, 0);
+ size_t mem_size=AP_bloom_mem_size(bloom[0]);
+ printf("fp: %.4f, element_num: %lld, approx_num: %lld, max_expand: %lld, fill_ratio: %.2f, slice_num: %lld, mem_size: %zu\n",
+ fp_rate, (long long)(per_second_count*mycase->time_window_ms/1000), info.approximate_item_num,
+ info.max_expand_times, info.fill_ratio, info.total_slice_number, mem_size);
+ //printf("checked: %d, added: %d\n", checked, added);
+ for(int i=0; i<mycase->n_replica; i++)
+ {
+ AP_bloom_free(bloom[i]);
+ }
+}
+TEST(APBloom, Basic)
+{
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ long long time_window_ms=300;
+ long long capacity=1000;
+ double error_rate=0.001;
+ struct AP_bloom *bloom=AP_bloom_new(now, error_rate, capacity, time_window_ms, 3);
+ int key=11;
+ int ret=0;
+ struct timeval first_insert;
+ first_insert=now;
+ AP_bloom_add(bloom, first_insert, (char *)&key, sizeof(key));
+ ret=AP_bloom_check(bloom, now, (char *)&key, sizeof(key));
+ EXPECT_EQ(ret, 1);
+
+ now.tv_usec+=time_window_ms/10;
+ ret=AP_bloom_check(bloom, now, (char *)&key, sizeof(key));
+ EXPECT_EQ(ret, 1);
+
+ struct AP_bloom_info info;
+ AP_bloom_info(bloom, &info);
+ EXPECT_EQ(info.capacity, capacity);
+ EXPECT_EQ(info.error, error_rate);
+ EXPECT_EQ(info.time_window_ms, time_window_ms);
+ EXPECT_TRUE(timercmp(&info.oldest_item_time, &first_insert, <=));
+
+ now.tv_sec+=time_window_ms/1000+1;
+ ret=AP_bloom_check(bloom, now, (char *)&key, sizeof(key));
+ EXPECT_EQ(ret, 0);
+ key++;
+ ret=AP_bloom_check(bloom, now, (char *)&key, sizeof(key));
+ EXPECT_EQ(ret, 0);
+
+ AP_bloom_add(bloom, now, (char *)&key, sizeof(key));
+ ret=AP_bloom_check(bloom, now, (char *)&key, sizeof(key));
+ EXPECT_EQ(ret, 1);
+
+
+
+ //boudary test with invalid time.
+ now.tv_sec = 0;
+ ret=AP_bloom_check(bloom, now, (char *)&key, sizeof(key));
+ EXPECT_EQ(ret, 0);
+
+
+ AP_bloom_free(bloom);
+}
+TEST(APBloom, NoSliding)
+{
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ struct AP_bloom *bloom[2];
+ for(int i=0; i<2; i++)
+ {
+ bloom[i]=AP_bloom_new(now, 0.001, 1000, 0, 0);
+ }
+ int key=11;
+ int n_item=1000;
+ for(int i=0; i<n_item; i++)
+ {
+ AP_bloom_add(bloom[i%2], now, (char *)&key, sizeof(key));
+ key++;
+ now.tv_sec++;
+ }
+ ap_bloom_sync(bloom, 2);
+ key=11;
+ now.tv_sec += 100;
+ int count=0;
+ for(int i=0; i<n_item; i++)
+ {
+ count += AP_bloom_check(bloom[i%2], now, (char *)&key, sizeof(key));
+ key++;
+ }
+ EXPECT_EQ(count, n_item);
+ for(int i=0; i<2; i++)
+ {
+ AP_bloom_free(bloom[i]);
+ }
+}
+TEST(APBloom, Merge)
+{
+ struct timeval start, now;
+ gettimeofday(&start, NULL);
+ now=start;
+ long long timespan=300;
+ struct AP_bloom *bloom[2];
+
+ bloom[0]=AP_bloom_new(start, 0.001, 1000, timespan, 3);
+ start.tv_sec--;
+ bloom[1]=AP_bloom_new(start, 0.001, 1000, timespan, 3);
+ int key=11;
+ AP_bloom_add(bloom[0], now, (char *)&key, sizeof(key));
+ int ret=AP_bloom_check(bloom[0], now, (char *)&key, sizeof(key));
+ EXPECT_EQ(ret, 1);
+ ap_bloom_sync(bloom, 2);
+ ret=AP_bloom_check(bloom[1], now, (char *)&key, sizeof(key));
+ EXPECT_EQ(ret, 1);
+
+ for(int i=0; i<2; i++)
+ {
+ AP_bloom_free(bloom[i]);
+ }
+
+ //Test merge with different capacity and error rate.
+ bloom[0]=AP_bloom_new(start, 0.001, 1000, timespan, 3);
+ start.tv_sec++;
+ long long new_capacity=10000;
+ double new_error_rate=0.0001;
+ bloom[1]=AP_bloom_new(start, new_error_rate, new_capacity, timespan, 3);
+ AP_bloom_add(bloom[0], now, (char *)&key, sizeof(key));
+ ret=AP_bloom_check(bloom[0], now, (char *)&key, sizeof(key));
+ EXPECT_EQ(ret, 1);
+ ap_bloom_sync(bloom, 2);
+ ret=AP_bloom_check(bloom[1], now, (char *)&key, sizeof(key));
+ //bloom[1] is newer than bloom[0], so results in bloom[0] is discarded.
+ EXPECT_EQ(ret, 0);
+ struct AP_bloom_info info;
+ AP_bloom_info(bloom[0], &info);
+ EXPECT_EQ(info.capacity, new_capacity);
+ EXPECT_EQ(info.error, new_error_rate);
+ for(int i=0; i<2; i++)
+ {
+ AP_bloom_free(bloom[i]);
+ }
+}
+TEST(APBloom, Scalability)
+{
+ struct ap_bloom_case mycase[6]={
+ {1, 300, 8, 1000, 0.001, 0.1},
+ {1, 300, 8, 1000, 0.001, 1},
+ {2, 300, 8, 1000, 0.001, 10},
+ {2, 300, 8, 1000, 0.001, 20},
+ {2, 300, 8, 1000, 0.001, 100},
+ {2, 300, 8, 1000, 0.001, 200},
+ };
+ for(int i=0; i<6; i++)
+ {
+ ap_bloom_test(mycase+i);
+ }
+}
+TEST(APBloom, VariousCapacity)
+{
+ struct ap_bloom_case mycase[3]={
+ {2, 300, 13, 1000, 0.001, 1},
+ {1, 300, 13, 10000, 0.001, 1},
+ {2, 300, 13, 100000, 0.001, 2}
+ };
+ for(int i=0; i<3; i++)
+ {
+ ap_bloom_test(mycase+i);
+ }
+}
+TEST(APBloom, VariousWindow)
+{
+ int n_case=8;
+ struct ap_bloom_case mycase[n_case];
+
+ for(int i=0; i<n_case; i++)
+ {
+ mycase[i].n_replica=2;
+ mycase[i].time_window_ms=30*(1<<i);
+ mycase[i].time_slice_num=32;
+ mycase[i].ideal_capacity=10000;
+ mycase[i].ideal_error_rate=0.001;
+ mycase[i].crowdness=1.3;
+ }
+ for(int i=0; i<n_case; i++)
+ {
+ ap_bloom_test(mycase+i);
+ }
+}
+TEST(APBloom, VariousReplica)
+{
+ int n_case=6;
+ struct ap_bloom_case mycase[n_case];
+ for(int i=0; i<n_case; i++)
+ {
+ mycase[i].n_replica=i+1;
+ mycase[i].time_window_ms=30*1000;
+ mycase[i].time_slice_num=16;
+ mycase[i].ideal_capacity=10000;
+ mycase[i].ideal_error_rate=0.001;
+ mycase[i].crowdness=1.3;
+ }
+ for(int i=0; i<n_case; i++)
+ {
+ ap_bloom_test(mycase+i);
+ }
+}
+TEST(APBloom, VariousTimeSlice)
+{
+ int n_case=7;
+ struct ap_bloom_case mycase[n_case];
+ for(int i=0; i<n_case; i++)
+ {
+ mycase[i].n_replica=2;
+ mycase[i].time_window_ms=300*1000;
+ mycase[i].time_slice_num=1<<i;
+ mycase[i].ideal_capacity=10000;
+ mycase[i].ideal_error_rate=0.001;
+ mycase[i].crowdness=1.3;
+ }
+ for(int i=0; i<n_case; i++)
+ {
+ ap_bloom_test(mycase+i);
+ }
+}
+TEST(APBloom, VariousError)
+{
+ int n_case=6;
+ struct ap_bloom_case mycase[n_case];
+ for(int i=0; i<n_case; i++)
+ {
+ mycase[i].n_replica=2;
+ mycase[i].time_window_ms=300*1000;
+ mycase[i].time_slice_num=16;
+ mycase[i].ideal_capacity=10000;
+ mycase[i].ideal_error_rate=0.10;
+ for(int j=0; j<i; j++)
+ {
+ mycase[i].ideal_error_rate/=10;
+ }
+ mycase[i].crowdness=1.3;
+ }
+ for(int i=0; i<n_case; i++)
+ {
+ ap_bloom_test(mycase+i);
+ }
+}
+TEST(APBloom, Performance)
+{
+ struct ap_bloom_case mycase[1]={
+ {1, 300, 4, 100000, 0.001, 10}
+ };
+ for(int i=0; i<1; i++)
+ {
+ ap_bloom_test(mycase+i);
+ }
+}
+TEST(APBloom, Debug)
+{
+
+}
+int main(int argc, char ** argv)
+{
+ int ret=0;
+ ::testing::InitGoogleTest(&argc, argv);
+ ret=RUN_ALL_TESTS();
+ return ret;
+}
diff --git a/CRDT/st_hyperloglog.c b/CRDT/st_hyperloglog.c
index 897c32a..93bbb6e 100644
--- a/CRDT/st_hyperloglog.c
+++ b/CRDT/st_hyperloglog.c
@@ -20,76 +20,35 @@
#define INT_CEIL(num, denom) (((num) + (denom) - 1) / (denom))
#define PRECISION_TO_WORD(precision) INT_CEIL(NUM_REG(precision), REG_PER_WORD)
+#define RESET_TIME_SLOT_US(time_window_seconds, precision) (time_window_seconds*2*1000/NUM_REG(precision))
struct ST_HLL_configuration
{
unsigned char precision;
- unsigned char pad;
- unsigned short time_window_s;
- struct timeval timestamp;
+ unsigned char pad[7];
+ long long time_window_ms;
+ struct timeval last_cfg;
};
struct ST_hyperloglog
{
struct ST_HLL_configuration cfg;
- int reset_idx;
+ long long reset_idx;
struct timeval reset_time;
uint32_t *registers;
};
const size_t BLOB_HDR_SIZE= offsetof(struct ST_hyperloglog, registers);
#define REGISTER_SIZE(precision) INT_WIDTH*INT_CEIL(NUM_REG(precision), REG_PER_WORD)
-struct ST_hyperloglog *ST_hyperloglog_new(unsigned char precision, int time_window_seconds, const struct timeval now)
-{
- struct ST_hyperloglog *h=ALLOC(struct ST_hyperloglog, 1);
- // Ensure the precision is somewhat sane
- if (precision < HLL_MIN_PRECISION || precision > HLL_MAX_PRECISION)
- return NULL;
-
- // Store precision
- h->cfg.precision = precision;
- h->cfg.time_window_s=time_window_seconds;
- //memcpy(&h->cfg.timestamp, &now, sizeof(h->cfg.timestamp));
-
- memcpy(&h->reset_time, &now, sizeof(h->reset_time));
-
- int words = PRECISION_TO_WORD(precision);
- // Allocate and zero out the registers
- h->registers = ALLOC(uint32_t, words);
- return h;
-}
-void ST_hyperloglog_configure(struct ST_hyperloglog *h, unsigned char precision, int time_window_seconds, const struct timeval now)
-{
- h->cfg.time_window_s=time_window_seconds;
- if(h->cfg.precision != precision)
- {
- free(h->registers);
-
- h->cfg.precision=precision;
- // Allocate and zero out the registers
- h->registers = ALLOC(uint32_t, PRECISION_TO_WORD(h->cfg.precision));
-
- }
- memcpy(&h->cfg.timestamp, &now, sizeof(h->cfg.timestamp));
- return;
-}
-void ST_hyperloglog_free(struct ST_hyperloglog *h)
-{
- free(h->registers);
- h->registers=NULL;
- free(h);
- return;
-}
-
-static int get_register(const struct ST_hyperloglog *h, int idx) {
- uint32_t word = *(h->registers + (idx / REG_PER_WORD));
+static int get_register(const uint32_t *registers, int idx) {
+ uint32_t word = *(registers + (idx / REG_PER_WORD));
word = word >> REG_WIDTH * (idx % REG_PER_WORD);
return word & ((1 << REG_WIDTH) - 1);
}
-static void set_register(const struct ST_hyperloglog *h, int idx, int val) {
- uint32_t *word = h->registers + (idx / REG_PER_WORD);
+static void set_register(uint32_t *registers, int idx, int val) {
+ uint32_t *word = registers + (idx / REG_PER_WORD);
// Shift the val into place
unsigned shift = REG_WIDTH * (idx % REG_PER_WORD);
@@ -100,15 +59,29 @@ static void set_register(const struct ST_hyperloglog *h, int idx, int val) {
*word = (*word & ~val_mask) | val;
return;
}
-static void reset_register(const struct ST_hyperloglog *h, int idx)
+static void reset_register(uint32_t *registers, int idx)
{
- uint32_t *word = h->registers + (idx / REG_PER_WORD);
+ uint32_t *word = registers + (idx / REG_PER_WORD);
unsigned shift = REG_WIDTH * (idx % REG_PER_WORD);
uint32_t val_mask = ((1 << REG_WIDTH) - 1) << shift;
*word &= ~val_mask;
}
-int hll_add_hash(struct ST_hyperloglog *h, uint64_t hash)
+static uint32_t *create_register(int precision)
+{
+ return ALLOC(uint32_t, PRECISION_TO_WORD(precision));
+}
+static void free_register(uint32_t *registers)
+{
+ free(registers);
+ return;
+}
+static void clear_register(uint32_t *registers, int precision)
+{
+ memset(registers, 0, PRECISION_TO_WORD(precision)*sizeof(uint32_t));
+ return;
+}
+static int st_hll_add_hash(struct ST_hyperloglog *h, uint64_t hash)
{
// Determine the index using the first p bits
int idx = hash >> (64 - h->cfg.precision);
@@ -120,150 +93,15 @@ int hll_add_hash(struct ST_hyperloglog *h, uint64_t hash)
int leading = __builtin_clzll(hash) + 1;
// Update the register if the new value is larger
- if (leading > get_register(h, idx)) {
- set_register(h, idx, leading);
+ if (leading > get_register(h->registers, idx)) {
+ set_register(h->registers, idx, leading);
return 1;
}
return 0;
}
-static void periodic_reset(struct ST_hyperloglog *h, const struct timeval now)
-{
- if(h->cfg.time_window_s==0) return;
- int num_reg=NUM_REG(h->cfg.precision);
- int reset_time_slot_us=h->cfg.time_window_s*2*1000*1000/num_reg;
- long long delta_us=timeval_delta_us(h->reset_time, now);
-
- struct timeval step;
- step.tv_sec=reset_time_slot_us/1000/1000;
- step.tv_usec=reset_time_slot_us%(1000*1000);
-
- if(delta_us>reset_time_slot_us)
- {
- for(int i=0; i<delta_us/reset_time_slot_us; i++)
- {
- reset_register(h, h->reset_idx);
- h->reset_idx = (h->reset_idx+1)%num_reg;
- timeradd(&h->reset_time, &step, &h->reset_time);
- }
- }
-}
-int ST_hyperloglog_add(struct ST_hyperloglog *h, const char *key, size_t keylen, const struct timeval now)
-{
- periodic_reset(h, now);
-
- uint64_t hash=0;
- hash=XXH3_64bits_withSeed(key, keylen, 171);
- return hll_add_hash(h, hash);
-}
-static struct ST_hyperloglog *ST_hyperloglog_copy(const struct ST_hyperloglog *src)
-{
- struct ST_hyperloglog *dst=ST_hyperloglog_new(src->cfg.precision, src->cfg.time_window_s, src->reset_time);
- size_t num_reg = NUM_REG(src->cfg.precision);
- size_t words = INT_CEIL(num_reg, REG_PER_WORD);
-
- memcpy(dst->registers, src->registers, words*sizeof(int32_t));
- return dst;
-}
-void ST_hyperloglog_step(struct ST_hyperloglog *h, const struct timeval now)
-{
- periodic_reset(h, now);
- return;
-}
-void ST_hyperloglog_merge(struct ST_hyperloglog *dst, const struct ST_hyperloglog *src)
-{
-
- if(timercmp(&(dst->cfg.timestamp), &(src->cfg.timestamp), <))//Last-Write-Wins
- {
- ST_hyperloglog_configure(dst, src->cfg.precision, src->cfg.time_window_s, src->cfg.timestamp);
- }
- if(dst->cfg.precision != src->cfg.precision) return;
- if(dst->reset_time.tv_sec - src->reset_time.tv_sec > dst->cfg.time_window_s)//src is too old, no need to merge
- {
- return;
- }
- else if(src->reset_time.tv_sec - dst->reset_time.tv_sec > dst->cfg.time_window_s) //dst is too old, just copy the src register
- {
- dst->reset_idx=src->reset_idx;
- memcpy(&dst->reset_time, &src->reset_time, sizeof(src->reset_time));
- memcpy(dst->registers, src->registers, PRECISION_TO_WORD(dst->cfg.precision)*sizeof(int32_t));
- }
- else
- {
- struct ST_hyperloglog *tmp=ST_hyperloglog_copy(src);//create a copy of src for periodic reset
- if(timercmp(&(dst->reset_time), &(tmp->reset_time), <))//correct with latest timestamp
- {
- periodic_reset(dst, tmp->reset_time);
- }
- else
- {
- periodic_reset(tmp, dst->reset_time);
- }
- int n_register=NUM_REG(dst->cfg.precision);
- int s_reg=0, d_reg=0;
- for(int i=0; i<n_register; i++)
- {
-
- s_reg=get_register(tmp, i);
- d_reg=get_register(dst, i);
- set_register(dst, i, MAX(s_reg, d_reg));
- }
- ST_hyperloglog_free(tmp);
- }
- return;
-}
-size_t ST_hyperloglog_serialized_size(const struct ST_hyperloglog *h)
-{
- size_t sz=0;
- size_t num_reg = NUM_REG(h->cfg.precision);
- size_t words = INT_CEIL(num_reg, REG_PER_WORD);
-
- sz += BLOB_HDR_SIZE;
- sz += words*sizeof(int32_t);
- return sz;
-}
-void ST_hyperloglog_serialize(const struct ST_hyperloglog *h, char **blob, size_t *blob_sz)
-{
- size_t sz=0, offset=0;
- size_t num_reg = NUM_REG(h->cfg.precision);
- size_t words = INT_CEIL(num_reg, REG_PER_WORD);
-
- sz = ST_hyperloglog_serialized_size(h);
-
- char *buffer = ALLOC(char, sz);
- memcpy(buffer+offset, h, BLOB_HDR_SIZE);
- offset += BLOB_HDR_SIZE;
-
- memcpy(buffer+offset, h->registers, words*sizeof(int32_t));
- offset += words*sizeof(int32_t);
- *blob_sz=sz;
- *blob=buffer;
- return;
-}
-struct ST_hyperloglog *ST_hyperloglog_deserialize(const char *blob, size_t blob_sz)
-{
- struct ST_hyperloglog *h=ALLOC(struct ST_hyperloglog, 1);
- size_t offset=0;
- memcpy(h, blob, BLOB_HDR_SIZE);
- offset += BLOB_HDR_SIZE;
-
- size_t num_reg = NUM_REG(h->cfg.precision);
- size_t words = INT_CEIL(num_reg, REG_PER_WORD);
- h->registers=ALLOC(uint32_t, words);
- memcpy(h->registers, blob+offset, words*sizeof(int32_t));
- return h;
-}
-void ST_hyperloglog_merge_blob(struct ST_hyperloglog *dst, const char *blob, size_t blob_sz)
-{
- struct ST_hyperloglog *src=ST_hyperloglog_deserialize(blob, blob_sz);
- ST_hyperloglog_merge(dst, src);
- ST_hyperloglog_free(src);
- return;
-}
-static double g_switchThreshold[15] = {10, 20, 40, 80, 220, 400, 900, 1800, 3100, 6500,
- 11500, 20000, 50000, 120000, 350000};
-static double *g_rawEstimateData[] = {
+static const double *g_rawEstimateData[] = {
// precision 4
(double[]) { 11, 11.717, 12.207, 12.7896, 13.2882, 13.8204, 14.3772, 14.9342, 15.5202, 16.161, 16.7722, 17.4636, 18.0396, 18.6766, 19.3566, 20.0454, 20.7936, 21.4856, 22.2666, 22.9946, 23.766, 24.4692, 25.3638, 26.0764, 26.7864, 27.7602, 28.4814, 29.433, 30.2926, 31.0664, 31.9996, 32.7956, 33.5366, 34.5894, 35.5738, 36.2698, 37.3682, 38.0544, 39.2342, 40.0108, 40.7966, 41.9298, 42.8704, 43.6358, 44.5194, 45.773, 46.6772, 47.6174, 48.4888, 49.3304, 50.2506, 51.4996, 52.3824, 53.3078, 54.3984, 55.5838, 56.6618, 57.2174, 58.3514, 59.0802, 60.1482, 61.0376, 62.3598, 62.8078, 63.9744, 64.914, 65.781, 67.1806, 68.0594, 68.8446, 69.7928, 70.8248, 71.8324, 72.8598, 73.6246, 74.7014, 75.393, 76.6708, 77.2394, },
// precision 5
@@ -296,7 +134,7 @@ static double *g_rawEstimateData[] = {
(double[]) { 189084, 192250.913, 195456.774, 198696.946, 201977.762, 205294.444, 208651.754, 212042.099, 215472.269, 218941.91, 222443.912, 225996.845, 229568.199, 233193.568, 236844.457, 240543.233, 244279.475, 248044.27, 251854.588, 255693.2, 259583.619, 263494.621, 267445.385, 271454.061, 275468.769, 279549.456, 283646.446, 287788.198, 291966.099, 296181.164, 300431.469, 304718.618, 309024.004, 313393.508, 317760.803, 322209.731, 326675.061, 331160.627, 335654.47, 340241.442, 344841.833, 349467.132, 354130.629, 358819.432, 363574.626, 368296.587, 373118.482, 377914.93, 382782.301, 387680.669, 392601.981, 397544.323, 402529.115, 407546.018, 412593.658, 417638.657, 422762.865, 427886.169, 433017.167, 438213.273, 443441.254, 448692.421, 453937.533, 459239.049, 464529.569, 469910.083, 475274.03, 480684.473, 486070.26, 491515.237, 496995.651, 502476.617, 507973.609, 513497.19, 519083.233, 524726.509, 530305.505, 535945.728, 541584.404, 547274.055, 552967.236, 558667.862, 564360.216, 570128.148, 575965.08, 581701.952, 587532.523, 593361.144, 599246.128, 605033.418, 610958.779, 616837.117, 622772.818, 628672.04, 634675.369, 640574.831, 646585.739, 652574.547, 658611.217, 664642.684, 670713.914, 676737.681, 682797.313, 688837.897, 694917.874, 701009.882, 707173.648, 713257.254, 719415.392, 725636.761, 731710.697, 737906.209, 744103.074, 750313.39, 756504.185, 762712.579, 768876.985, 775167.859, 781359, 787615.959, 793863.597, 800245.477, 806464.582, 812785.294, 819005.925, 825403.057, 831676.197, 837936.284, 844266.968, 850642.711, 856959.756, 863322.774, 869699.931, 876102.478, 882355.787, 888694.463, 895159.952, 901536.143, 907872.631, 914293.672, 920615.14, 927130.974, 933409.404, 939922.178, 946331.47, 952745.93, 959209.264, 965590.224, 972077.284, 978501.961, 984953.19, 991413.271, 997817.479, 1004222.658, 1010725.676, 1017177.138, 1023612.529, 1030098.236, 1036493.719, 1043112.207, 1049537.036, 1056008.096, 1062476.184, 1068942.337, 1075524.95, 1081932.864, 1088426.025, 1094776.005, 1101327.448, 1107901.673, 1114423.639, 1120884.602, 1127324.923, 1133794.24, 1140328.886, 1146849.376, 1153346.682, 1159836.502, 1166478.703, 1172953.304, 1179391.502, 1185950.982, 1192544.052, 1198913.41, 1205430.994, 1212015.525, 1218674.042, 1225121.683, 1231551.101, 1238126.379, 1244673.795, 1251260.649, 1257697.86, 1264320.983, 1270736.319, 1277274.694, 1283804.95, 1290211.514, 1296858.568, 1303455.691, }
};
-static double *g_biasData[] = {
+static const double *g_biasData[] = {
// precision 4
(double[]) { 10, 9.717, 9.207, 8.7896, 8.2882, 7.8204, 7.3772, 6.9342, 6.5202, 6.161, 5.7722, 5.4636, 5.0396, 4.6766, 4.3566, 4.0454, 3.7936, 3.4856, 3.2666, 2.9946, 2.766, 2.4692, 2.3638, 2.0764, 1.7864, 1.7602, 1.4814, 1.433, 1.2926, 1.0664, 0.999600000000001, 0.7956, 0.5366, 0.589399999999998, 0.573799999999999, 0.269799999999996, 0.368200000000002, 0.0544000000000011, 0.234200000000001, 0.0108000000000033, -0.203400000000002, -0.0701999999999998, -0.129600000000003, -0.364199999999997, -0.480600000000003, -0.226999999999997, -0.322800000000001, -0.382599999999996, -0.511200000000002, -0.669600000000003, -0.749400000000001, -0.500399999999999, -0.617600000000003, -0.6922, -0.601599999999998, -0.416200000000003, -0.338200000000001, -0.782600000000002, -0.648600000000002, -0.919800000000002, -0.851799999999997, -0.962400000000002, -0.6402, -1.1922, -1.0256, -1.086, -1.21899999999999, -0.819400000000002, -0.940600000000003, -1.1554, -1.2072, -1.1752, -1.16759999999999, -1.14019999999999, -1.3754, -1.29859999999999, -1.607, -1.3292, -1.7606, },
// precision 5
@@ -345,56 +183,10 @@ static double alpha(unsigned char precision) {
}
}
-/*
- * Computes the raw cardinality estimate
- */
-static double raw_estimate(const struct ST_hyperloglog *h, int *num_zero)
+// Estimates cardinality using a linear counting. Used when some registers still have a zero value.
+static double linear_count(int num_reg, int num_zero)
{
- unsigned char precision = h->cfg.precision;
- int num_reg = NUM_REG(precision);
- double multi = alpha(precision) * num_reg * num_reg;
-
- int reg_val;
- double inv_sum = 0;
- for (int i=0; i < num_reg; i++) {
- reg_val = get_register(h, i);
- inv_sum += pow(2.0, -1 * reg_val);
- if (!reg_val) *num_zero += 1;
- }
- return multi * (1.0 / inv_sum);
-}
-
-/*
- * Estimates cardinality using a linear counting.
- * Used when some registers still have a zero value.
- */
-static double linear_count(const struct ST_hyperloglog *h, int num_zero)
-{
- int registers = NUM_REG(h->cfg.precision);
- return registers *
- log((double)registers / (double)num_zero);
-}
-static double linear_count2(const struct ST_hyperloglog *h, int num_zero)
-{
- int m = NUM_REG(h->cfg.precision);
- int n_calc=0, n_zero=0;
-
- double Mi=0, Wi=0;
- for (int i=0; i < m; i++)
- {
- Wi=(i>=h->reset_idx)?(m+h->reset_idx-i):(h->reset_idx-i)+1;
- Mi = get_register(h, i);
- if(Wi>m/8)
- {
- n_calc++;
- if(Mi==0)
- {
- n_zero++;
- }
- }
-
- }
- double est=m* log((double)n_calc / (double)n_zero);
+ double est = num_reg * log((double)num_reg / (double)num_zero);
return est;
}
/**
@@ -420,10 +212,9 @@ static int binary_search(double val, int num, const double *array) {
* empircal data collected by Google, from the
* paper mentioned above.
*/
-static double bias_estimate(const struct ST_hyperloglog *h, double raw_est) {
+static double bias_estimate(int precision, double raw_est) {
// Determine the samples available
int samples;
- int precision = h->cfg.precision;
switch (precision) {
case 4:
samples = 80;
@@ -437,8 +228,8 @@ static double bias_estimate(const struct ST_hyperloglog *h, double raw_est) {
}
// Get the proper arrays based on precision
- double *estimates = *(g_rawEstimateData+(precision-4));
- double *biases = *(g_biasData+(precision-4));
+ const double *estimates = *(g_rawEstimateData+(precision-4));
+ const double *biases = *(g_biasData+(precision-4));
// Get the matching biases
int idx = binary_search(raw_est, samples, estimates);
@@ -449,82 +240,293 @@ static double bias_estimate(const struct ST_hyperloglog *h, double raw_est) {
else
return (biases[idx] + biases[idx-1]) / 2;
}
-static double raw_estimate2(const struct ST_hyperloglog *h, int *num_zero)
+/*
+void a(void)
+{
+ if(delta_us>reset_time_slot_us)
+ {
+ for(int i=0; i<delta_us/reset_time_slot_us; i++)
+ {
+ reset_register(h, h->reset_idx);
+ h->reset_idx = (h->reset_idx+1)%num_reg;
+ timeradd(&h->reset_time, &step, &h->reset_time);
+ }
+ }
+}
+*/
+
+// Computes the raw cardinality estimate
+static double raw_estimate(const struct ST_hyperloglog *h, int *num_zero, int *effective_reg)
{
unsigned char precision = h->cfg.precision;
int m = NUM_REG(precision);
- int n_calc=0;
- double Mi=0, Wi=0;
+ double Mi=0, Wi=0;//Wi as the time window span of the i-th register Mi.
double lambda=0;
double harmonic_mean=0;
+ *num_zero=0;
+ *effective_reg=0;
for (int i=0; i < m; i++)
{
Wi=(i>=h->reset_idx)?(m+h->reset_idx-i):(h->reset_idx-i)+1;
- Mi = get_register(h, i);
- if(!Mi)
- *num_zero += 1;
- //if (!Mi) *num_zero += 1;
- if( Wi < m/8 && h->cfg.time_window_s)
+ //neglect the 1/8 of the lowest registers to reduce the variance in the final evaluation.
+ if( Wi < m/8 && h->cfg.time_window_ms)
{
continue;
}
-
+ (*effective_reg)++;
+ Mi = get_register(h->registers, i);
+ if(!Mi)
+ *num_zero += 1;
lambda=pow(2.0, Mi-1)*m*m/Wi;
harmonic_mean += 1/lambda;
- n_calc++;
}
- harmonic_mean=n_calc/harmonic_mean;
+ harmonic_mean= (*effective_reg)/harmonic_mean;
return harmonic_mean;
}
- __attribute__ ((unused)) static double ST_HLL_count_no_sliding(const struct ST_hyperloglog *h)
-{
- int num_zero = 0;
- int num_reg = NUM_REG(h->cfg.precision);
- double raw_est = raw_estimate(h, &num_zero);
- // Check if we need to apply bias correction
-
- if (raw_est <= 5 * num_reg) {
- raw_est -= bias_estimate(h, raw_est);
- }
-
- // Check if linear counting should be used
- double alt_est;
- if (num_zero) {
- alt_est = linear_count(h, num_zero);
- } else {
- alt_est = raw_est;
- }
-
- // Determine which estimate to use
- if (alt_est <= g_switchThreshold[h->cfg.precision-4]) {
- return alt_est;
- } else {
- return raw_est;
- }
-}
-double ST_hyperloglog_count(const struct ST_hyperloglog *h)
+static double st_hll_count(const struct ST_hyperloglog *h)
{
- int num_zero=0;
- double raw_est=raw_estimate2(h, &num_zero);
+
+ int num_zero=0, effective_reg=0;
+ double raw_est=raw_estimate(h, &num_zero, &effective_reg);
raw_est*=alpha(h->cfg.precision);
-
+ double est=0;
double num_reg=NUM_REG(h->cfg.precision);
if (raw_est <= 5 * num_reg) {
- raw_est -= bias_estimate(h, raw_est);
+ raw_est -= bias_estimate(h->cfg.precision, raw_est);
}
if(raw_est<=5*num_reg/2)
{
if(num_zero)
- return linear_count2(h, num_zero);
+ {
+ est=linear_count(effective_reg, num_zero);
+ assert(est>=0);
+ return est;
+ }
+
}
if(raw_est>INT32_MAX/30)
{
return INT32_MIN*log(1-raw_est/INT32_MAX);
}
+ assert(raw_est>=0);
return raw_est;
}
+static void st_hll_slide_window(struct ST_hyperloglog *h, struct timeval now)
+{
+ if(h->cfg.time_window_ms==0)
+ return;
+ long long reset_time_slot_us=RESET_TIME_SLOT_US(h->cfg.time_window_ms, h->cfg.precision);
+ long long delta_us=timeval_delta_us(h->reset_time, now);
+ long long elapse_us=timeval_delta_us(h->cfg.last_cfg, h->reset_time);
+ assert(elapse_us%reset_time_slot_us==0);
+ assert(h->reset_idx == (int)(elapse_us/reset_time_slot_us) % NUM_REG(h->cfg.precision));
+ if(delta_us < reset_time_slot_us)
+ return;
+ if(delta_us/reset_time_slot_us > NUM_REG(h->cfg.precision))
+ {
+ clear_register(h->registers, h->cfg.precision);
+ h->reset_idx = (h->reset_idx + delta_us/reset_time_slot_us) % NUM_REG(h->cfg.precision);
+ struct timeval elapse;
+ delta_us -= (delta_us%reset_time_slot_us);
+ elapse.tv_sec=delta_us/1000/1000;
+ elapse.tv_usec=delta_us%(1000*1000);
+ timeradd(&h->reset_time, &elapse, &h->reset_time);
+ return;
+ }
+ struct timeval step;
+ step.tv_sec=reset_time_slot_us/1000/1000;
+ step.tv_usec=reset_time_slot_us%(1000*1000);
+
+ for(int i=0; i<delta_us/reset_time_slot_us; i++)
+ {
+ reset_register(h->registers, h->reset_idx);
+ h->reset_idx = (h->reset_idx+1)%NUM_REG(h->cfg.precision);
+ timeradd(&h->reset_time, &step, &h->reset_time);
+ }
+ return;
+}
+struct ST_hyperloglog *ST_hyperloglog_new(unsigned char precision, long long time_window_seconds, const struct timeval now)
+{
+ struct ST_hyperloglog *h=ALLOC(struct ST_hyperloglog, 1);
+ // Ensure the precision is somewhat sane
+ if (precision < HLL_MIN_PRECISION || precision > HLL_MAX_PRECISION)
+ return NULL;
+
+ // Store precision
+ h->cfg.precision = precision;
+ h->cfg.time_window_ms=time_window_seconds;
+ h->cfg.last_cfg=now;
+ h->reset_time=now;
+ h->reset_idx=0;
+
+ h->registers = create_register(h->cfg.precision);
+ return h;
+}
+void ST_hyperloglog_configure(struct ST_hyperloglog *h, unsigned char precision, int time_window_seconds, const struct timeval now)
+{
+ h->cfg.time_window_ms=time_window_seconds;
+ free_register(h->registers);
+ h->cfg.precision=precision;
+ h->registers = create_register(h->cfg.precision);
+ h->cfg.last_cfg=now;
+ h->reset_time=now;
+ h->reset_idx=0;
+
+ return;
+}
+void ST_hyperloglog_free(struct ST_hyperloglog *h)
+{
+ free(h->registers);
+ h->registers=NULL;
+ free(h);
+ return;
+}
+#define ST_HLL_HASH_SEED 171
+int ST_hyperloglog_add(struct ST_hyperloglog *h, const char *key, size_t keylen, const struct timeval now)
+{
+ st_hll_slide_window(h, now);
+
+ uint64_t hash=0;
+ hash=XXH3_64bits_withSeed(key, keylen, ST_HLL_HASH_SEED);
+ return st_hll_add_hash(h, hash);
+}
+int ST_hyperloglog_add_hash(struct ST_hyperloglog *h, uint64_t hash, const struct timeval now)
+{
+ st_hll_slide_window(h, now);
+ return st_hll_add_hash(h, hash);
+}
+struct ST_hyperloglog *ST_hyperloglog_duplicate(const struct ST_hyperloglog *origin)
+{
+ struct ST_hyperloglog *copy=ST_hyperloglog_new(origin->cfg.precision, origin->cfg.time_window_ms, origin->reset_time);
+ memcpy(&copy->cfg, &origin->cfg, sizeof(struct ST_HLL_configuration));
+ size_t num_reg = NUM_REG(origin->cfg.precision);
+ size_t words = INT_CEIL(num_reg, REG_PER_WORD);
+ copy->reset_idx=origin->reset_idx;
+ copy->reset_time=origin->reset_time;
+ memcpy(copy->registers, origin->registers, words*sizeof(int32_t));
+ return copy;
+}
+void ST_hyperloglog_step(struct ST_hyperloglog *h, const struct timeval now)
+{
+ st_hll_slide_window(h, now);
+ return;
+}
+//#define DEBUG
+void ST_hyperloglog_merge(struct ST_hyperloglog *dst, const struct ST_hyperloglog *src)
+{
+#ifdef DEBUG
+ double dst_cnt_before_merge, dst_cnt_after_merge, src_cnt_before_merge, src_cnt_after_merge;
+ dst_cnt_before_merge=st_hll_count(dst);
+ src_cnt_before_merge=st_hll_count(src);
+#endif
+ if(timercmp(&(dst->cfg.last_cfg), &(src->cfg.last_cfg), <))//Last-Write-Wins
+ {
+ ST_hyperloglog_configure(dst, src->cfg.precision, src->cfg.time_window_ms, src->cfg.last_cfg);
+ }
+ else if(timercmp(&(dst->cfg.last_cfg), &(src->cfg.last_cfg), >))
+ {
+ return;
+ }
+ if(dst->cfg.precision != src->cfg.precision) return;
+ struct ST_hyperloglog *tmp=ST_hyperloglog_duplicate(src);//create a copy of src for periodic reset
+ if(timercmp(&(dst->reset_time), &(tmp->reset_time), <))//correct with latest timestamp
+ {
+ st_hll_slide_window(dst, tmp->reset_time);
+ }
+ else
+ {
+ st_hll_slide_window(tmp, dst->reset_time);
+ }
+ int n_register=NUM_REG(dst->cfg.precision);
+ int s_reg=0, d_reg=0;
+ assert(dst->reset_idx==tmp->reset_idx);
+ for(int i=0; i<n_register; i++)
+ {
+
+ s_reg=get_register(tmp->registers, i);
+ d_reg=get_register(dst->registers, i);
+ set_register(dst->registers, i, MAX(s_reg, d_reg));
+ }
+#ifdef DEBUG
+ dst_cnt_after_merge=st_hll_count(dst);
+ src_cnt_after_merge=st_hll_count(tmp);
+ printf("dst: %f -> %f, src: %f -> %f\n", dst_cnt_before_merge, dst_cnt_after_merge, src_cnt_before_merge, src_cnt_after_merge);
+#endif
+ ST_hyperloglog_free(tmp);
+ return;
+}
+size_t ST_hyperloglog_serialized_size(const struct ST_hyperloglog *h)
+{
+ size_t sz=0;
+ size_t num_reg = NUM_REG(h->cfg.precision);
+ size_t words = INT_CEIL(num_reg, REG_PER_WORD);
+
+ sz += BLOB_HDR_SIZE;
+ sz += words*sizeof(int32_t);
+ return sz;
+}
+void ST_hyperloglog_serialize(const struct ST_hyperloglog *h, char **blob, size_t *blob_sz)
+{
+ size_t sz=0, offset=0;
+ size_t num_reg = NUM_REG(h->cfg.precision);
+ size_t words = INT_CEIL(num_reg, REG_PER_WORD);
+
+ sz = ST_hyperloglog_serialized_size(h);
+
+ char *buffer = ALLOC(char, sz);
+ memcpy(buffer+offset, h, BLOB_HDR_SIZE);
+ offset += BLOB_HDR_SIZE;
+
+ memcpy(buffer+offset, h->registers, words*sizeof(int32_t));
+ offset += words*sizeof(int32_t);
+ *blob_sz=sz;
+ *blob=buffer;
+ return;
+}
+struct ST_hyperloglog *ST_hyperloglog_deserialize(const char *blob, size_t blob_sz)
+{
+ struct ST_hyperloglog *h=ALLOC(struct ST_hyperloglog, 1);
+ size_t offset=0;
+ memcpy(h, blob, BLOB_HDR_SIZE);
+ offset += BLOB_HDR_SIZE;
+
+ size_t num_reg = NUM_REG(h->cfg.precision);
+ size_t words = INT_CEIL(num_reg, REG_PER_WORD);
+
+ h->registers=ALLOC(uint32_t, words);
+ memcpy(h->registers, blob+offset, words*sizeof(int32_t));
+ return h;
+}
+void ST_hyperloglog_merge_blob(struct ST_hyperloglog *dst, const char *blob, size_t blob_sz)
+{
+ struct ST_hyperloglog *src=ST_hyperloglog_deserialize(blob, blob_sz);
+ ST_hyperloglog_merge(dst, src);
+ ST_hyperloglog_free(src);
+ return;
+}
+
+double ST_hyperloglog_count(const struct ST_hyperloglog *h, struct timeval now)
+{
+ long long delta_us = timeval_delta_us(h->reset_time, now);
+ double est=0;
+ if(h->cfg.time_window_ms && delta_us >= RESET_TIME_SLOT_US(h->cfg.time_window_ms, h->cfg.precision))
+ {
+ struct ST_hyperloglog *h_copy=ST_hyperloglog_duplicate(h);
+ st_hll_slide_window(h_copy, now);
+ est=st_hll_count(h_copy);
+ ST_hyperloglog_free(h_copy);
+ assert(est>=0);
+ }
+ else
+ {
+ est=st_hll_count(h);
+ assert(est>=0);
+ }
+ assert(est>=0);
+ return est;
+}
/**
* Computes the upper bound on variance given
diff --git a/CRDT/st_hyperloglog.h b/CRDT/st_hyperloglog.h
index cb43c75..ad6fb90 100644
--- a/CRDT/st_hyperloglog.h
+++ b/CRDT/st_hyperloglog.h
@@ -11,7 +11,7 @@
#pragma once
#include <stddef.h>
#include <sys/time.h>
-#include <uuid/uuid.h>
+#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
@@ -21,12 +21,30 @@ extern "C"
#define HLL_MAX_PRECISION 18 // 262,144 registers
struct ST_hyperloglog;
-struct ST_hyperloglog *ST_hyperloglog_new(unsigned char precision, int time_window_seconds, const struct timeval now);
+/** ***************************************************************************
+ * Create a Staggered HyperLogLog (STHLL) that can estimate cardinality of a sliding twindow.
+ *
+ * Parameters:
+ * -----------
+ * precision - The precision of the HyperLogLog. The number of registers is 2^precision. The value should be between 4 to 18.
+ * time_window_ms - The duration of the time window in milliseconds.
+ * now - The current time.
+*
+ * Return:
+ * -------
+ * Pointer to the created STHLL - on success
+ * NULL - on failure
+ *
+ */
+struct ST_hyperloglog *ST_hyperloglog_new(unsigned char precision, long long time_window_ms, const struct timeval now);
void ST_hyperloglog_free(struct ST_hyperloglog *h);
+struct ST_hyperloglog *ST_hyperloglog_duplicate(const struct ST_hyperloglog *origin);
+
//Return 1 if at least 1 ST HyperLogLog internal register was altered. 0 otherwise.
int ST_hyperloglog_add(struct ST_hyperloglog *h, const char *key, size_t keylen, const struct timeval now);
+int ST_hyperloglog_add_hash(struct ST_hyperloglog *h, uint64_t hash, const struct timeval now);
void ST_hyperloglog_step(struct ST_hyperloglog *h, const struct timeval now);
-double ST_hyperloglog_count(const struct ST_hyperloglog *h);
+double ST_hyperloglog_count(const struct ST_hyperloglog *h, struct timeval now);
size_t ST_hyperloglog_serialized_size(const struct ST_hyperloglog *h);
void ST_hyperloglog_serialize(const struct ST_hyperloglog *h, char **blob, size_t *blob_sz);
struct ST_hyperloglog *ST_hyperloglog_deserialize(const char *blob, size_t blob_sz);
diff --git a/CRDT/crdt_tb_gtest.cpp b/CRDT/tb_crdt_gtest.cpp
index bdc8acc..d38b356 100644
--- a/CRDT/crdt_tb_gtest.cpp
+++ b/CRDT/tb_crdt_gtest.cpp
@@ -106,7 +106,7 @@ void traffic_distribution(traffic_type type)
for (i = 0; i < REPLICA_NUMBER; i++)
{
uuid_generate(uuid);
- buckets[i] = OC_token_bucket_new(uuid, start, CIR, CBS);
+ buckets[i] = OC_token_bucket_new(uuid, start, CIR, 1, CBS);
}
long long tokens = 0, flexible_tokens = 0;
long long consumed = 0, requested = 0, upper_limit = 0;
@@ -162,7 +162,7 @@ TEST(OCTokenBucket, Basic)
struct timeval now;
gettimeofday(&now, NULL);
- bucket=OC_token_bucket_new(uuid, now, CIR, CBS);
+ bucket=OC_token_bucket_new(uuid, now, CIR, 1, CBS);
long long tokens=0;
tokens=OC_token_bucket_consume(bucket, now, TB_CONSUME_NORMAL, 140);
@@ -187,6 +187,32 @@ TEST(OCTokenBucket, Basic)
EXPECT_EQ(tokens, 10);
OC_token_bucket_free(bucket);
}
+TEST(OCTokenBucket, Period)
+{
+ uuid_t uuid;
+ uuid_generate(uuid);
+
+ struct OC_token_bucket *bucket=NULL;
+ long long rate=100;
+ long long capacity=40;
+ long long period=300;
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ bucket=OC_token_bucket_new(uuid, now, rate, period, capacity);
+ long long tokens=0;
+
+ tokens=OC_token_bucket_consume(bucket, now, TB_CONSUME_NORMAL, capacity);
+ EXPECT_EQ(tokens, capacity);
+
+ now.tv_sec+=period/10;
+ tokens=OC_token_bucket_consume(bucket, now, TB_CONSUME_NORMAL, 1+rate/10);
+ EXPECT_EQ(tokens, 0);
+
+ tokens=OC_token_bucket_consume(bucket, now, TB_CONSUME_NORMAL, rate/10);
+ EXPECT_EQ(tokens, rate/10);
+ OC_token_bucket_free(bucket);
+}
TEST(OCTokenBucket, Serialize)
{
uuid_t uuid;
@@ -199,7 +225,7 @@ TEST(OCTokenBucket, Serialize)
for(int i=0; i<n_replica; i++)
{
uuid_generate(uuid);
- b[i]=OC_token_bucket_new(uuid, now, CIR, CBS);
+ b[i]=OC_token_bucket_new(uuid, now, CIR, 1, CBS);
OC_token_bucket_consume(b[i], now, TB_CONSUME_NORMAL, 10);
}
OC_token_bucket_sync(b, n_replica);
@@ -227,10 +253,10 @@ TEST(OCTokenBucket, Boundary)
struct timeval now;
gettimeofday(&now, NULL);
struct OC_token_bucket *bucket=NULL;
- long long tokens=0, consumed=0;
+ long long tokens=0;
//Zero CIR
- bucket=OC_token_bucket_new(uuid, now, 0, 1000);
+ bucket=OC_token_bucket_new(uuid, now, 0, 1, 1000);
tokens=OC_token_bucket_consume(bucket, now, TB_CONSUME_NORMAL, 1000);
EXPECT_EQ(tokens, 1000);
@@ -240,13 +266,15 @@ TEST(OCTokenBucket, Boundary)
OC_token_bucket_free(bucket);
//Zero CBS
- bucket=OC_token_bucket_new(uuid, now, 1000, 0);
+ bucket=OC_token_bucket_new(uuid, now, 1000, 1, 0);
tokens=OC_token_bucket_consume(bucket, now, TB_CONSUME_NORMAL, 1);
EXPECT_EQ(tokens, 0);
OC_token_bucket_free(bucket);
//Infinite Tokens
- bucket=OC_token_bucket_new(uuid, now, 0, 0);
+ long long consumed=0;
+ long long capacity=1;
+ bucket=OC_token_bucket_new(uuid, now, 1, 0, capacity);
tokens=OC_token_bucket_consume(bucket, now, TB_CONSUME_NORMAL, 140);
consumed+=tokens;
EXPECT_EQ(tokens, 140);
@@ -260,10 +288,11 @@ TEST(OCTokenBucket, Boundary)
OC_token_bucket_info(bucket, now, &info);
EXPECT_EQ(info.consumed, consumed);
- EXPECT_EQ(info.refilled, consumed);
+ EXPECT_EQ(info.refilled, consumed+capacity);
+ EXPECT_LE(info.refilled-info.consumed, info.capacity);
now.tv_sec++;
- OC_token_bucket_configure(bucket, now, 100, 500);
+ OC_token_bucket_configure(bucket, now, 100, 1, 500);
now.tv_sec+=500/100;
tokens=OC_token_bucket_consume(bucket, now, TB_CONSUME_NORMAL, 500);
@@ -272,8 +301,7 @@ TEST(OCTokenBucket, Boundary)
OC_token_bucket_info(bucket, now, &info);
EXPECT_EQ(info.consumed, consumed);
- EXPECT_EQ(info.refilled, consumed);
-
+ EXPECT_LE(info.refilled-info.consumed, info.capacity);
OC_token_bucket_free(bucket);
}
TEST(OCTokenBucket, Merge)
@@ -289,7 +317,7 @@ TEST(OCTokenBucket, Merge)
for(i=0; i<2; i++)
{
uuid_generate(uuid);
- buckets[i]=OC_token_bucket_new(uuid, now, CIR, CBS);
+ buckets[i]=OC_token_bucket_new(uuid, now, CIR, 1, CBS);
}
long long tokens=0;
OC_token_bucket_sync(buckets, 2);
@@ -323,7 +351,7 @@ TEST(OCTokenBucket, Replicate)
struct timeval now;
gettimeofday(&now, NULL);
uuid_generate(uuid);
- buckets[0]=OC_token_bucket_new(uuid, now, CIR, CBS);
+ buckets[0]=OC_token_bucket_new(uuid, now, CIR, 1, CBS);
tokens=OC_token_bucket_consume(buckets[0], now, TB_CONSUME_NORMAL, 130);
EXPECT_EQ(tokens, 130);
@@ -348,19 +376,23 @@ TEST(OCTokenBucket, Replicate)
OC_token_bucket_free(buckets[i]);
}
}
-double OC_token_bucket_test(size_t replica_num, long long mimic_duration_s, long long sync_interval_ms, int saturation_percent)
+double test_oc_token_bucket(size_t replica_num, long long mimic_duration_s, long long sync_interval_ms, int saturation_percent)
{
struct OC_token_bucket *buckets[replica_num];
size_t i=0, j=0;
- long long CIR=512*1024;
- long long CBS=2*1024*1024;
+ long long period=8;
+ long long rate=512*1024*period;
+ long long capacity=2*1024*1024;
+
+ long long CIR=rate/period;
+
uuid_t uuid;
struct timeval start;
gettimeofday(&start, NULL);
for(i=0; i<replica_num; i++)
{
uuid_generate(uuid);
- buckets[i]=OC_token_bucket_new(uuid, start, CIR, CBS);
+ buckets[i]=OC_token_bucket_new(uuid, start, rate, period, capacity);
}
srandom(17);
long long tokens=0;
@@ -387,7 +419,7 @@ double OC_token_bucket_test(size_t replica_num, long long mimic_duration_s, long
{
OC_token_bucket_sync(buckets, replica_num);
}
- upper_limit=CBS+CIR*(timeval_to_ms(now)-timeval_to_ms(start))/1000;
+ upper_limit=capacity+CIR*(timeval_to_ms(now)-timeval_to_ms(start))/1000;
}
@@ -395,7 +427,7 @@ double OC_token_bucket_test(size_t replica_num, long long mimic_duration_s, long
OC_token_bucket_info(buckets[0], now, &info);
refilled=info.refilled;
- upper_limit=CBS+CIR*timeval_delta_ms(start, now)/1000;
+ upper_limit=capacity+CIR*timeval_delta_ms(start, now)/1000;
printf("duration_s=%lld, replica_num=%zu, upper_limit=%lld, requested=%lld, allocated=%lld, wasted=%lld\n",
mimic_duration_s, replica_num, upper_limit, requested, allocated, (requested>upper_limit?upper_limit-allocated:0));
@@ -419,7 +451,7 @@ TEST(OCTokenBucket, ShortPeriodHeavyConsumer)
for(int i=0; i<20; i++)
{
test_duration_s=10*(i+1);
- accuracy=OC_token_bucket_test(replica_num, test_duration_s, sync_interval_ms, 200);
+ accuracy=test_oc_token_bucket(replica_num, test_duration_s, sync_interval_ms, 200);
EXPECT_NEAR(accuracy, 1, 0.05);
}
}
@@ -435,7 +467,7 @@ TEST(OCTokenBucket, HeavyConsumer)
for(j=0; j<10; j++)
{
sync_interval_ms=100*(j+1);
- accuracy=OC_token_bucket_test(replica_num, test_duration_s, sync_interval_ms, 200);
+ accuracy=test_oc_token_bucket(replica_num, test_duration_s, sync_interval_ms, 200);
printf("replica_num=%lld, sync_interval_ms=%lld, accuracy=%.4f\n", replica_num, sync_interval_ms, accuracy);
EXPECT_NEAR(accuracy, 1, 0.05);
}
@@ -454,7 +486,7 @@ TEST(OCTokenBucket, LightConsumer)
for(j=0; j<20; j++)
{
sync_interval_ms=10*(j+1);
- accuracy=OC_token_bucket_test(replica_num, test_duration_s, sync_interval_ms, 90);
+ accuracy=test_oc_token_bucket(replica_num, test_duration_s, sync_interval_ms, 90);
EXPECT_NEAR(accuracy, 1, 0.03);
}
}
@@ -472,7 +504,7 @@ TEST(OCTokenBucket, ConcurrentHeavyConsumer)
for(i=0; i<REPLICA_NUMBER; i++)
{
uuid_generate(uuid);
- buckets[i]=OC_token_bucket_new(uuid, start, CIR, CBS);
+ buckets[i]=OC_token_bucket_new(uuid, start, CIR, 1, CBS);
}
srandom(17);
long long tokens=0;
@@ -526,7 +558,7 @@ TEST(OCTokenBucket, MixTypeConsumer)
for(i=0; i<n_replica; i++)
{
uuid_generate(uuid);
- buckets[i]=OC_token_bucket_new(uuid, start, CIR, CBS);
+ buckets[i]=OC_token_bucket_new(uuid, start, CIR, 1, CBS);
}
srandom(17);
long long tokens=0;
@@ -581,7 +613,7 @@ TEST(OCTokenBucket, Reconfigure)
for(i=0; i<REPLICA_NUMBER; i++)
{
uuid_generate(uuid);
- buckets[i]=OC_token_bucket_new(uuid, start, CIR, CBS);
+ buckets[i]=OC_token_bucket_new(uuid, start, CIR, 1, CBS);
}
srandom(17);
long long tokens=0;
@@ -622,7 +654,7 @@ TEST(OCTokenBucket, Reconfigure)
CBS*=5;
requested=0;
consumed=0;
- OC_token_bucket_configure(buckets[0], start, CIR, CBS);
+ OC_token_bucket_configure(buckets[0], start, CIR, 1, CBS);
for(i=0; (long long)i<mimic_duration_us/step_us; i++)
{
timeradd(&now, &step, &now);
@@ -667,7 +699,7 @@ TEST(OCTokenBucket, PartitionTolerance)
for(i=0; i<REPLICA_NUMBER; i++)
{
uuid_generate(uuid);
- buckets[i]=OC_token_bucket_new(uuid, start, CIR, CBS);
+ buckets[i]=OC_token_bucket_new(uuid, start, CIR, 1, CBS);
}
srandom(17);
long long tokens=0;
@@ -713,27 +745,28 @@ TEST(OCTokenBucket, PartitionTolerance)
{
OC_token_bucket_free(buckets[i]);
}
-
}
-struct sftb_class
+
+struct ftb_class
{
long long class_id;
long long weight;
- long long requested_CIR;
- long long demand_tokens;
+ long long rate;
+ double next_demand;
+ long long demanded_tokens;
long long allocated_tokens;
long long ideal_tokens;
};
-int cmp_sftb_class(const void *a, const void *b)
+int cmp_ftb_member(const void *a, const void *b)
{
- struct sftb_class *ra=(struct sftb_class*)a;
- struct sftb_class *rb=(struct sftb_class*)b;
- return (int)(ra->demand_tokens-rb->demand_tokens);
+ struct ftb_class *ra=(struct ftb_class*)a;
+ struct ftb_class *rb=(struct ftb_class*)b;
+ return (int)(ra->demanded_tokens-rb->demanded_tokens);
}
-double max_min_fairness_index(long long available_tokens, struct sftb_class * classes, size_t n_class)
+double max_min_fairness_index(long long available_tokens, struct ftb_class * classes, size_t n_class)
{
- qsort(classes, n_class, sizeof(struct sftb_class), cmp_sftb_class);
+ qsort(classes, n_class, sizeof(struct ftb_class), cmp_ftb_member);
long long total_weight=0;
for(size_t i=0; i<n_class; i++)
{
@@ -748,20 +781,20 @@ double max_min_fairness_index(long long available_tokens, struct sftb_class * cl
long long share=left_tokens/left_weight;
for(size_t i=0; i<n_class; i++)
{
- long long my_share=classes[i].weight*share;
- if(classes[i].demand_tokens == classes[i].ideal_tokens)
+ long long my_share = classes[i].weight*share;
+ if(classes[i].demanded_tokens == classes[i].ideal_tokens)
{
continue;
}
- else if(classes[i].demand_tokens - classes[i].ideal_tokens <= my_share)
+ else if(classes[i].demanded_tokens - classes[i].ideal_tokens <= my_share)
{
- left_tokens -= (classes[i].demand_tokens - classes[i].ideal_tokens);
- classes[i].ideal_tokens=classes[i].demand_tokens;
+ left_tokens -= (classes[i].demanded_tokens - classes[i].ideal_tokens);
+ classes[i].ideal_tokens=classes[i].demanded_tokens;
}
else
{
left_tokens -= my_share;
- classes[i].ideal_tokens+=my_share;
+ classes[i].ideal_tokens += my_share;
}
}
@@ -769,7 +802,7 @@ double max_min_fairness_index(long long available_tokens, struct sftb_class * cl
n_satisfied=0;
for(size_t i=0; i<n_class; i++)
{
- if(classes[i].demand_tokens == classes[i].ideal_tokens)
+ if(classes[i].demanded_tokens == classes[i].ideal_tokens)
{
n_satisfied++;
continue;
@@ -793,64 +826,110 @@ void ftb_sync(struct fair_token_bucket **ftb, size_t n_ftb)
size_t blob_sz=0;
for(size_t i=0; i<n_ftb; i++)
{
+ fair_token_bucket_serialize(ftb[i], &blob, &blob_sz);
for(size_t j=0; j<n_ftb; j++)
{
if(i==j) continue;
- fair_token_bucket_serialize(ftb[j], &blob, &blob_sz);
- fair_token_bucket_merge_blob(ftb[i], blob, blob_sz);
- free(blob);
- blob=NULL;
+ fair_token_bucket_merge_blob(ftb[j], blob, blob_sz);
}
+ free(blob);
+ blob=NULL;
}
return;
}
+struct ftb_case
+{
+ long long rate;
+ long long period;
+ long long capacity;
+ long long perturb_interval_ms;
+ long long divisor;
+ long long sync_interval_ms;
+ int print;
+ int duration_s;
+ int n_replica;
+ int randomization;
+};
+
+void ftb_case_init(struct ftb_case *mycase)
+{
+ memset(mycase, 0, sizeof(struct ftb_case));
+ mycase->n_replica=1;
+ mycase->divisor=8192;
+ mycase->perturb_interval_ms=10;
+ mycase->period=1;
+ mycase->print=1;
+ mycase->sync_interval_ms=100;
+}
double expected_fairness_index=0.04;
-double test_fair_token_bucket(struct sftb_class *classes, size_t n_class, long long CIR, long long CBS, int duration_s, int n_replica)
+double test_fair_token_bucket(struct ftb_class *classes, size_t n_class, struct ftb_case *mycase)
{
uuid_t uuid;
-
+ srand(171);
+ for(size_t i=0; i<n_class; i++)
+ {
+ classes[i].demanded_tokens=0;
+ classes[i].allocated_tokens=0;
+ classes[i].ideal_tokens=0;
+ classes[i].next_demand=0;
+ }
struct timeval start, step, now;
- int step_us=1000;
+ int step_us=100;
gettimeofday(&start, NULL);
memcpy(&now, &start, sizeof(now));
step.tv_sec=0;
step.tv_usec=(suseconds_t)step_us;
+ int n_replica=mycase->n_replica;
struct fair_token_bucket *ftb[n_replica];
for(int i=0; i<n_replica; i++)
{
uuid_generate(uuid);
- ftb[i]=fair_token_bucket_new(uuid, now, CIR, CBS, 8192);
+ ftb[i]=fair_token_bucket_new(uuid, now, mycase->rate, mycase->period, mycase->capacity, mycase->divisor);
+ fair_token_bucket_set_pertub_interval(ftb[i], mycase->perturb_interval_ms);
}
- int sync_interval_ms=50;
+ int sync_interval_ms=mycase->sync_interval_ms;
long long got=0;
- for(int i=0; i<duration_s*(1000*1000/step_us); i++)
+ int r=0;
+ struct timeval last_sync=now;
+ for(int i=0; i<mycase->duration_s*(1000*1000/step_us); i++)
{
timeradd(&now, &step, &now);
int k=random()%n_class;
- int r=k%n_replica;//for class-replica coherence
for(size_t j=0; j<n_class; j++)
{
int idx=(j+k)%n_class;
- long long this_demand=classes[idx].requested_CIR*step_us/(1000*1000);
+ if(mycase->randomization && idx==(int)(now.tv_sec%n_class))
+ {
+ continue;
+ }
+ r=idx%n_replica;// each class sticks to one replica.
+ classes[idx].next_demand += (double)classes[idx].rate*step_us/(1000*1000);
+ if(classes[idx].next_demand < 1)
+ {
+ continue;
+ }
+ long long this_demand = (long long)floor(classes[idx].next_demand);
+ classes[idx].next_demand -= this_demand;
+ assert(classes[idx].next_demand >= 0);
got=fair_token_bucket_consume(ftb[r], now,
(const char*) &(classes[idx].class_id),
sizeof(classes[idx].class_id),
classes[idx].weight,
TB_CONSUME_NORMAL,
this_demand);
- classes[idx].allocated_tokens+=got;
- classes[idx].demand_tokens+=this_demand;
-
+ classes[idx].allocated_tokens += got;
+ classes[idx].demanded_tokens += this_demand;
}
- if(0==i%(sync_interval_ms*1000/step_us))
+ if(timeval_delta_ms(last_sync, now) > mycase->sync_interval_ms)
{
ftb_sync(ftb, n_replica);
+ last_sync=now;
+ //r=i%n_replica;
}
}
- long long available_tokens=CIR*duration_s+CBS;
+ long long available_tokens = mycase->rate*mycase->duration_s/mycase->period + mycase->capacity;
double index=max_min_fairness_index(available_tokens, classes, n_class);
- int print=1;
- if(print)
+ if(mycase->print || index < 0.9)
{
printf("class\tweight\tdemand\tallocated\tideal\r\n");
for(size_t i=0; i<n_class; i++)
@@ -863,11 +942,11 @@ double test_fair_token_bucket(struct sftb_class *classes, size_t n_class, long l
//continue;
printf("%lld\t%lld\t%lld\t%lld\t%lld\r\n", classes[i].class_id,
classes[i].weight,
- classes[i].demand_tokens/duration_s,
- classes[i].allocated_tokens/duration_s,
- classes[i].ideal_tokens/duration_s);
+ classes[i].demanded_tokens/mycase->duration_s,
+ classes[i].allocated_tokens/mycase->duration_s,
+ classes[i].ideal_tokens/mycase->duration_s);
}
- printf("Replica %d, sync interval %d ms, fairness index %f\r\n", n_replica, sync_interval_ms, index);
+ printf("Replica %d, Perturb %lld ms, sync interval %d ms, fairness index %f\r\n", n_replica, mycase->perturb_interval_ms, sync_interval_ms, index);
}
for(int i=0; i<n_replica; i++)
{
@@ -875,47 +954,71 @@ double test_fair_token_bucket(struct sftb_class *classes, size_t n_class, long l
}
return index;
}
-
TEST(FairTokenBucket, Basic)
{
double index=0;
- long long duration_s=20;
- struct sftb_class one_heavy_classes[5]={{1, 1, 20000, 0, 0, 0},
- {2, 1, 20000, 0, 0, 0},
- {3, 1, 20000, 0, 0, 0},
- {4, 1, 20000, 0, 0, 0},
- {5, 1, 50000, 0, 0, 0}};
+ struct ftb_case mycase;
+ ftb_case_init(&mycase);
+ mycase.rate=100000;
+ mycase.capacity=10000;
+ mycase.duration_s=40;
+ mycase.perturb_interval_ms=10;
+
+ struct ftb_class two_classes[2]={
+ {.class_id=1, .weight=1, .rate=110000},
+ {.class_id=2, .weight=4, .rate=100},};
- index=test_fair_token_bucket(one_heavy_classes, 5, 120000, 200000, duration_s, 1);
+ index=test_fair_token_bucket(two_classes, 2, &mycase);
EXPECT_NEAR(index, 1, expected_fairness_index);
- struct sftb_class all_light_classes[5]={{1, 1, 20000, 0, 0, 0},
- {2, 1, 20000, 0, 0, 0},
- {3, 1, 20000, 0, 0, 0},
- {4, 1, 20000, 0, 0, 0},
- {5, 1, 20000, 0, 0, 0}};
+ struct ftb_class heavy_2_of_2_classes[2]={
+ {.class_id=1, .weight=1, .rate=120000},
+ {.class_id=2, .weight=4, .rate=10000},};
- index=test_fair_token_bucket(all_light_classes, 5, 100000, 200000, duration_s, 1);
+ index=test_fair_token_bucket(heavy_2_of_2_classes, 2, &mycase);
EXPECT_NEAR(index, 1, expected_fairness_index);
- struct sftb_class two_heavy_classes[5]={{1, 1, 20000, 0, 0, 0},
- {2, 1, 20000, 0, 0, 0},
- {3, 1, 50000, 0, 0, 0},
- {400, 1, 50000, 0, 0, 0},
- {5, 1, 20003, 0, 0, 0}};
+ struct ftb_class heavy_1_of_5_classes[5]={
+ {.class_id=1, .weight=1, .rate=20000},
+ {.class_id=2, .weight=1, .rate=20000},
+ {.class_id=3, .weight=1, .rate=20000},
+ {.class_id=4, .weight=1, .rate=20000},
+ {.class_id=5, .weight=1, .rate=50000}};
- index=test_fair_token_bucket(two_heavy_classes, 5, 100000, 200000, duration_s, 1);
+ index=test_fair_token_bucket(heavy_1_of_5_classes, 5, &mycase);
EXPECT_NEAR(index, 1, expected_fairness_index);
- struct sftb_class all_heavy_classes[5]={{1, 1, 40000, 0, 0, 0},
- {2, 1, 40000, 0, 0, 0},
- {3, 1, 50000, 0, 0, 0},
- {4, 1, 60000, 0, 0, 0},
- {5, 1, 40000, 0, 0, 0}};
+ struct ftb_class all_light_classes[5]={
+ {.class_id=1, .weight=1, .rate=20000},
+ {.class_id=2, .weight=1, .rate=20000},
+ {.class_id=3, .weight=1, .rate=20000},
+ {.class_id=4, .weight=1, .rate=20000},
+ {.class_id=5, .weight=1, .rate=20000}};
- index=test_fair_token_bucket(all_heavy_classes, 5, 100000, 200000, duration_s, 1);
+ index=test_fair_token_bucket(all_light_classes, 5, &mycase);
EXPECT_NEAR(index, 1, expected_fairness_index);
+
+ struct ftb_class heavy_2_of_5_classes[5]={
+ {.class_id=1, .weight=1, .rate=20000},
+ {.class_id=2, .weight=1, .rate=20000},
+ {.class_id=3, .weight=1, .rate=50000},
+ {.class_id=4, .weight=1, .rate=50000},
+ {.class_id=5, .weight=1, .rate=20003}};
+
+ index=test_fair_token_bucket(heavy_2_of_5_classes, 5, &mycase);
+ EXPECT_NEAR(index, 1, expected_fairness_index);
+
+ struct ftb_class all_heavy_classes[5]={
+ {.class_id=1, .weight=1, .rate=40000},
+ {.class_id=2, .weight=1, .rate=40000},
+ {.class_id=3, .weight=1, .rate=50000},
+ {.class_id=4, .weight=1, .rate=60000},
+ {.class_id=5, .weight=1, .rate=40000}};
+
+ index=test_fair_token_bucket(all_heavy_classes, 5, &mycase);
+ EXPECT_NEAR(index, 1, expected_fairness_index);
+
}
TEST(FairTokenBucket, Merge)
{
@@ -934,11 +1037,11 @@ TEST(FairTokenBucket, Merge)
for(int i=0; i<n_replica; i++)
{
uuid_generate(uuid);
- ftb[i]=fair_token_bucket_new(uuid, now, CIR, CBS, 8192);
+ ftb[i]=fair_token_bucket_new(uuid, now, CIR, CBS, 1, 8192);
}
long long got=0;
- int sync_interval_ms=50, duration_s=20;
+ int duration_s=20;
for(int i=0; i<duration_s*(1000*1000/step_us); i++)
{
timeradd(&now, &step, &now);
@@ -966,224 +1069,306 @@ TEST(FairTokenBucket, Merge)
fair_token_bucket_free(ftb[i]);
}
}
-TEST(FairTokenBucket, Replicate)
-{
- uuid_t uuid;
- int n_replica=2;
- long long CIR=10*1024*1024, CBS=CIR;
- int duration_s=100;
- size_t n_class=2;
- struct sftb_class classes[n_class]={{1, 1, CIR*3/2, 0, 0, 0},
- {2, 2, CIR*3/2, 0, 0, 0}};
-
- struct timeval start, step, now;
- int step_us=1000;
- gettimeofday(&start, NULL);
- memcpy(&now, &start, sizeof(now));
- step.tv_sec=0;
- step.tv_usec=(suseconds_t)step_us;
-
- struct fair_token_bucket *ftb[n_replica];
- uuid_generate(uuid);
- ftb[0]=fair_token_bucket_new(uuid, now, CIR, CBS, 8192);
- long long got=0;
- got=fair_token_bucket_consume(ftb[0], now,
- (const char*) &(classes[0].class_id),
- sizeof(classes[0].class_id),
- classes[0].weight,
- TB_CONSUME_NORMAL,
- 1);
- classes[0].allocated_tokens+=got;
- classes[0].demand_tokens+=1;
-
-
- char *blob=NULL;
- size_t blob_sz=0;
- fair_token_bucket_serialize(ftb[0], &blob, &blob_sz);
-
- uuid_generate(uuid);
- ftb[1]=fair_token_bucket_replicate(uuid, blob, blob_sz);
- free(blob);
- blob=NULL;
-
- int sync_interval_ms=50;
-
-
- for(int i=0; i<duration_s*(1000*1000/step_us); i++)
- {
- timeradd(&now, &step, &now);
- int k=random()%n_class;
- //int r=random()%n_replica;
- for(size_t j=0; j<n_class; j++)
- {
- int idx=(j+k)%n_class;
- long long this_demand=classes[idx].requested_CIR*step_us/(1000*1000);
- got=fair_token_bucket_consume(ftb[1], now,
- (const char*) &(classes[idx].class_id),
- sizeof(classes[idx].class_id),
- classes[idx].weight,
- TB_CONSUME_NORMAL,
- this_demand);
- classes[idx].allocated_tokens+=got;
- classes[idx].demand_tokens+=this_demand;
-
- }
- if(0==i%(sync_interval_ms*1000/step_us))
- {
- //ftb_sync(ftb, n_replica);
- }
- }
- long long available_tokens=CIR*duration_s+CBS;
- double index=max_min_fairness_index(available_tokens, classes, n_class);
- EXPECT_NEAR(index, 1, expected_fairness_index);
-
- for(int i=0; i<n_replica; i++)
- {
- fair_token_bucket_free(ftb[i]);
- }
-}
-
TEST(FairTokenBucket, Debug)
{
double index=0;
- long long duration_s=20;
- struct sftb_class two_heavy_classes[5]={{1, 1, 20000, 0, 0, 0},
- {2, 1, 20000, 0, 0, 0},
- {3, 1, 50000, 0, 0, 0},
- {400, 1, 50000, 0, 0, 0},
- {5, 1, 20003, 0, 0, 0}};
- for(int i=1; i<6; i++)
- {
- index=test_fair_token_bucket(two_heavy_classes, 5, 100000, 200000, duration_s, i);
- }
+ struct ftb_case mycase;
+ ftb_case_init(&mycase);
+ mycase.rate=100000;
+ mycase.capacity=10000;
+ mycase.duration_s=40;
+ mycase.perturb_interval_ms=10;
+
+ struct ftb_class heavy_1_of_5_classes[5]={
+ {.class_id=1, .weight=1, .rate=20000},
+ {.class_id=2, .weight=1, .rate=20000},
+ {.class_id=3, .weight=1, .rate=20000},
+ {.class_id=4, .weight=1, .rate=20000},
+ {.class_id=5, .weight=1, .rate=50000}};
+
+ index=test_fair_token_bucket(heavy_1_of_5_classes, 5, &mycase);
EXPECT_NEAR(index, 1, expected_fairness_index);
}
TEST(FairTokenBucket, Replicas)
{
double index=0;
- long long duration_s=350;
- int n_replica=2;
- struct sftb_class one_heavy_classes[5]={{1, 1, 20000, 0, 0, 0},
- {2, 1, 20000, 0, 0, 0},
- {3, 1, 20000, 0, 0, 0},
- {4, 1, 20000, 0, 0, 0},
- {5, 1, 50000, 0, 0, 0}};
+ struct ftb_case mycase;
+ ftb_case_init(&mycase);
+ mycase.rate=120000;
+ mycase.capacity=200000;
+ mycase.duration_s=350;
+ mycase.n_replica=2;
+ mycase.perturb_interval_ms=10;
+ mycase.sync_interval_ms=10;
+ struct ftb_class one_heavy_classes[5]={
+ {.class_id=1, .weight=1, .rate=20000},
+ {.class_id=2, .weight=1, .rate=20000},
+ {.class_id=3, .weight=1, .rate=20000},
+ {.class_id=4, .weight=1, .rate=20000},
+ {.class_id=5, .weight=1, .rate=50000}};
+
- index=test_fair_token_bucket(one_heavy_classes, 1, 120000, 200000, duration_s, n_replica);
+ index=test_fair_token_bucket(one_heavy_classes, 5, &mycase);
EXPECT_NEAR(index, 1, expected_fairness_index);
- struct sftb_class two_heavy_classes[5]={{1, 1, 20000, 0, 0, 0},
- {2, 1, 20000, 0, 0, 0},
- {3, 1, 50000, 0, 0, 0},
- {400, 1, 50000, 0, 0, 0},
- {5, 1, 20003, 0, 0, 0}};
+ struct ftb_class two_heavy_classes[5]={
+ {.class_id=1, .weight=1, .rate=20000},
+ {.class_id=2, .weight=1, .rate=20000},
+ {.class_id=3, .weight=1, .rate=50000},
+ {.class_id=4, .weight=1, .rate=50000},
+ {.class_id=500, .weight=1, .rate=20003}};
- index=test_fair_token_bucket(two_heavy_classes, 5, 100000, 200000, duration_s, n_replica);
+ index=test_fair_token_bucket(two_heavy_classes, 5, &mycase);
+ EXPECT_NEAR(index, 1, expected_fairness_index);
+
+ long long CIR=10*1024*1024;
+ mycase.rate=CIR;
+ mycase.capacity=CIR;
+ mycase.duration_s=100;
+ mycase.n_replica=1;
+
+ struct ftb_class class2[2]={
+ {.class_id=1, .weight=1, .rate=CIR*3/2,},
+ {.class_id=2, .weight=2, .rate=CIR*3/2},};
+ index=test_fair_token_bucket(class2, 2, &mycase);
EXPECT_NEAR(index, 1, expected_fairness_index);
}
TEST(FairTokenBucket, Weight)
{
double index=0;
- long long duration_s=500;
- int n_replica=1;
- struct sftb_class one_heavy_classes[5]={{1, 1, 20000, 0, 0, 0},
- {2, 2, 20000, 0, 0, 0},
- {3, 3, 20000, 0, 0, 0},
- {4, 4, 30000, 0, 0, 0},
- {5, 5, 50000, 0, 0, 0}};
+ struct ftb_case mycase;
+ ftb_case_init(&mycase);
+ mycase.rate=120000;
+ mycase.capacity=200000;
+ mycase.duration_s=500;
+ mycase.n_replica=1;
+ struct ftb_class one_heavy_classes[5]={
+ {.class_id=1, .weight=1, .rate=20000},
+ {.class_id=2, .weight=1, .rate=20000},
+ {.class_id=3, .weight=1, .rate=20000},
+ {.class_id=4, .weight=1, .rate=30000},
+ {.class_id=5, .weight=1, .rate=50000}};
+
- double one_heavy_index=test_fair_token_bucket(one_heavy_classes, 5, 120000, 200000, duration_s, n_replica);
+ double one_heavy_index=test_fair_token_bucket(one_heavy_classes, 5, &mycase);
EXPECT_NEAR(one_heavy_index, 1, expected_fairness_index);
- struct sftb_class t1_heavy_classes[5]={{1, 1, 40000, 0, 0, 0},
- {2, 2, 20000, 0, 0, 0},
- {3, 3, 20000, 0, 0, 0},
- {4, 4, 30000, 0, 0, 0},
- {5, 5, 50000, 0, 0, 0}};
+ struct ftb_class t1_heavy_classes[5]={
+ {.class_id=1, .weight=1, .rate=40000},
+ {.class_id=2, .weight=2, .rate=20000},
+ {.class_id=3, .weight=3, .rate=20000},
+ {.class_id=4, .weight=4, .rate=30000},
+ {.class_id=5, .weight=5, .rate=50000}};
+
- double t1_heavy_index=test_fair_token_bucket(t1_heavy_classes, 5, 120000, 200000, duration_s, n_replica);
+ double t1_heavy_index=test_fair_token_bucket(t1_heavy_classes, 5, &mycase);
EXPECT_NEAR(t1_heavy_index, 1, expected_fairness_index);
- struct sftb_class t2_heavy_classes[5]={{1, 1, 40000, 0, 0, 0},
- {2, 2, 20000, 0, 0, 0},
- {3, 3, 20000, 0, 0, 0},
- {4, 4, 30000, 0, 0, 0},
- {5, 10, 50000, 0, 0, 0}};
+ struct ftb_class t2_heavy_classes[5]={
+ {.class_id=1, .weight=1, .rate=40000},
+ {.class_id=2, .weight=2, .rate=20000},
+ {.class_id=3, .weight=3, .rate=20000},
+ {.class_id=4, .weight=4, .rate=30000},
+ {.class_id=5, .weight=10, .rate=50000}};
- double t2_heavy_index=test_fair_token_bucket(t2_heavy_classes, 5, 120000, 200000, duration_s, n_replica);
+ double t2_heavy_index=test_fair_token_bucket(t2_heavy_classes, 5, &mycase);
EXPECT_NEAR(t2_heavy_index, 1, expected_fairness_index);
- struct sftb_class t3_heavy_classes[5]={{1, 1, 40000, 0, 0, 0},
- {2, 2, 20000, 0, 0, 0},
- {3, 3, 22000, 0, 0, 0},
- {4, 4, 20000, 0, 0, 0},
- {5, 10, 10000, 0, 0, 0}};
+ struct ftb_class t3_heavy_classes[5]={
+ {.class_id=1, .weight=1, .rate=40000},
+ {.class_id=2, .weight=2, .rate=20000},
+ {.class_id=3, .weight=3, .rate=22000},
+ {.class_id=4, .weight=4, .rate=20000},
+ {.class_id=5, .weight=10, .rate=10000}};
- double t3_heavy_index=test_fair_token_bucket(t3_heavy_classes, 5, 100000, 200000, duration_s, n_replica);
+ double t3_heavy_index=test_fair_token_bucket(t3_heavy_classes, 5, &mycase);
EXPECT_NEAR(t3_heavy_index, 1, expected_fairness_index);
- struct sftb_class all_light_classes[5]={{1, 1, 20000, 0, 0, 0},
- {2, 2, 20000, 0, 0, 0},
- {3, 3, 22000, 0, 0, 0},
- {4, 4, 20000, 0, 0, 0},
- {5, 10, 23000, 0, 0, 0}};
+ struct ftb_class all_light_classes[5]={
+ {.class_id=1, .weight=1, .rate=20000},
+ {.class_id=2, .weight=2, .rate=20000},
+ {.class_id=3, .weight=3, .rate=22000},
+ {.class_id=4, .weight=4, .rate=20000},
+ {.class_id=5, .weight=10, .rate=23000}};
- index=test_fair_token_bucket(all_light_classes, 5, 100000, 200000, duration_s, n_replica);
+
+ index=test_fair_token_bucket(all_light_classes, 5, &mycase);
EXPECT_NEAR(index, 1, expected_fairness_index);
- struct sftb_class light_with_big_player[5]={{1, 1, 10000, 0, 0, 0},
- {2, 2, 10000, 0, 0, 0},
- {3, 3, 10000, 0, 0, 0},
- {4, 4, 10000, 0, 0, 0},
- {5, 10, 60000, 0, 0, 0}};
+ struct ftb_class light_with_big_player[5]={
+ {.class_id=1, .weight=1, .rate=10000},
+ {.class_id=2, .weight=2, .rate=10000},
+ {.class_id=3, .weight=3, .rate=10000},
+ {.class_id=4, .weight=4, .rate=10000},
+ {.class_id=5, .weight=10, .rate=60000}};
- double all_light_index=test_fair_token_bucket(light_with_big_player, 5, 100000, 200000, duration_s, n_replica);
+ double all_light_index=test_fair_token_bucket(light_with_big_player, 5, &mycase);
EXPECT_NEAR(all_light_index, 1, expected_fairness_index);
- struct sftb_class sd_al[10]={{1, 5, 30000, 0, 0, 0},
- {2, 5, 30000, 0, 0, 0},
- {3, 3, 12000, 0, 0, 0},
- {4, 3, 12000, 0, 0, 0},
- {5, 3, 11000, 0, 0, 0},
- {6, 2, 14000, 0, 0, 0},
- {7, 2, 5000, 0, 0, 0},
- {8, 2, 8000, 0, 0, 0},
- {9, 2, 9000, 0, 0, 0},
- {10, 2, 20000, 0, 0, 0}};
+ struct ftb_class sd_al[10]={
+ {.class_id=1, .weight=5, .rate=30000},
+ {.class_id=2, .weight=5, .rate=30000},
+ {.class_id=3, .weight=3, .rate=12000},
+ {.class_id=4, .weight=3, .rate=12000},
+ {.class_id=5, .weight=3, .rate=11000},
+ {.class_id=6, .weight=2, .rate=14000},
+ {.class_id=7, .weight=2, .rate=5000},
+ {.class_id=8, .weight=2, .rate=8000},
+ {.class_id=9, .weight=2, .rate=9000},
+ {.class_id=10, .weight=2, .rate=20000}};
- double sd_al_index=test_fair_token_bucket(sd_al, 10, 100000, 200000, duration_s, n_replica);
+ double sd_al_index=test_fair_token_bucket(sd_al, 10, &mycase);
EXPECT_NEAR(sd_al_index, 1, expected_fairness_index);
}
TEST(FairTokenBucket, Weight5000)
{
size_t n_class=5000;
long long per_class_CIR=1000000;
- long long CIR=n_class*per_class_CIR;
- long long CBS=CIR;
- struct sftb_class very_heavy_classes[n_class];
+
+ struct ftb_case mycase;
+ ftb_case_init(&mycase);
+ mycase.rate=n_class*per_class_CIR;
+ mycase.capacity=mycase.rate;
+ mycase.duration_s=40;
+ mycase.n_replica=1;
+ mycase.perturb_interval_ms=10;
+
+ struct ftb_class very_heavy_classes[n_class];
memset(very_heavy_classes, 0, sizeof(very_heavy_classes));
for(size_t i=0; i<n_class; i++)
{
very_heavy_classes[i].class_id=i;
- very_heavy_classes[i].requested_CIR=per_class_CIR+(random()%20)*per_class_CIR;
+ very_heavy_classes[i].rate=per_class_CIR+(random()%20)*per_class_CIR;
very_heavy_classes[i].weight=i%20+1;
}
- double index=test_fair_token_bucket(very_heavy_classes, n_class, CIR, CBS, 40, 1);
+ double index=test_fair_token_bucket(very_heavy_classes, n_class, &mycase);
EXPECT_NEAR(index, 1, expected_fairness_index);
- struct sftb_class slight_heavy_classes[n_class];
+ struct ftb_class slight_heavy_classes[n_class];
memset(slight_heavy_classes, 0, sizeof(slight_heavy_classes));
for(size_t i=0; i<n_class; i++)
{
slight_heavy_classes[i].class_id=i;
- slight_heavy_classes[i].requested_CIR=per_class_CIR+i*per_class_CIR/n_class;
+ slight_heavy_classes[i].rate=per_class_CIR+i*per_class_CIR/n_class;
slight_heavy_classes[i].weight=i%20+1;
}
- index=test_fair_token_bucket(slight_heavy_classes, n_class, CIR, CBS, 40, 1);
+ index=test_fair_token_bucket(slight_heavy_classes, n_class, &mycase);
+ EXPECT_NEAR(index, 1, expected_fairness_index);
+}
+TEST(FairTokenBucket, VariousPerturb1)
+{
+ double index=0;
+ struct ftb_case mycase;
+ ftb_case_init(&mycase);
+ mycase.rate=50000;
+ mycase.capacity=50000;
+ mycase.duration_s=20;
+ mycase.print=0;
+ struct ftb_class one_heavy_classes[2]={
+ {.class_id=1, .weight=1, .rate=50000},
+ {.class_id=2, .weight=4, .rate=100},};
+
+ int max_replicas=8;
+ long long perturb_array[6]={200, 150, 100, 50, 20, 10};
+ printf("replica\tperturb\tindex\r\n");
+ for(int i=0; i<max_replicas; i++)
+ {
+ for(size_t j=0; j<sizeof(perturb_array)/sizeof(long long); j++)
+ {
+ mycase.n_replica=i+1;
+ mycase.perturb_interval_ms=perturb_array[j];
+ index=test_fair_token_bucket(one_heavy_classes, 2, &mycase);
+ //EXPECT_NEAR(index, 1, expected_fairness_index);
+ printf("%d\t%lld\t%.2f\r\n", i+1, perturb_array[j], index);
+ }
+ }
+}
+TEST(FairTokenBucket, VariousPerturb2)
+{
+ double index=0;
+ struct ftb_case mycase;
+ ftb_case_init(&mycase);
+ mycase.rate=50000;
+ mycase.capacity=50000;
+ mycase.duration_s=20;
+ mycase.print=0;
+ int max_replicas=3;
+ long long perturb_array[7]={250, 200, 100, 50, 20, 10, 5};
+
+ struct ftb_class two_heavy_classes[6]={
+ {.class_id=1, .weight=1, .rate=20000},
+ {.class_id=2, .weight=1, .rate=20000},
+ {.class_id=3, .weight=1, .rate=50000},
+ {.class_id=400, .weight=1, .rate=50000},
+ {.class_id=5, .weight=1, .rate=20003},
+ {.class_id=6, .weight=1, .rate=100}};
+ printf("replica\tperturb\tindex\r\n");
+ for(int i=0; i<max_replicas; i++)
+ {
+ for(size_t j=0; j<sizeof(perturb_array)/sizeof(long long); j++)
+ {
+ mycase.n_replica=i+1;
+ mycase.perturb_interval_ms=perturb_array[j];
+ index=test_fair_token_bucket(two_heavy_classes, sizeof(two_heavy_classes)/sizeof(struct ftb_class), &mycase);
+ //EXPECT_NEAR(index, 1, expected_fairness_index);
+ printf("%d\t%lld\t%.2f\r\n", i+1, perturb_array[j], index);
+ }
+ }
+}
+TEST(FairTokenBucket, Key100)
+{
+ double index=0;
+ struct ftb_case mycase;
+ ftb_case_init(&mycase);
+ mycase.rate=50000;
+ mycase.capacity=50000;
+ mycase.duration_s=40;
+ mycase.perturb_interval_ms=100;
+ mycase.print=1;
+ int n_key=100;
+ struct ftb_class myclasses[n_key];
+ for(int i=0; i<n_key; i++)
+ {
+ myclasses[i].class_id=i;
+ myclasses[i].weight=1;
+ myclasses[i].rate=100*(i+1);
+ }
+ index=test_fair_token_bucket(myclasses, n_key, &mycase);
EXPECT_NEAR(index, 1, expected_fairness_index);
}
+TEST(FairTokenBucket, DynamicMembers)
+{
+ double index=0;
+ struct ftb_case mycase;
+ ftb_case_init(&mycase);
+ mycase.rate=100000;
+ mycase.capacity=10000;
+ mycase.duration_s=40;
+ mycase.perturb_interval_ms=10;
+ mycase.randomization=1;
+ struct ftb_class all_heavy_class[5]={
+ {.class_id=1, .weight=1, .rate=40000},
+ {.class_id=2, .weight=1, .rate=40000},
+ {.class_id=3, .weight=1, .rate=50000},
+ {.class_id=4, .weight=1, .rate=50000},
+ {.class_id=5, .weight=1, .rate=40003}};
+
+
+ index=test_fair_token_bucket(all_heavy_class, 5, &mycase);
+ EXPECT_NEAR(index, 1, expected_fairness_index);
+
+ struct ftb_class heavy_1_of_5_class[5]={
+ {.class_id=1, .weight=1, .rate=20000},
+ {.class_id=2, .weight=1, .rate=20000},
+ {.class_id=3, .weight=1, .rate=20000},
+ {.class_id=4, .weight=1, .rate=30000},
+ {.class_id=5, .weight=1, .rate=90003}};
+
+ index=test_fair_token_bucket(heavy_1_of_5_class, 5, &mycase);
+ EXPECT_NEAR(index, 1, expected_fairness_index);
+}
struct btb_key
{
long long key;
@@ -1195,29 +1380,32 @@ void btb_sync(struct bulk_token_bucket **btb, size_t n_btb)
{
char *blob=NULL;
size_t blob_sz=0;
+ if(n_btb==1) return;
for(size_t i=0; i<n_btb; i++)
{
+ bulk_token_bucket_serialize(btb[i], &blob, &blob_sz);
for(size_t j=0; j<n_btb; j++)
{
if(i==j) continue;
- bulk_token_bucket_serialize(btb[j], &blob, &blob_sz);
- bulk_token_bucket_merge_blob(btb[i], blob, blob_sz);
- free(blob);
- blob=NULL;
+
+ bulk_token_bucket_merge_blob(btb[j], blob, blob_sz);
}
+ free(blob);
+ blob=NULL;
}
return;
}
struct btb_case
{
- long long CIR;
- long long CBS;
+ long long refill;
+ long long capacity;
+ long long period;
int n_replica;
int bucket_num;
int key_num;
int duration_s;
- long long estimate_keys;
+ long long approximate_keys;
long long more;
long long less;
double collision_rate;
@@ -1229,7 +1417,7 @@ void bulk_token_bucket_test_print_result(const struct btb_case *results, size_t
for(size_t i=0; i<n_result; i++)
{
printf("%d\t%d\t%d\t%lld\t%lld\t%lld\t%.4f\t%.4f\n", results[i].bucket_num, results[i].n_replica,
- results[i].key_num, results[i].estimate_keys, results[i].more, results[i].less,
+ results[i].key_num, results[i].approximate_keys, results[i].more, results[i].less,
results[i].collision_rate, results[i].index);
}
}
@@ -1242,23 +1430,23 @@ void bulk_token_bucket_test(struct btb_case *mycase)
memcpy(&now, &start, sizeof(now));
step.tv_sec=0;
step.tv_usec=(suseconds_t)step_us;
- long long max_tokens=mycase->duration_s*mycase->CIR+mycase->CBS;
+ long long max_tokens=mycase->duration_s*mycase->refill+mycase->capacity;
struct bulk_token_bucket *btb[mycase->n_replica];
int n_replica=mycase->n_replica;
int key_num=mycase->key_num;
for(int i=0; i<n_replica; i++)
{
uuid_generate(uuid);
- btb[i]=bulk_token_bucket_new(uuid, now, mycase->CIR, mycase->CBS, mycase->bucket_num);
+ btb[i]=bulk_token_bucket_new(uuid, now, mycase->refill, mycase->period, mycase->capacity, mycase->bucket_num);
}
struct btb_key bk[key_num];
for(int i=0; i<key_num; i++)
{
bk[i].key=random();
- bk[i].request_CIR=mycase->CIR/2+(i*mycase->CIR)/key_num;
+ bk[i].request_CIR=mycase->refill/mycase->period/2+(i*mycase->refill)/key_num;
bk[i].allocated_tokens=0;
- if(bk[i].request_CIR<mycase->CIR)
+ if(bk[i].request_CIR<mycase->refill)
{
bk[i].ideal_tokens=bk[i].request_CIR*mycase->duration_s;
}
@@ -1268,9 +1456,10 @@ void bulk_token_bucket_test(struct btb_case *mycase)
}
}
int sync_interval_ms=200;
+ struct timeval last_sync=now;
for(int i=0; i<mycase->duration_s*(1000*1000/step_us); i++)
{
- timeradd(&now, &step, &now);
+
for(int j=0; j<key_num; j++)
{
int r=random()%mycase->n_replica;
@@ -1278,10 +1467,12 @@ void bulk_token_bucket_test(struct btb_case *mycase)
TB_CONSUME_FLEXIBLE,
bk[j].request_CIR*step_us/(1000*1000));
}
- if(0==i%(sync_interval_ms*1000/step_us))
+ if(timeval_delta_ms(last_sync, now) > sync_interval_ms)
{
btb_sync(btb, mycase->n_replica);
+ last_sync=now;
}
+ timeradd(&now, &step, &now);
}
double index=0, ratio=0;
long long more=0, less=0;
@@ -1298,13 +1489,14 @@ void bulk_token_bucket_test(struct btb_case *mycase)
}
index=1-sqrt(index/key_num);
struct bulk_token_bucket_info info;
+ EXPECT_NEAR(info.approximate_keys, key_num, key_num/5);
bulk_token_bucket_info(btb[0], now, &info);
mycase->index=index;
- mycase->estimate_keys=info.estimate_keys;
+ mycase->approximate_keys=info.approximate_keys;
mycase->more=more;
mycase->less=less;
mycase->collision_rate=info.collision_rate;
-
+
for(int i=0; i<n_replica; i++)
{
bulk_token_bucket_free(btb[i]);
@@ -1323,7 +1515,7 @@ TEST(BulkTokenBucket, Basic)
step.tv_usec=(suseconds_t)step_us;
long long CIR=10000, CBS=10000;
long long request_CIR=10000;
- struct bulk_token_bucket *btb=bulk_token_bucket_new(uuid, now, CIR, CBS, 4);
+ struct bulk_token_bucket *btb=bulk_token_bucket_new(uuid, now, CIR, 1, CBS, 4);
long long allocated=0, max_tokens=duration_s*CIR+CBS, available=0;
@@ -1344,6 +1536,44 @@ TEST(BulkTokenBucket, Basic)
bulk_token_bucket_free(btb);
EXPECT_LE(allocated, max_tokens);
}
+TEST(BulkTokenBucket, Period)
+{
+ uuid_t uuid;
+ uuid_generate(uuid);
+
+ long long refill=10, capacity=10, period=30;
+ long long step_us=1000*1000, duration_s=period*100;
+ long long request_CIR=1;
+
+ struct timeval start, step, now;
+ gettimeofday(&start, NULL);
+ memcpy(&now, &start, sizeof(now));
+ step.tv_sec=0;
+ step.tv_usec=(suseconds_t)step_us;
+
+ struct bulk_token_bucket *btb=bulk_token_bucket_new(uuid, now, refill, period, capacity, 4);
+
+ long long allocated=0, max_tokens=duration_s/period*refill+capacity, available=0;
+ const char *key="192.168.0.1";
+ available=bulk_token_bucket_read_available(btb, now, key, strlen(key));
+ EXPECT_EQ(available, capacity);
+ allocated+=bulk_token_bucket_consume(btb, now, key, strlen(key), TB_CONSUME_FLEXIBLE, capacity);
+ EXPECT_EQ(allocated, capacity);
+ available=bulk_token_bucket_read_available(btb, now, key, strlen(key));
+ EXPECT_EQ(available, 0);
+
+ int i=0;
+ for(i=0; i<duration_s*(1000*1000/step_us); i++)
+ {
+ timeradd(&now, &step, &now);
+ allocated+=bulk_token_bucket_consume(btb, now, key, strlen(key), TB_CONSUME_FLEXIBLE, request_CIR*step_us/(1000*1000));
+ }
+ EXPECT_EQ(allocated, max_tokens);
+ now.tv_sec+=period/2;
+ available=bulk_token_bucket_read_available(btb, now, key, strlen(key));
+ EXPECT_EQ(available, capacity/2);
+ bulk_token_bucket_free(btb);
+}
TEST(BulkTokenBucket, Merge)
{
int n_replica=5;
@@ -1362,7 +1592,7 @@ TEST(BulkTokenBucket, Merge)
{
uuid_generate(uuid);
timeradd(&now, &step, &now);
- btb[i]=bulk_token_bucket_new(uuid, now, CIR, CBS, 128*i);
+ btb[i]=bulk_token_bucket_new(uuid, now, CIR, 1, CBS, 128*i);
allocated[i]=0;
}
int r=0;
@@ -1380,7 +1610,7 @@ TEST(BulkTokenBucket, Merge)
}
struct bulk_token_bucket_info info;
bulk_token_bucket_info(btb[0], now, &info);
- EXPECT_NEAR(info.estimate_keys, n_replica, n_replica/5);
+ EXPECT_NEAR(info.approximate_keys, n_replica, n_replica/5);
long long upper_limit=CIR*duration_s+CBS;
int success=0;
for(int i=0; i<n_replica; i++)
@@ -1396,8 +1626,9 @@ TEST(BulkTokenBucket, RareCollision)
struct btb_case test[n_case];
for(int i=0; i<n_case; i++)
{
- test[i].CIR=1000*1000;
- test[i].CBS=1000*1000;
+ test[i].refill=1000*1000;
+ test[i].capacity=1000*1000;
+ test[i].period=1;
test[i].bucket_num=512;
test[i].n_replica=2;
test[i].key_num=(1<<i);
@@ -1413,8 +1644,9 @@ TEST(BulkTokenBucket, HighCollision)
struct btb_case test[n_case];
for(int i=0; i<n_case; i++)
{
- test[i].CIR=1000*1000;
- test[i].CBS=1000*1000;
+ test[i].refill=1000*1000;
+ test[i].capacity=1000*1000;
+ test[i].period=1;
test[i].bucket_num=512;
test[i].n_replica=1;
test[i].key_num=128*(i+1);
@@ -1429,8 +1661,9 @@ TEST(BulkTokenBucket, VariousCIR)
struct btb_case test[n_case];
for(int i=0; i<n_case; i++)
{
- test[i].CIR=400*1000*(i+1);
- test[i].CBS=1000*1000;
+ test[i].refill=400*1000*(i+1);
+ test[i].capacity=1000*1000;
+ test[i].period=1;
test[i].bucket_num=512;
test[i].n_replica=1;
test[i].key_num=32;
@@ -1445,8 +1678,9 @@ TEST(BulkTokenBucket, Bucket1M)
struct btb_case test[n_case];
for(int i=0; i<n_case; i++)
{
- test[i].CIR=1000*1000;
- test[i].CBS=1000*1000;
+ test[i].refill=1000*1000;
+ test[i].capacity=1000*1000;
+ test[i].period=1;
test[i].bucket_num=1024*1024;
test[i].n_replica=1;
test[i].key_num=1024*(i+1);
@@ -1461,8 +1695,9 @@ TEST(BulkTokenBucket, Replicas)
struct btb_case test[n_case];
for(int i=0; i<n_case; i++)
{
- test[i].CIR=1000*1000;
- test[i].CBS=2*1000*1000;
+ test[i].refill=1000*1000;
+ test[i].capacity=2*1000*1000;
+ test[i].period=1;
test[i].bucket_num=512;
test[i].n_replica=2;
test[i].key_num=1<<i;
@@ -1478,8 +1713,27 @@ TEST(BulkTokenBucket, Replica4)
struct btb_case test[n_case];
for(int i=0; i<n_case; i++)
{
- test[i].CIR=1000*1000;
- test[i].CBS=2*1000*1000;
+ test[i].refill=1000*1000;
+ test[i].capacity=2*1000*1000;
+ test[i].period=1;
+ test[i].bucket_num=512;
+ test[i].n_replica=4;
+ test[i].key_num=1<<i;
+ //test[i].key_num=16*(i+1);
+ test[i].duration_s=100;
+ bulk_token_bucket_test(test+i);
+ }
+ bulk_token_bucket_test_print_result(test, n_case);
+}
+TEST(BulkTokenBucket, VariousPeriod)
+{
+ int n_case=8;
+ struct btb_case test[n_case];
+ for(int i=0; i<n_case; i++)
+ {
+ test[i].refill=256;
+ test[i].capacity=256;
+ test[i].period=1<<i;
test[i].bucket_num=512;
test[i].n_replica=4;
test[i].key_num=1<<i;
diff --git a/CRDT/token_bucket_common.c b/CRDT/token_bucket_common.c
index 870432a..a93c1a9 100644
--- a/CRDT/token_bucket_common.c
+++ b/CRDT/token_bucket_common.c
@@ -3,14 +3,14 @@
#include <assert.h>
-long long tb_available(long long CIR, long long CBS, long long consumed, long long refilled, long long delta_ms, long long refill_interval_ms, long long *new_refilled)
+long long tb_available(long long rate, long long period, long long capacity, long long consumed, long long refilled, long long delta_ms, long long refill_interval_ms, long long *new_refilled)
{
- long long to_add = CIR*delta_ms/1000;
+ long long to_add = period?(rate*delta_ms/period/1000):0;
*new_refilled = refilled;
if(refilled==0 && consumed==0)
{
- *new_refilled=CBS;
+ *new_refilled=capacity;
return *new_refilled;
}
if(delta_ms > refill_interval_ms && to_add>0)
@@ -19,26 +19,27 @@ long long tb_available(long long CIR, long long CBS, long long consumed, long lo
{
*new_refilled = consumed;
}
- if(to_add + refilled - consumed < CBS)
+ if(to_add + refilled - consumed < capacity)
{
*new_refilled += to_add;
}
else
{
- *new_refilled += CBS - (refilled - consumed);
+ *new_refilled += capacity - (refilled - consumed);
}
}
return MAX(*new_refilled-consumed, 0);
}
-long long tb_local_available(long long CIR, long long available, size_t n_replica)
+long long tb_local_available(long long rate, long long period, long long available, long long capacity, size_t n_replica)
{
- long long reserved=CIR*(n_replica-1)/n_replica;
+ long long reserved=period?(rate*(n_replica-1)/n_replica/period):0;
+ reserved=MIN(reserved, capacity*(n_replica-1)/n_replica);
long long local_available=MAX(available-reserved, 0);
return local_available;
}
-long long tb_consume_reserve_based(long long CIR, long long available, size_t n_replica, enum tb_consume_type cmd, long long tokens)
+long long tb_consume_reserve_based(long long rate, long long period, long long available, long long capacity, size_t n_replica, enum tb_consume_type cmd, long long tokens)
{
- long long local_available=tb_local_available(CIR, available, n_replica);
+ long long local_available=tb_local_available(rate, period, available, capacity, n_replica);
long long allocated=0;
switch(cmd)
{
diff --git a/CRDT/token_bucket_common.h b/CRDT/token_bucket_common.h
index 5b5fe5a..1cd7d0f 100644
--- a/CRDT/token_bucket_common.h
+++ b/CRDT/token_bucket_common.h
@@ -14,10 +14,11 @@ enum tb_consume_type
TB_CONSUME_FLEXIBLE,
TB_CONSUME_AS_MUCH_AS_POSSIBLE
};
-long long tb_available(long long CIR, long long CBS, long long consumed, long long refilled, long long delta_ms, long long refill_interval_ms, long long *new_refilled);
-long long tb_local_available(long long CIR, long long available, size_t n_replica);
+long long tb_available(long long rate, long long period, long long capacity, long long consumed, long long refilled, long long delta_ms, long long refill_interval_ms, long long *new_refilled);
+long long tb_local_available(long long rate, long long period, long long available, long long capacity, size_t n_replica);
+
+long long tb_consume_reserve_based(long long rate, long long period, long long available, long long capacity, size_t n_replica, enum tb_consume_type cmd, long long tokens);
long long tb_consume_quantum_based(long long available, long long deficit, long long quantum, enum tb_consume_type cmd, long long tokens);
-long long tb_consume_reserve_based(long long CIR, long long available, size_t n_replica, enum tb_consume_type cmd, long long tokens);
#ifdef __cplusplus
}
#endif \ No newline at end of file
diff --git a/docs/command_toc.md b/docs/command_toc.md
new file mode 100644
index 0000000..fd558e2
--- /dev/null
+++ b/docs/command_toc.md
@@ -0,0 +1,81 @@
+# Commands
+
+## Command Categories
+
+The supported command are category as follows:
+* [Generic Command](./commands/generic.md)
+* [String and Integer Types](./commands/string_and_integer.md)
+* [Set Type](./commands/set.md)
+* [Hash Type](./commands/hash.md)
+* [Token Bucket Types](./commands/token_bucket.md)
+* [Bloom Filter Type](./commands/bloom_filter.md)
+
+## COMMAND LIST
+
+Syntax
+
+```
+COMMAND LIST
+```
+
+The `COMMAND LIST` command returns an array of the server's command names.
+
+Return
+
+- Array reply: a list of command names.
+
+
+
+## Quick List
+
+| Command | Arguments | Response |
+| ------------------------ | ------------------------------------ | ------------------------------------------------------------ |
+| GET | key | Bulk string reply: the value of key, or nil when key does not exist. |
+| SET | key value | Simple string reply: OK if SET was executed correctly. |
+| DEL | key | Integer reply: The number of keys that were removed. |
+| INCRYBY | key increment | Integer reply: the value of key after the increment |
+| EXPIRE | key seconds | Integer reply, specifically: 1 if the timeout was set. 0 if the timeout was not set. e.g. key doesn't exist |
+| TTL | key | Integer reply: TTL in seconds, in case of error: -2 if the key does not exist; -1 if the key exists but has no associated expire. |
+| PERSIST | key | Integer reply, specifically:<br/><br/>1 if the timeout was removed.<br/>0 if key does not exist or does not have an associated timeout. |
+| KEYSLOT | key | Integer reply: The hash slot the specified key hashes to. |
+| SADD | key member [member ...] | Integer reply: the number of elements that were added to the set, not including all the elements already present in the set. |
+| SREM | key member [member ...] | Integer reply: the number of members that were removed from the set, not including non existing members. The key will NOT be deleted if all members are removed. If key does not exist, it is treated as an empty set and this command returns 0. |
+| SCARD | key | Integer reply: the cardinality (number of elements) of the set, or 0 if key does not exist. |
+| SISMEMBER | key member | Integer reply, specifically:<br/><br/>1 if the element is a member of the set.<br/>0 if the element is not a member of the set, or if key does not exist. |
+| SMEMBERS | key | Array reply: all elements of the set. Empty array if the key does not exist. |
+| HSET | HSET key field value [field value ...] | |
+| HGET |
+| TCFG | key capacity rate | Simple String Reply: OK if the token bucket was configured. Create the token bucket if key does not exist. |
+| TCONSUME | key tokens [NORMAL\|FORCE\|FLEXIBLE] | Integer reply: the number of tokens that were allow to consume, or -1 if key does not exist. |
+| TINFO | key | Array Reply |
+| FTCFG | key capacity rate divisor | Simple String Reply: OK if the token bucket was configured. Create the token bucket if key does not exist. |
+| FTCONSUME | key member weight tokens | Integer reply: the number of tokens that were allow to consume, or -1 if key does not exist. |
+| FTINFO | key | Array Reply|
+| BTCFG | key capacity rate buckets | Simple String Reply: OK if the token bucket was configured. Create the token bucket if key does not exist. |
+| BTCONSUME | key member tokens [NORMAL\|FORCE\|FLEXIBLE] | Integer reply: the number of tokens that were allow to consume, or -1 if key does not exist. |
+| BTINFO | key | Array Reply| | |
+| KEYSPACE RADD | key IP port | Add replica to the key, create the key if key does not exist. |
+| KEYSPACE XRADD | key IP Port | Add replica to the key, return NULL if the key does not exist. |
+| KEYSPACE RLIST | key | Node Array reply:<br/>1) "192.168.1.200:5211"<br/>2) "192.168.1.201:5211"<br/>3) "[2001:db8::1]:5211"<br/> |
+| KEYSPACE DEL | key | |
+| KEYSPACE SETSLOT | see source code | |
+| KEYSPACE GETKEYSINSLOT | IP port slot | |
+| KEYSPACE ADDKEYSTOSLOT | IP port slot blob | |
+| KEYSPACE DELSLOTKEYS | IP port slot | |
+| KEYSPACE KEYS | IP port pattern | |
+| KEYSPACE COUNTKEYSINSLOT | slot | |
+| CRDT DEL | key | Integer reply: The number of cached keys that were removed. |
+| CRDT MERGE | key blob | Simple string reply: OK if MERGE was executed correctly. |
+| CRDT GET | key | Blog reply: a blob of state-based CRDT |
+| CRDT INFO | key | Array reply: |
+| CLUSTER KEYS | pattern | Array reply: list of keys matching pattern. |
+| CLUSTER NODES | | |
+| CLUSTER SLOTS | | |
+| CLUSTER ADDNODE | IP:port | |
+| TUNNEL | IP:port | |
+
+NOTE:
+
+- Most of SwarmKV commands are identical the [Redis Commands](https://redis.io/commands/).
+- CLUSTER commands are used for maintainance and debugging purpose, and only available in *swarmkv-cli*.
+
diff --git a/docs/commands.md b/docs/commands.md
deleted file mode 100644
index dd57658..0000000
--- a/docs/commands.md
+++ /dev/null
@@ -1,781 +0,0 @@
-# Commands
-
-
-
-## Generic
-
-### DEL
-
-Syntax
-
-```
-DEL key
-```
-
-Removes the specified key. A key is ignored if it does not exist.
-
-Return
-
-- Integer reply: The number of keys that were removed.
-
-### TYPE
-
-Syntax
-
-```
-TYPE key
-```
-
-Returns the string representation of the type of the value stored at key. The different types that can be returned are: string, integer, set, hash, token-bucket and undefined.
-
-Return
-
-- String reply: type of key, or none when key does not exist.
-
-### EXPIRE
-
-Syntax
-
-```
-EXPIRE key seconds
-```
-
-Set a timeout on `key`. After the timeout has expired, the key will automatically be deleted.
-
-Return
-
-- Integer reply, specifically:
- - 1 if the timeout was set.
- - 0 if the timeout was not set. e.g. key doesn't exist
-
-### TTL
-
-Syntax
-
-```
-TTL key
-```
-
-Returns the remaining time to live of a key that has a timeout.
-
-Return
-
-- Integer reply: TTL in seconds, in case of error:
- - -2 if the key does not exist;
- - -1 if the key exists but has no associated expire.
-
-### PERSIST
-
-Syntax
-
-```
-PERSIST key
-```
-
-Remove the existing timeout on `key`, turning the key from *volatile* (a key with an expire set) to *persistent* (a key that will never expire as no timeout is associated).
-
-Return
-
-- Integer reply, specifically:
- - 1 if the timeout was removed.
- - 0 if key does not exist or does not have an associated timeout.
-
-
-### KEYSLOT
-
-Syntax
-
-```
-KEYSLOT key
-```
-
-Returns an integer identifying the hash slot the specified key hashes to. This command is mainly useful for debugging and testing.
-
-Return
-
-- Integer reply: The hash slot number.
-
-
-## String and Integer
-
-### GET
-
-Syntax
-
-```shell
-GET key
-```
-
-Get the value of `key`.
-
-Return
-
-- String reply: the value of key, or nil when key does not exist.
-- An error is returned if the value stored at `key` is not a string or integer, because `GET`only handles string and integer values.
-
-### SET
-
-Syntax
-
-```
-SET key value
-```
-
-Set `key` to hold the string `value`. If `value` is a numeric string, it's consider as an integer. If `key` already holds string or integer value, it is overwritten.
-
-Return
-
-- String reply: OK if SET was executed correctly.
-
-### INCRBY
-
-Syntax
-
-```
-INCRBY key increment
-```
-
-Increments the number stored at `key` by `increment`. If the key does not exist, it is set to `0` before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64 bit signed integers.
-
-Return
-
-- Integer reply: the value of key after the increment
-
-### INCR
-
-Syntax
-
-```
-INCR key
-```
-
-Increments the number stored at `key` by one. If the key does not exist, it is set to `0` before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64 bit signed integers.
-
-Return
-
-- Integer reply: the value of key after the increment
-
-### DECR
-
-Syntax
-
-```
-DECR key
-```
-
-Decrements the number stored at `key` by one. If the key does not exist, it is set to `0` before performing the operation
-
-Return
-
-- Integer reply: the value of key after the decrement
-
-## Set
-
-### SADD
-
-Syntax
-
-```
-SADD key member [member ...]
-```
-
-Add the specified members to the set stored at `key`. Specified members that are already a member of this set are ignored. If `key` does not exist, a new set is created before adding the specified members.
-
-An error is returned when the value stored at `key` is not a set.
-
-Return
-
-- Integer reply: the number of elements that were added to the set, not including all the elements already present in the set.
-
-### SCARD
-
-Syntax
-
-```
-SCARD key
-```
-
-Returns the set cardinality (number of elements) of the set stored at `key`.
-
-Return
-
-- Integer reply: the cardinality (number of elements) of the set, or `0` if `key` does not exist.
-
-### SREM
-
-Syntax
-
-```
-SREM key member [member ...]
-```
-
-Remove the specified members from the set stored at `key`. Specified members that are not a member of this set are ignored. If `key` does not exist, it is treated as an empty set and this command returns `0`.
-
-An error is returned when the value stored at `key` is not a set.
-
-Return
-
-- Integer reply: the number of members that were removed from the set, not including non existing members. The key will NOT be deleted if all members are removed. If key does not exist, it is treated as an empty set and this command returns 0.
-
-### SISMEMBER
-
-Syntax
-
-```
-SISMEMBER key member
-```
-
-Returns if `member` is a member of the set stored at `key`.
-
-Return
-
-- Integer reply, specifically:
- - `1` if the element is a member of the set.
- - `0` if the element is not a member of the set, or if `key` does not exist.
-
-### SMEMBERS
-
-Syntax
-
-```
-SMEMBERS key
-```
-
-Returns all the members of the set value stored at `key`.
-
-Return
-
-- Array reply: all elements of the set, or empty list if the key does not exist.
-
-## Hash
-
-### HSET
-
-Syntax
-
-```
-HSET key field value [field value ...]
-```
-
-Sets `field` in the hash stored at `key` to `value`. If `key` does not exist, a new key holding a hash is created. If `field` already exists in the hash, it is overwritten.
-
-Return
-
-- Integer reply: The number of fields that were added.
-
-### HDEL
-
-Syntax
-
-```
-HDEL key field [field ...]
-```
-
-Removes the specified fields from the hash stored at `key`. Specified fields that do not exist within this hash are ignored. If `key` does not exist, it is treated as an empty hash and this command returns `0`.
-
-Return
-
-- Integer reply: the number of fields that were removed from the hash, not including specified but non existing fields.
-
-### HGET
-
-Syntax
-
-```
-HGET key field
-```
-
-Returns the value associated with `field` in the hash stored at `key`.
-
-Return
-
-- String reply: the value associated with field, or nil when field is not present in the hash or key does not exist.
-
-### HMGET
-
-Syntax
-
-```
-HMGET key field [field ...]
-```
-
-Returns the values associated with the specified `fields` in the hash stored at `key`.
-
-For every `field` that does not exist in the hash, a `nil` value is returned. Running `HMGET` against a non-existing `key` will return a a `nil` reply.
-
-Return
-
-- Array reply: list of values associated with the given fields, in the same order as they are requested.
-
-### HINCRBY
-
-Syntax
-
-```
-HINCRBY key field increment
-```
-
-Increments the number stored at `field` in the hash stored at `key` by `increment`. If `key` does not exist, a new key holding a hash is created. If `field` does not exist the value is set to `0` before the operation is performed.
-
-The range of values supported by `HINCRBY` is limited to 64 bit signed integers.
-
-Return
-
-- Integer reply: the value at field after the increment operation.
-
-### HKEYS
-
-Syntax
-
-```
-HKEYS key
-```
-
-Returns all field names in the hash stored at `key`.
-
-Return
-
-- Array reply: list of fields in the hash, or an empty list when key does not exist.
-
-### HLEN
-
-Syntax
-
-```
-HLEN key
-```
-
-Returns the number of fields contained in the hash stored at `key`.
-
-Return
-
-- Integer reply: number of fields in the hash, or 0 when key does not exist.
-
-### HGETALL
-
-Syntax
-
-```
-HGETALL key
-```
-
-Returns all fields and values of the hash stored at `key`. In the returned value, every field name is followed by its value, so the length of the reply is twice the size of the hash.
-
-Return
-
-- Array reply: list of fields and their values stored in the hash, or an empty list when key does not exist.
-
-## Token Bucket
-
-### TCFG
-
-Syntax
-
-```
-TCFG key rate capacity
-```
-
-Config a token bucket of `key`, which has a fixed `capacity` bucket and has tokens are added at a fixed `rate`. If `key` does not exist, a new key holding a token bucket is created. If both capacity and rate are 0, the token bucket has infinite tokens.
-
-Return
-
-- Simple String Reply: OK if the token bucket was configured.
-
-### TCONSUME
-
-Syntax
-
-```
-TCONSUME key tokens [ NORMAL | FORCE | FLEXIBLE ]
-```
-
-Consume `tokens` from the token bucket stored at `key`.
-
-The option modify `TCONSUME` behavior if the token bucket has not enough `tokens`.
-
-- NORMAL -- Return 0. The default behavior.
-- FORCE -- Return the requested tokens, which are consumed forcibly.
-- FLEXIBLE -- Return the available tokens.
-
-Return
-
-- Integer Reply: the number of tokens that were allow to consume, or -1 if key does not exist.
-
-### TINFO
-
-Syntax
-
-```
-TINFO key
-```
-
-Return information of token bucket stored at `key`.
-
-Return
-
-- Array reply with information of the token bucket.
-
-Example
-
-```
-swarmkv-sync> tcfg tb-192.168.0.1 20000 10000
-OK
-swarmkv-sync> tinfo tb-192.168.0.1
- 1) "Capacity"
- 2) (integer) 20000
- 3) "Rate"
- 4) (integer) 10000
- 5) "Consumed"
- 6) (integer) 1080
- 7) "Refilled"
- 8) (integer) 21080
- 9) "Available"
-10) (integer) 20000
-```
-## Fair Token Bucket
-
-### FTCFG
-
-Syntax
-
-```
-FTCFG key rate capacity divisor
-```
-
-Config a fair token bucket of `key`, except `rate` and `capacity` as normal token bucket, it has a `divisor` which is used to set a different table size to record the deficit of each member. The specified divisor must be a power of two.
-If `key` does not exist, a new key holding a fair token bucket is created.
-
-Return
-
-- Simple String Reply: OK if the token bucket was configured.
-
-### FTCONSUME
-
-Syntax
-
-```
-FTCONSUME key member weight tokens
-```
-
-Consume `tokens` as `member` with `weight` from the fair token bucket stored at `key`.
-
-The `weight` must be within the range of 1 to 20.
-
-Return
-
-- Integer Reply: the number of tokens that were allow to consume, or -1 if key does not exist.
-
-### FTINFO
-
-Syntax
-
-```
-FTINFO key
-```
-
-Return information of the fair token bucket storead at `key`. The `ActiveMembers` is estimated by HyperLogLog.
-
-Return
-
-- Array reply with information of the filter.
-
-Example
-
-```
-swarmkv-2-nodes> ftcfg abc 1000 2000 4096
-OK
-swarmkv-2-nodes> ftconsume abc 1 2 20
-(integer) 20
-swarmkv-2-nodes> ftinfo abc
- 1) "Rate"
- 2) (integer) 1000
- 3) "Capacity"
- 4) (integer) 2000
- 5) "Consumed"
- 6) (integer) 120
- 7) "Refilled"
- 8) (integer) 2120
- 9) "Available"
-10) (integer) 2000
-11) "Divisor"
-12) (integer) 4096
-13) "ActiveMembers"
-14) (integer) 3
-```
-
-## Bulk Token Bucket
-
-### BTCFG
-
-Syntax
-
-```
-BTCFG key rate capacity buckets
-```
-
-Config a bulk token bucket of `key`, except `rate` and `capacity` as normal token bucket, it has a `buckets` which specifies the number of sub-token buckets. It should be far more greater than the possible members to ensure a higher accuracy.
-If `key` does not exist, a new key holding a fair token bucket is created.
-
-Return
-
-- Simple String Reply: OK if the token bucket was configured.
-
-### BTCONSUME
-
-Syntax
-
-```
-BTCONSUME key member tokens [ NORMAL | FORCE | FLEXIBLE ]
-```
-
-Consume `tokens` form the sub-token bucket `member` of the bulk token bucket stored at `key`.
-
-
-Return
-
-- Integer Reply: the number of tokens that were allow to consume, or -1 if key does not exist.
-
-### BTINFO
-
-Syntax
-
-```
-BTINFO key [member]
-```
-
-Return information of the fair token bucket storead at `key`. The `ActiveMembers` is estimated by HyperLogLog.
-You can optionally specified a `member` to query it's availalbe tokens.
-
-Return
-
-- Array reply with information of the filter.
-
-Example
-
-```
-swarmkv-2-nodes> BTCFG bulk 50000 100000 2048
-OK
-swarmkv-2-nodes> btconsume bulk user1 2000
-(integer) 2000
-swarmkv-2-nodes> BTINFO bulk
- 1) "Rate"
- 2) (integer) 50000
- 3) "Capacity"
- 4) (integer) 100000
- 5) "Buckets"
- 6) (integer) 2048
- 7) "ActiveMembers"
- 8) (integer) 2
- 9) "Collisions"
-10) (double) 0.000000
-11) "Query"
-12) (integer) -1
-swarmkv-2-nodes> BTINFO bulk user1
- 1) "Rate"
- 2) (integer) 50000
- 3) "Capacity"
- 4) (integer) 100000
- 5) "Buckets"
- 6) (integer) 2048
- 7) "ActiveMembers"
- 8) (integer) 2
- 9) "Collisions"
-10) (double) 0.000000
-11) "Query"
-12) (integer) 100000
-```
-
-## Debug
-
-### INFO
-
-Syntax
-
-```
-INFO [section]
-```
-
-The `INFO` command returns information and statistics about the node in a format that is simple to parse by computers and easy to read by humans.
-
-The optional parameter can be used to select a specific section of information:
-
-- Node
-- Store
-- Keyspace
-- Network
-
-Return
-
-- Bulk string reply: as a collection of text lines. Lines can contain a section name (starting with a # character) or a property. All the properties are in the form of **field: value** terminated by \r\n.
-
-Example
-
-```
-swarmkv-sync> info
-# Node
-swarmkv_version: 3.0.0
-address: 127.0.0.1:40619
-uuid: 922eab5d-2634-47f6-934a-1504379a12a3
-worker_threads: 1
-server_time_usec: 1668444667564448
-up_time_in_seconds: 302
-up_time_in_days: 0
-
-# Store
-shards: 8
-keys: 0
-to_sync: 0
-synced: 0
-sync_ok: 0
-sync_err: 0
-sync_interval_in_usec:500
-
-# Keyspace
-health_check_port: 0
-slots: 0
-keys: 0
-expires: 0
-
-# Network
-timeout_in_msec: 500.00
-connections: 2
-pending_rpcs: 0
-timed_out_rpcs: 0
-input_bytes: 4197
-output_bytes: 3481
-input_cmds: 0
-output_cmds: 45
-input_replies: 45
-output_replies: 0
-input_msgs: 45
-output_msgs: 45
-input_buffer: 333
-output_buffer: 0
-unknown_sequence: 0
-instantaneous_input_kbps: 0.00
-instantaneous_output_kbps: 0.00
-instantaneous_input_cps: 0.00
-instantaneous_output_cps: 0.00
-```
-
-### LATENCY
-
-Syntax
-
-```
-LATENCY <subcommand> [<arg> [value] [opt] ...]
-```
-
-The `LANTENCY` command returns latency metrics of command execution.
-Subcommands are:
-* COMMAND [command]
- - Return time-latency samples for a specified command name.
-* PEER [IP:port]
- - Return time-latency samples for the specified peer.
-* EVENT [event]
- - Return time-latency samples for the specified event.
-* RESET [command|event|peer]
- - Reset data of a specified catalog or all the data if no catalog provided.
-
-### DEBUG
-
-Syntax
-
-```
-DEBUG <subcommand> [<arg> [value] [opt] ...].
-```
-
-Subcommands are:
-* SLEEP <seconds>
- - Stop the server for <seconds>. Decimals allowed.
-* ASSERT
- - Crash by assertion failed.
-
-### COMMAND LIST
-
-Syntax
-
-```
-COMMAND LIST
-```
-
-The `COMMAND LIST` command returns an array of the server's command names.
-
-Return
-
-- Array reply: a list of command names.
-
-## Cluster Management
-
-### CLUSTER KEYS
-
-Syntax
-
-```
-CLUSTER KEYS pattern
-```
-Supported glob-style patterns:
-
-- `h?llo` matches `hello`, `hallo` and `hxllo`
-- `h*llo` matches `hllo` and `heeeello`
-- `h[ae]llo` matches `hello` and `hallo,` but not `hillo`
-- `h[^e]llo` matches `hallo`, `hbllo`, ... but not `hello`
-- `h[a-b]llo` matches `hallo` and `hbllo`
-
-Use `\` to escape special characters if you want to match them verbatim.
-The pattern is exactly same as Redis https://redis.io/commands/keys/ .
-
-
-
-## Quick List
-
-| Command | Arguments | Response |
-| ------------------------ | ------------------------------------ | ------------------------------------------------------------ |
-| GET | key | Bulk string reply: the value of key, or nil when key does not exist. |
-| SET | key value | Simple string reply: OK if SET was executed correctly. |
-| DEL | key | Integer reply: The number of keys that were removed. |
-| INCRYBY | key increment | Integer reply: the value of key after the increment |
-| EXPIRE | key seconds | Integer reply, specifically: 1 if the timeout was set. 0 if the timeout was not set. e.g. key doesn't exist |
-| TTL | key | Integer reply: TTL in seconds, in case of error: -2 if the key does not exist; -1 if the key exists but has no associated expire. |
-| PERSIST | key | Integer reply, specifically:<br/><br/>1 if the timeout was removed.<br/>0 if key does not exist or does not have an associated timeout. |
-| KEYSLOT | key | Integer reply: The hash slot the specified key hashes to. |
-| SADD | key member [member ...] | Integer reply: the number of elements that were added to the set, not including all the elements already present in the set. |
-| SREM | key member [member ...] | Integer reply: the number of members that were removed from the set, not including non existing members. The key will NOT be deleted if all members are removed. If key does not exist, it is treated as an empty set and this command returns 0. |
-| SCARD | key | Integer reply: the cardinality (number of elements) of the set, or 0 if key does not exist. |
-| SISMEMBER | key member | Integer reply, specifically:<br/><br/>1 if the element is a member of the set.<br/>0 if the element is not a member of the set, or if key does not exist. |
-| SMEMBERS | key | Array reply: all elements of the set. Empty array if the key does not exist. |
-| HSET | HSET key field value [field value ...] | |
-| HGET |
-| TCFG | key capacity rate | Simple String Reply: OK if the token bucket was configured. Create the token bucket if key does not exist. |
-| TCONSUME | key tokens [NORMAL\|FORCE\|FLEXIBLE] | Integer reply: the number of tokens that were allow to consume, or -1 if key does not exist. |
-| TINFO | key | Array Reply |
-| FTCFG | key capacity rate divisor | Simple String Reply: OK if the token bucket was configured. Create the token bucket if key does not exist. |
-| FTCONSUME | key member weight tokens | Integer reply: the number of tokens that were allow to consume, or -1 if key does not exist. |
-| FTINFO | key | Array Reply|
-| BTCFG | key capacity rate buckets | Simple String Reply: OK if the token bucket was configured. Create the token bucket if key does not exist. |
-| BTCONSUME | key member tokens [NORMAL\|FORCE\|FLEXIBLE] | Integer reply: the number of tokens that were allow to consume, or -1 if key does not exist. |
-| BTINFO | key | Array Reply| | |
-| KEYSPACE RADD | key IP port | Add replica to the key, create the key if key does not exist. |
-| KEYSPACE XRADD | key IP Port | Add replica to the key, return NULL if the key does not exist. |
-| KEYSPACE RLIST | key | Node Array reply:<br/>1) "192.168.1.200:5211"<br/>2) "192.168.1.201:5211"<br/>3) "[2001:db8::1]:5211"<br/> |
-| KEYSPACE DEL | key | |
-| KEYSPACE SETSLOT | see source code | |
-| KEYSPACE GETKEYSINSLOT | IP port slot | |
-| KEYSPACE ADDKEYSTOSLOT | IP port slot blob | |
-| KEYSPACE DELSLOTKEYS | IP port slot | |
-| KEYSPACE KEYS | IP port pattern | |
-| KEYSPACE COUNTKEYSINSLOT | slot | |
-| CRDT DEL | key | Integer reply: The number of cached keys that were removed. |
-| CRDT MERGE | key blob | Simple string reply: OK if MERGE was executed correctly. |
-| CRDT GET | key | Blog reply: a blob of state-based CRDT |
-| CLUSTER KEYS | pattern | Array reply: list of keys matching pattern. |
-| CLUSTER NODES | | |
-| CLUSTER SLOTS | | |
-| CLUSTER ADDNODE | IP:port | |
-| TUNNEL | IP:port | |
-
-NOTE:
-
-- Most of SwarmKV commands are identical the [Redis Commands](https://redis.io/commands/).
-- CLUSTER commands are used for maintainance and debugging purpose, and only available in *swarmkv-cli*.
-
diff --git a/docs/commands/bloom_filter.md b/docs/commands/bloom_filter.md
new file mode 100644
index 0000000..ae18e8f
--- /dev/null
+++ b/docs/commands/bloom_filter.md
@@ -0,0 +1,103 @@
+## Bloom Filter
+
+### BFRESERVE
+
+Syntax
+
+```
+BFRESERVE key error_rate capacity [TIME window-milliseconds slice-number]
+```
+
+ Create an empty bloom filter that can keep elements within a sliding time window with following arguments:
+ - error_rate -- Probability of collision (as long as the capacity is not exceeded). Should be between 0.000001 to 0.1.
+ - capacity -- The expected number of element which will be inserted. At least 1000. If the actual number of elements exceeds the capacity, the bloom filter will expand.
+ - TIME -- Create a time-limited bloom filter. The following arguments are required:
+ - window-milliseconds -- The duration of the time window in milliseconds. Items in time range (now - window-milliseconds, now] are kept.
+ - slice-number -- The number of time slices within the window, which sets the granularity of timeouts to window-milliseconds/slice-number. The value should be between 0 and 64.
+- COUNT --- Create a count-limited bloom filter. **NOT IMPLEMENTED YET**
+
+Return
+
+- Simple String Reply: OK if the bloom filter created successfully.
+- Empty Array if key already exists.
+
+### BFADD
+
+Syntax
+
+```
+BFADD key item [item ...]
+```
+
+Add every `item` to the bloom filter stored at `key`.
+
+
+Return
+
+- Simple String Reply: OK if the items are added to the bloom filter successfully.
+
+### BFEXISTS
+
+Syntax
+
+```
+BFEXISTS key item
+```
+
+Determines if the `item` was added to the bloom filter stored at `key`. This command is similar to BFMEXISTS, except that only one item can be checked.
+
+Return
+
+- Integer reply - where "1" means that, with high probability, item was already added to the filter, and "0" means that key does not exist or that item was definitely not added to the filter.
+
+### BFMEXISTS
+
+Syntax
+
+```
+BFMEXISTS key item [item ...]
+```
+
+Determines if each `item` was added to the bloom filter stored at `key`.
+
+
+Return
+
+- Array reply of Integer reply - where "1" means that, with high probability, item was already added to the filter, and "0" means that key does not exist or that item was definitely not added to the filter.
+
+### BFCARD
+
+Syntax
+
+```
+BFCARD key
+```
+Approximating the number of items (cardinality) of the Bloom filter stored at `key`.
+
+Return
+
+- Integer Reply: the estimated number of items, or 0 if the key does not exist.
+
+### BFINFO
+
+Syntax
+
+```
+BFINFO key
+```
+Returns information about a Bloom filter.
+- ErrorRate -- error_rate arguments specified in BFRESERVE.
+- Capacity -- capacity arguments specified in BFRESERVE.
+- TimeWindowMs -- window-milliseconds arguments specified in BFRESERVE.
+- TimeSlices -- slice-number arguments specified in BFRESERVE.
+- HashNum -- Hash function number caculated by the error_rate, which is log2(1/error_rate).
+- TotalSlices -- The actual slice number after expansion.
+- MaxExpansionTimes -- The maximum expansion times of every time slice.
+- ApproximateItemNum -- The estimated number of items in the filter. Same as value as `BFCARD` returned.
+- FillRatio -- The maximum fill ratio of every slice. Fill ratio that higher than 0.5 indicates high false positives.
+- OldestItemTime -- The possible oldest item of the filter.
+
+Return
+
+- Array reply with argument name (Simple string reply) and value (Integer reply or Double reply) pairs
+- Empty array reply if key does not exist. \ No newline at end of file
diff --git a/docs/commands/cluster.md b/docs/commands/cluster.md
new file mode 100644
index 0000000..38b7d8e
--- /dev/null
+++ b/docs/commands/cluster.md
@@ -0,0 +1,20 @@
+## Cluster Management
+
+### CLUSTER KEYS
+
+Syntax
+
+```
+CLUSTER KEYS pattern
+```
+Supported glob-style patterns:
+
+- `h?llo` matches `hello`, `hallo` and `hxllo`
+- `h*llo` matches `hllo` and `heeeello`
+- `h[ae]llo` matches `hello` and `hallo,` but not `hillo`
+- `h[^e]llo` matches `hallo`, `hbllo`, ... but not `hello`
+- `h[a-b]llo` matches `hallo` and `hbllo`
+
+Use `\` to escape special characters if you want to match them verbatim.
+The pattern is exactly same as Redis https://redis.io/commands/keys/ .
+
diff --git a/docs/commands/generic.md b/docs/commands/generic.md
new file mode 100644
index 0000000..5b31de7
--- /dev/null
+++ b/docs/commands/generic.md
@@ -0,0 +1,92 @@
+## Generic
+
+### DEL
+
+Syntax
+
+```
+DEL key
+```
+
+Removes the specified key. A key is ignored if it does not exist.
+
+Return
+
+- Integer reply: The number of keys that were removed.
+
+### TYPE
+
+Syntax
+
+```
+TYPE key
+```
+
+Returns the string representation of the type of the value stored at key. The different types that can be returned are: string, integer, set, hash, token-bucket and undefined.
+
+Return
+
+- String reply: type of key, or none when key does not exist.
+
+### EXPIRE
+
+Syntax
+
+```
+EXPIRE key seconds
+```
+
+Set a timeout on `key`. After the timeout has expired, the key will automatically be deleted.
+
+Return
+
+- Integer reply, specifically:
+ - 1 if the timeout was set.
+ - 0 if the timeout was not set. e.g. key doesn't exist
+
+### TTL
+
+Syntax
+
+```
+TTL key
+```
+
+Returns the remaining time to live of a key that has a timeout.
+
+Return
+
+- Integer reply: TTL in seconds, in case of error:
+ - -2 if the key does not exist;
+ - -1 if the key exists but has no associated expire.
+
+### PERSIST
+
+Syntax
+
+```
+PERSIST key
+```
+
+Remove the existing timeout on `key`, turning the key from *volatile* (a key with an expire set) to *persistent* (a key that will never expire as no timeout is associated).
+
+Return
+
+- Integer reply, specifically:
+ - 1 if the timeout was removed.
+ - 0 if key does not exist or does not have an associated timeout.
+
+
+### KEYSLOT
+
+Syntax
+
+```
+KEYSLOT key
+```
+
+Returns an integer identifying the hash slot the specified key hashes to. This command is mainly useful for debugging and testing.
+
+Return
+
+- Integer reply: The hash slot number.
diff --git a/docs/commands/hash.md b/docs/commands/hash.md
new file mode 100644
index 0000000..5a9820a
--- /dev/null
+++ b/docs/commands/hash.md
@@ -0,0 +1,117 @@
+## Hash
+
+### HSET
+
+Syntax
+
+```
+HSET key field value [field value ...]
+```
+
+Sets `field` in the hash stored at `key` to `value`. If `key` does not exist, a new key holding a hash is created. If `field` already exists in the hash, it is overwritten.
+
+Return
+
+- Integer reply: The number of fields that were added.
+
+### HDEL
+
+Syntax
+
+```
+HDEL key field [field ...]
+```
+
+Removes the specified fields from the hash stored at `key`. Specified fields that do not exist within this hash are ignored. If `key` does not exist, it is treated as an empty hash and this command returns `0`.
+
+Return
+
+- Integer reply: the number of fields that were removed from the hash, not including specified but non existing fields.
+
+### HGET
+
+Syntax
+
+```
+HGET key field
+```
+
+Returns the value associated with `field` in the hash stored at `key`.
+
+Return
+
+- String reply: the value associated with field, or nil when field is not present in the hash or key does not exist.
+
+### HMGET
+
+Syntax
+
+```
+HMGET key field [field ...]
+```
+
+Returns the values associated with the specified `fields` in the hash stored at `key`.
+
+For every `field` that does not exist in the hash, a `nil` value is returned. Running `HMGET` against a non-existing `key` will return a a `nil` reply.
+
+Return
+
+- Array reply: list of values associated with the given fields, in the same order as they are requested.
+
+### HINCRBY
+
+Syntax
+
+```
+HINCRBY key field increment
+```
+
+Increments the number stored at `field` in the hash stored at `key` by `increment`. If `key` does not exist, a new key holding a hash is created. If `field` does not exist the value is set to `0` before the operation is performed.
+
+The range of values supported by `HINCRBY` is limited to 64 bit signed integers.
+
+Return
+
+- Integer reply: the value at field after the increment operation.
+
+### HKEYS
+
+Syntax
+
+```
+HKEYS key
+```
+
+Returns all field names in the hash stored at `key`.
+
+Return
+
+- Array reply: list of fields in the hash, or an empty list when key does not exist.
+
+### HLEN
+
+Syntax
+
+```
+HLEN key
+```
+
+Returns the number of fields contained in the hash stored at `key`.
+
+Return
+
+- Integer reply: number of fields in the hash, or 0 when key does not exist.
+
+### HGETALL
+
+Syntax
+
+```
+HGETALL key
+```
+
+Returns all fields and values of the hash stored at `key`. In the returned value, every field name is followed by its value, so the length of the reply is twice the size of the hash.
+
+Return
+
+- Array reply: list of fields and their values stored in the hash, or an empty list when key does not exist. \ No newline at end of file
diff --git a/docs/commands/set.md b/docs/commands/set.md
new file mode 100644
index 0000000..2884725
--- /dev/null
+++ b/docs/commands/set.md
@@ -0,0 +1,77 @@
+## Set
+
+### SADD
+
+Syntax
+
+```
+SADD key member [member ...]
+```
+
+Add the specified members to the set stored at `key`. Specified members that are already a member of this set are ignored. If `key` does not exist, a new set is created before adding the specified members.
+
+An error is returned when the value stored at `key` is not a set.
+
+Return
+
+- Integer reply: the number of elements that were added to the set, not including all the elements already present in the set.
+
+### SCARD
+
+Syntax
+
+```
+SCARD key
+```
+
+Returns the set cardinality (number of elements) of the set stored at `key`.
+
+Return
+
+- Integer reply: the cardinality (number of elements) of the set, or `0` if `key` does not exist.
+
+### SREM
+
+Syntax
+
+```
+SREM key member [member ...]
+```
+
+Remove the specified members from the set stored at `key`. Specified members that are not a member of this set are ignored. If `key` does not exist, it is treated as an empty set and this command returns `0`.
+
+An error is returned when the value stored at `key` is not a set.
+
+Return
+
+- Integer reply: the number of members that were removed from the set, not including non existing members. The key will NOT be deleted if all members are removed. If key does not exist, it is treated as an empty set and this command returns 0.
+
+### SISMEMBER
+
+Syntax
+
+```
+SISMEMBER key member
+```
+
+Returns if `member` is a member of the set stored at `key`.
+
+Return
+
+- Integer reply, specifically:
+ - `1` if the element is a member of the set.
+ - `0` if the element is not a member of the set, or if `key` does not exist.
+
+### SMEMBERS
+
+Syntax
+
+```
+SMEMBERS key
+```
+
+Returns all the members of the set value stored at `key`.
+
+Return
+
+- Array reply: all elements of the set, or empty list if the key does not exist. \ No newline at end of file
diff --git a/docs/commands/string_and_integer.md b/docs/commands/string_and_integer.md
new file mode 100644
index 0000000..02bb73d
--- /dev/null
+++ b/docs/commands/string_and_integer.md
@@ -0,0 +1,72 @@
+## String and Integer
+
+### GET
+
+Syntax
+
+```shell
+GET key
+```
+
+Get the value of `key`.
+
+Return
+
+- String reply: the value of key, or nil when key does not exist.
+- An error is returned if the value stored at `key` is not a string or integer, because `GET`only handles string and integer values.
+
+### SET
+
+Syntax
+
+```
+SET key value
+```
+
+Set `key` to hold the string `value`. If `value` is a numeric string, it's consider as an integer. If `key` already holds string or integer value, it is overwritten.
+
+Return
+
+- String reply: OK if SET was executed correctly.
+
+### INCRBY
+
+Syntax
+
+```
+INCRBY key increment
+```
+
+Increments the number stored at `key` by `increment`. If the key does not exist, it is set to `0` before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64 bit signed integers.
+
+Return
+
+- Integer reply: the value of key after the increment
+
+### INCR
+
+Syntax
+
+```
+INCR key
+```
+
+Increments the number stored at `key` by one. If the key does not exist, it is set to `0` before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64 bit signed integers.
+
+Return
+
+- Integer reply: the value of key after the increment
+
+### DECR
+
+Syntax
+
+```
+DECR key
+```
+
+Decrements the number stored at `key` by one. If the key does not exist, it is set to `0` before performing the operation
+
+Return
+
+- Integer reply: the value of key after the decrement \ No newline at end of file
diff --git a/docs/commands/token_bucket.md b/docs/commands/token_bucket.md
new file mode 100644
index 0000000..e788624
--- /dev/null
+++ b/docs/commands/token_bucket.md
@@ -0,0 +1,219 @@
+## Token Bucket
+
+### TCFG
+
+Syntax
+
+```
+TCFG key rate capacity [PD seconds]
+```
+
+Config a token bucket of `key`, which has a fixed `capacity` bucket and has tokens are added at a fixed `rate`. If `key` does not exist, a new key holding a token bucket is created. If both capacity and rate are 0, the token bucket has infinite tokens. The optional `PD` is used to set the token refill period, which is 1 seconds by default.
+
+Return
+
+- Simple String Reply: OK if the token bucket was configured.
+
+### TCONSUME
+
+Syntax
+
+```
+TCONSUME key tokens [ NORMAL | FORCE | FLEXIBLE ]
+```
+
+Consume `tokens` from the token bucket stored at `key`.
+
+The option modify `TCONSUME` behavior if the token bucket has not enough `tokens`.
+
+- NORMAL -- Return 0. The default behavior.
+- FORCE -- Return the requested tokens, which are consumed forcibly.
+- FLEXIBLE -- Return the available tokens.
+
+Return
+
+- Integer Reply: the number of tokens that were allow to consume, or -1 if key does not exist.
+
+### TINFO
+
+Syntax
+
+```
+TINFO key
+```
+
+Return information of token bucket stored at `key`.
+
+Return
+
+- Array reply with information of the token bucket.
+
+Example
+
+```
+swarmkv-sync> tcfg tb-192.168.0.1 20000 10000
+OK
+swarmkv-sync> tinfo tb-192.168.0.1
+ 1) "Capacity"
+ 2) (integer) 20000
+ 3) "Rate"
+ 4) (integer) 10000
+ 5) "Consumed"
+ 6) (integer) 1080
+ 7) "Refilled"
+ 8) (integer) 21080
+ 9) "Available"
+10) (integer) 20000
+```
+## Fair Token Bucket
+
+### FTCFG
+
+Syntax
+
+```
+FTCFG key rate capacity divisor [PD seconds]
+```
+
+Config a fair token bucket of `key`, except `rate` and `capacity` as normal token bucket, it has a `divisor` which is used to set a different table size to record the deficit of each member. The specified divisor must be a power of two. The optional `PD` is used to set the token refill period, which is 1 seconds by default.
+If `key` does not exist, a new key holding a fair token bucket is created.
+
+Return
+
+- Simple String Reply: OK if the token bucket was configured.
+
+### FTCONSUME
+
+Syntax
+
+```
+FTCONSUME key member weight tokens
+```
+
+Consume `tokens` as `member` with `weight` from the fair token bucket stored at `key`.
+
+The `weight` must be within the range of 1 to 20.
+
+Return
+
+- Integer Reply: the number of tokens that were allow to consume, or -1 if key does not exist.
+
+### FTINFO
+
+Syntax
+
+```
+FTINFO key
+```
+
+Return information of the fair token bucket storead at `key`. The `ActiveMembers` is estimated by HyperLogLog.
+
+Return
+
+- Array reply with information of the filter.
+
+Example
+
+```
+swarmkv-2-nodes> ftcfg abc 1000 2000 4096
+OK
+swarmkv-2-nodes> ftconsume abc 1 2 20
+(integer) 20
+swarmkv-2-nodes> ftinfo abc
+ 1) "Rate"
+ 2) (integer) 1000
+ 3) "Capacity"
+ 4) (integer) 2000
+ 5) "Consumed"
+ 6) (integer) 120
+ 7) "Refilled"
+ 8) (integer) 2120
+ 9) "Available"
+10) (integer) 2000
+11) "Divisor"
+12) (integer) 4096
+13) "ActiveMembers"
+14) (integer) 3
+```
+
+## Bulk Token Bucket
+
+### BTCFG
+
+Syntax
+
+```
+BTCFG key rate capacity buckets [PD seconds]
+```
+
+Config a bulk token bucket of `key`, except `rate` and `capacity` as normal token bucket, it has a `buckets` which specifies the number of sub-token buckets. It should be far more greater than the possible members to ensure a higher accuracy. The optional `PD` is used to set the token refill period, which is 1 seconds by default.
+If `key` does not exist, a new key holding a fair token bucket is created.
+
+Return
+
+- Simple String Reply: OK if the token bucket was configured.
+
+### BTCONSUME
+
+Syntax
+
+```
+BTCONSUME key member tokens [ NORMAL | FORCE | FLEXIBLE ]
+```
+
+Consume `tokens` from the sub-token bucket `member` of the bulk token bucket stored at `key`.
+
+
+Return
+
+- Integer Reply: the number of tokens that were allow to consume, or -1 if key does not exist.
+
+### BTINFO
+
+Syntax
+
+```
+BTINFO key [member]
+```
+
+Return information of the fair token bucket storead at `key`. The `ActiveMembers` is estimated by HyperLogLog.
+You can optionally specified a `member` to query it's availalbe tokens.
+
+Return
+
+- Array reply with information of the filter.
+
+Example
+
+```
+swarmkv-2-nodes> BTCFG bulk 50000 100000 2048
+OK
+swarmkv-2-nodes> btconsume bulk user1 2000
+(integer) 2000
+swarmkv-2-nodes> BTINFO bulk
+ 1) "Rate"
+ 2) (integer) 50000
+ 3) "Capacity"
+ 4) (integer) 100000
+ 5) "Buckets"
+ 6) (integer) 2048
+ 7) "ActiveMembers"
+ 8) (integer) 2
+ 9) "Collisions"
+10) (double) 0.000000
+11) "Query"
+12) (integer) -1
+swarmkv-2-nodes> BTINFO bulk user1
+ 1) "Rate"
+ 2) (integer) 50000
+ 3) "Capacity"
+ 4) (integer) 100000
+ 5) "Buckets"
+ 6) (integer) 2048
+ 7) "ActiveMembers"
+ 8) (integer) 2
+ 9) "Collisions"
+10) (double) 0.000000
+11) "Query"
+12) (integer) 100000
+``` \ No newline at end of file
diff --git a/docs/commands/trouble_shooting.md b/docs/commands/trouble_shooting.md
new file mode 100644
index 0000000..7628f69
--- /dev/null
+++ b/docs/commands/trouble_shooting.md
@@ -0,0 +1,105 @@
+## Debug
+
+### INFO
+
+Syntax
+
+```
+INFO [section]
+```
+
+The `INFO` command returns information and statistics about the node in a format that is simple to parse by computers and easy to read by humans.
+
+The optional parameter can be used to select a specific section of information:
+
+- Node
+- Store
+- Keyspace
+- Network
+
+Return
+
+- Bulk string reply: as a collection of text lines. Lines can contain a section name (starting with a # character) or a property. All the properties are in the form of **field: value** terminated by \r\n.
+
+Example
+
+```
+swarmkv-sync> info
+# Node
+swarmkv_version: 3.0.0
+address: 127.0.0.1:40619
+uuid: 922eab5d-2634-47f6-934a-1504379a12a3
+worker_threads: 1
+server_time_usec: 1668444667564448
+up_time_in_seconds: 302
+up_time_in_days: 0
+
+# Store
+shards: 8
+keys: 0
+to_sync: 0
+synced: 0
+sync_ok: 0
+sync_err: 0
+sync_interval_in_usec:500
+
+# Keyspace
+health_check_port: 0
+slots: 0
+keys: 0
+expires: 0
+
+# Network
+timeout_in_msec: 500.00
+connections: 2
+pending_rpcs: 0
+timed_out_rpcs: 0
+input_bytes: 4197
+output_bytes: 3481
+input_cmds: 0
+output_cmds: 45
+input_replies: 45
+output_replies: 0
+input_msgs: 45
+output_msgs: 45
+input_buffer: 333
+output_buffer: 0
+unknown_sequence: 0
+instantaneous_input_kbps: 0.00
+instantaneous_output_kbps: 0.00
+instantaneous_input_cps: 0.00
+instantaneous_output_cps: 0.00
+```
+
+### LATENCY
+
+Syntax
+
+```
+LATENCY <subcommand> [<arg> [value] [opt] ...]
+```
+
+The `LANTENCY` command returns latency metrics of command execution.
+Subcommands are:
+* COMMAND [command]
+ - Return time-latency samples for a specified command name.
+* PEER [IP:port]
+ - Return time-latency samples for the specified peer.
+* EVENT [event]
+ - Return time-latency samples for the specified event.
+* RESET [command|event|peer]
+ - Reset data of a specified catalog or all the data if no catalog provided.
+
+### DEBUG
+
+Syntax
+
+```
+DEBUG <subcommand> [<arg> [value] [opt] ...].
+```
+
+Subcommands are:
+* SLEEP <seconds>
+ - Stop the server for <seconds>. Decimals allowed.
+* ASSERT
+ - Crash by assertion failed. \ No newline at end of file
diff --git a/docs/crdt.md b/docs/crdt.md
index 9798dc4..7e2940c 100644
--- a/docs/crdt.md
+++ b/docs/crdt.md
@@ -1,36 +1,63 @@
-## CRDT Explained
+# CRDT Explained
Swarm KV uses state-based CRDT types to store values.
A Conflict-free Replicated Data Type (CRDT) is a data structure that simplifies distributed data storage systems and multi-user applications. In many systems, copies of some data need to be stored on multiple computers (known as replicas).
-### String
+
+# Basic CRDT
+
+## String
Swarm KV implements Last-Write-Wins Register (LWW Register) to store string values. Swarm KV didn't implement logical clock such as [Version Vector](https://en.wikipedia.org/wiki/Version_vector) or [Lamport Clock](https://en.wikipedia.org/wiki/Lamport_timestamp), instead, it uses real time (man 3 gettimeofday) for simplicity.
-### Integer
+## Integer
+
+Swarm KV uses Positive-Negative Counter ([PN-Counter](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type#PN-Counter_(Positive-Negative_Counter))) to store integer values.
+
+## Set and Hash
+
+Swarm KV implements Add-Wins Observed-Remove Set (OR Set) to store set and hash values. OR Set is state-based and has no tombstone. Please referrer [An optimized conflict-free replicated set](https://arxiv.org/pdf/1210.3368.pdf) for more information.
+
+# Probabilistic CRDT
+
+## Age-Partioned Bloom Filter
+An Age-partitioned Bloom Filter (APBloom) is a Bloom filter variant that supports duplicate detection within a sliding window. The APBloom records elements within a time range from 'now-window' to 'now'. It's a CRDT version of the [Age-partitioned bloom filters](https://arxiv.org/pdf/2001.03147.pdf).
-Swarm KV uses Positive-Negative Counter (PN-Counter) to store integer values.
+## Staggered HyperLogLog
+Staggered HyperLogLog is a HyperLogLog variant that supports near-continuous-time cardinality estimation. Please refere to [Staggered HLL: Near-continuous-time cardinality estimation with no overhead](https://www.sciencedirect.com/science/article/abs/pii/S0140366422002407) for more information.
-### Set and Hash
+# Token Bucket CRDT
-Swarm KV implements Add-Wins Observed-Remove Set (OR Set) to store set and hash values. OR Set is state-based and has no tombstone. Please referrer [An optimized conflict-free replicated set](https://arxiv.org/pdf/1210.3368.pdf) for more details.
+## Observed-Consumed Token Bucket
-### Token Bucket
+Swarm KV implements a novel Observe-Consumed Token Bucket (OC Token Bucket), which has a PN-Counter to track consumed tokens and a Last-write-wins register to track refilled tokens. It is configured with three parameters:
-Swarm KV implements a novel Observe-Consumed Token Bucket (OC Token Bucket), which has a PN-Counter to track consumed tokens and a Last-write-wins register to track refilled tokens. It is initialized with two parameters:
+ - rate - The number of tokens to be refilled in each period.
+ - period - The time period in second.
+ - capacity - The capacity of the token bucket.
-- CIR: Committed Information Rate
-- CBS: Committed Burst Size
+For example, consider a token bucket with a capacity of 100, refilling at a rate of 10 tokens every 5 minutes.
-At the very beginning, the bucket is full, which means it has *CBS* tokens. You can reconfigure the token bucket at any time. Note that the reconfiguration will not refill the token bucket to full.
+At the very beginning, the bucket is full, which means it has *capacity* tokens. You can reconfigure the token bucket at any time. Note that the reconfiguration will not refill the token bucket to full.
-If a network partition happens, the token bucket is out-of-sync, and each replica has the entire CIR. And after the partition heals, replicas share the CIR again. The OC Token Bucket is robust to overuse as long as the sync interval is reasonable, i.e., 200ms.
+If a network partition happens, the token bucket is out-of-sync, and each replica has the entire rate. And after the partition heals, replicas share the rate again. The OC Token Bucket is robust to overuse as long as the sync interval is reasonable, i.e., 200ms.
-### Fair Split Token Bucket
-Fair Split Token Bucket is implemented with a Count-min Sketch and an OC Token Bucket. It archieves [max-min fairness](https://www.ece.rutgers.edu/~marsic/Teaching/CCN/minmax-fairsh.html) which is defined as follows:
+## Fair Token Bucket
+Fair Token Bucket is used to distribute tokens from a single bucket among multiple members. It achieves [max-min fairness](https://www.ece.rutgers.edu/~marsic/Teaching/CCN/minmax-fairsh.html) through the use of Deficit Round Robin (DRR), which is defined as follows:
- Resources are allocated in order of increasing demand
- No source gets a resource share larger than its demand
- Sources with unsatisfied demands get an equal share of the resource
+The Fair Token Bucket is implemented using an OC Token Bucket and Staggered HyperLogLog. The Staggered HyperLogLog is used to count the active members within each weight category over the last 5 seconds. This count is then utilized for per-weight quantum estimation. Each member is hashed into a counter of Stochastic Fair Queuing (SFQ). The counter records the consumed tokens of current interval.If the counter exceed the weight * per-weight quantum, the member is rejected to consume the OC token bucket. The length of SFQ is configured by *divisor*. Because multiple members may get hashed to the same bucket, the hashing algorithm is perturbed at configurable intervals so that the unfairness lasts only for a short while.
+
+The underlying principle of the FTB is to reserve quantum for a perturbation interval (10 ms). If the reserved quantum remains unused, it is carried over to the next interval, contributing to an approach towards max-min fairness. The shorter the perturbation interval, the closer the system gets to achieving max-min fairness.
+
+Note that the SFQ is not synchronized across replicas, and an excessive member may consume more tokens than its allotted share.
+
+## Bulk Token Bucket
+A bulk version of OC token bucket, which can be used for large amount of token buckets with same configuration. It uses a Grow-Only Counter Array for recording consumed tokens.
+
+
+
# References
Bieniusa, Annette, et al. "[An optimized conflict-free replicated set](https://arxiv.org/pdf/1210.3368.pdf)." *arXiv preprint arXiv:1210.3368* (2012).
@@ -62,3 +89,6 @@ Flajolet, Philippe, et al. "[Hyperloglog: the analysis of a near-optimal cardina
Cornacchia, Alessandro, et al. "[Staggered HLL: Near-continuous-time cardinality estimation with no overhead.](https://www.sciencedirect.com/science/article/abs/pii/S0140366422002407)" Computer Communications 193 (2022): 168-175.
+Shtul, Ariel, Carlos Baquero, and Paulo Sérgio Almeida. "[Age-partitioned bloom filters.](https://arxiv.org/pdf/2001.03147.pdf)" arXiv preprint arXiv:2001.03147 (2020).
+
+Rodrigues, Ana, et al. "[Time-limited Bloom Filter.](https://arxiv.org/pdf/2306.06742.pdf)" Proceedings of the 38th ACM/SIGAPP Symposium on Applied Computing. 2023. \ No newline at end of file
diff --git a/docs/imgs/thread_model.svg b/docs/imgs/thread_model.svg
index c72f4db..258a77c 100644
--- a/docs/imgs/thread_model.svg
+++ b/docs/imgs/thread_model.svg
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Do not edit this file with editors other than diagrams.net -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="541px" height="431px" viewBox="-0.5 -0.5 541 431" content="&lt;mxfile host=&quot;Electron&quot; modified=&quot;2023-08-19T09:52:17.508Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36&quot; etag=&quot;z16tJf-ogPw3kx_q-YB4&quot; version=&quot;20.3.0&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;dmiFLDJCBrHLQ1h-NkZl&quot; name=&quot;Page-1&quot;&gt;7Vtbd5s4EP41Pqf7EA4gLuaxuWx7umk3u9lum33JkUE2NDJyBI7t/fUrjLgIQYwdcPCmfkjQgIT4ZjTfzAhG4GK+/kDhwv9MPIRHuuqtR+BypOuObbG/iWCTCixznApmNPBSkVYIboN/EReqXLoMPBQJF8aE4DhYiEKXhCFyY0EGKSUr8bIpweJdF3CGJMGtC7Es/RZ4sZ9Kx6ZayD+iYOZnd9ZUfmYOs4u5IPKhR1YlEbgagQtKSJwezdcXCCfYZbik/X5tOJtPjKIwbtNh5d5stLu7zwsrOrv651Pw+YdqnFl2OswTxEv+xHy28SaDgJJl6KFkFHUEzld+EKPbBXSTsyumcybz4zlmLY0dTkkYcy1qyeXyLPnEnxCN0bok4rP+gMgcxXTDLuFnDYebzCYzDY7oqlCIbvJ5+yVlGIBfCLkRzPKxC5zYAYdqH9is4cOWL6FnYVOPCVs2oxJsFxBjRJnsL58i6EkoslWzSA6Xc3wdTBEOQtY6XyAasCmxjuASc/FNIduFNnMWMWRdaN7GGC6iYLK9a4I/Re6SRsET+hNFqVoSKVnGyZ0ucl/TlaosTVSVKavKtGRNAbU3TYGODVxG6XkLaY+dIUCX0UsJOa3GxvUOgPN/fPzy+PUxhHPn6+rx04+zP+6+nGm94CbB0RrKtrhpqq2YraBz1OzC7q1ORu8boQ+Cf8hM/q35CDAGu32EfVQf0TUJtjXs/f2raOt2Ox8x7g04/aeZN6nKBLvN3KpRVo9m7rySmev7YqebAnSO3crMDa0v4MzdwKHQe59kaazlYhhFgStiJQLLDPFp20hOMfTo5ntyRmEPztt3/Mpt43IttDZZax3E37NB2PFdSV50SRpZj0a7jiGdoWeVyH0k8oQ0U1Zh2YvXKCmTUYRhzNaiMIs6zfE73JCATbmwEEc0EebkxCEisqQu4r3KyWRloDxHaBooRUYaaGtI+WO/wLbkvHWOIv8+Cr137tz7RTY0poBrOEFYtC+Ig1mYGB/T7NZdJqstYOn/e35iHnheMgZzg8z7wcItLpJH2z6seT4yL/dY19mykGM6XsTgdxmV6wR16/1MVTQwFpPjLAA91FAqw2Q9yHQaoX40OW5BhtrbJEPLsAdGhuZrkeF4X+wqZNg+v+kiNax9Bl2ufvTDhy8mQ06o+/NhSh8t+HA3cTqDIk5D1UW+A45ig8O409TEVa07uqJXxuqZPnU50Q5RfArsqTcViPZmT1VRbd0RNfEyW+mfLLMZlvT2hXhIpkeGTdyfruAyJpz+kqGjmJIHxn6YJMQZki0VTwOMq6JSkdvqqCqiinGopskMadcyZF9OXi6cJhqSPf1b0ZA5doalIdBiy+slNMwJNSPNu5K8nk1z4s5ZPCVhO89qG7jbg5Gf3/cFnOy05eRhJbNGZe0D49Bk1qwks85xk1lD9uoZG1O0wJsh83G+mjrgY2BYL2TgLPa3xR79EbIhZ68j3cKJZ48WMBS0Zj0uk73+c+Folvz/DW3O/t6OwPuyuaTd0/OZeEJbDnjgHG4QolHjHBoycHfDcl8PUbA72ZukbvN6kgug+zDbOtPf0xQ646uUg0zFMlm0rBsasB2gjvVRLzvlYCyuf6Nlmm30RVFGi93Xo1KUwDSlwqujipVXJcmTdiScSatUk+kooRwIJ4GxWHZgRnsYJ+mOoaiGU/ykcRXjuCRlSDaZV1wHz1L5ghoUS1UU2B9LmXWVp58sdVosVd0ZfXWWMuUi0q0PaUI6J5fs9hVJOG0L9n3pSE4t3rqOTKANS0dWnXfuMNo7iX2BtjUIc1j7ApYmGpNuO4fFe3ZlIKCDY28KWLI/P5U99XwJvcldgSwe7cmBiMnf8cqbh3sTs3VFc1jexHTEFxXBod5EcktHrmhacgHjdJLFfDkNKlmscEGP3kRO9H8mi6eWLFbfHHr1ZNGSXwZ964mIZYg1vVdPRGxZRyS83/rre3fyrt5n3/ImobFPZiSE+KqQ1ikRoylfx24Qzq63rcu0apyHKBNM3AcpKikVns1KMKGqoBqpyElQJxXpnaFCx6/oAl1XHEMtfpWPbxpCAoYk3JQu4wuh8bZAr7vts0GG1MWodmEH6TwaH1VRSyVupzIcUFRTroB3H8LUfsglf9ziZp8qYkIWp+G1GgMZybCb36OsfEaqG5bkpcY9ealaxchvoKI1cusz1P+vUirxvQr6UgprFt+op4ur+NAfXP0H&lt;/diagram&gt;&lt;/mxfile&gt;"><defs/><g><rect x="288" y="0" width="252" height="430" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="0" y="0" width="250" height="430" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="51" y="50" width="56" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 79 90 L 79 350" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 54px; height: 1px; padding-top: 70px; margin-left: 52px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Caller Thread</div></div></div></foreignObject><text x="79" y="74" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Caller Th...</text></switch></g><rect x="75" y="108" width="10" height="20" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="75" y="157.5" width="10" height="90.5" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="173" y="50" width="57" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 201.5 90 L 201.5 350" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 55px; height: 1px; padding-top: 70px; margin-left: 174px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Worker Thread 0</div></div></div></foreignObject><text x="202" y="74" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Worker Th...</text></switch></g><rect x="197" y="128" width="10" height="80" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="323" y="50" width="60" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 353 90 L 353 350" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 70px; margin-left: 324px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Worker Thread 0</div></div></div></foreignObject><text x="353" y="74" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Worker Thr...</text></switch></g><rect x="348" y="147" width="10" height="41" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 85 128 Q 85 128 193.13 128" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 198.38 128 L 191.38 131.5 L 193.13 128 L 191.38 124.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 129px; margin-left: 135px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">mesh_snd(cmd)</div></div></div></foreignObject><text x="135" y="132" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">mesh_snd(cmd)</text></switch></g><rect x="437" y="50" width="60" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 467 90 L 467 350" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 70px; margin-left: 438px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Worker Thread 1</div></div></div></foreignObject><text x="467" y="74" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Worker Thr...</text></switch></g><rect x="462" y="157.5" width="10" height="20" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 207 148 Q 207 148 341.63 147.05" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 346.88 147.01 L 339.91 150.56 L 341.63 147.05 L 339.86 143.56 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 146px; margin-left: 283px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">net_snd(cmd)</div></div></div></foreignObject><text x="283" y="149" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">net_snd(cmd)</text></switch></g><rect x="90" y="10" width="70" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 25px; margin-left: 125px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">Node1</div></div></div></foreignObject><text x="125" y="30" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">Node1</text></switch></g><rect x="379" y="10" width="70" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 25px; margin-left: 414px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">Node2</div></div></div></foreignObject><text x="414" y="30" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">Node2</text></switch></g><path d="M 348 188 Q 348 188 213.37 188" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 208.12 188 L 215.12 184.5 L 213.37 188 L 215.12 191.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 189px; margin-left: 281px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">net_snd(reply)</div></div></div></foreignObject><text x="281" y="192" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">net_snd(reply)</text></switch></g><path d="M 170 355.66 C 170 352.53 183.43 350 200 350 C 207.96 350 215.59 350.6 221.21 351.66 C 226.84 352.72 230 354.16 230 355.66 L 230 384.34 C 230 387.47 216.57 390 200 390 C 183.43 390 170 387.47 170 384.34 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 230 355.66 C 230 358.78 216.57 361.31 200 361.31 C 183.43 361.31 170 358.78 170 355.66" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 375px; margin-left: 171px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><span style="">Key-Value</span><br style="" /><span style="">Key-Peers</span></div></div></div></foreignObject><text x="200" y="378" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Key-Value...</text></switch></g><path d="M 197 208 Q 197 208 90.42 208.38" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 85.17 208.4 L 92.16 204.87 L 90.42 208.38 L 92.18 211.87 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 209px; margin-left: 146px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">mesh_snd(reply)</div></div></div></foreignObject><text x="146" y="212" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">mesh_snd(reply)</text></switch></g><path d="M 323 355.66 C 323 352.53 336.43 350 353 350 C 360.96 350 368.59 350.6 374.21 351.66 C 379.84 352.72 383 354.16 383 355.66 L 383 384.34 C 383 387.47 369.57 390 353 390 C 336.43 390 323 387.47 323 384.34 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 383 355.66 C 383 358.78 369.57 361.31 353 361.31 C 336.43 361.31 323 358.78 323 355.66" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 375px; margin-left: 324px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><span style="">Key-Value</span><br style="" /><span style="">Key-Peers</span></div></div></div></foreignObject><text x="353" y="378" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Key-Value...</text></switch></g><rect x="170" y="390" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 405px; margin-left: 200px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">Sharded</div></div></div></foreignObject><text x="200" y="408" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Sharded</text></switch></g><rect x="321" y="390" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 405px; margin-left: 351px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">Sharded</div></div></div></foreignObject><text x="351" y="408" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Sharded</text></switch></g><path d="M 358 157.25 Q 358 157.25 455.63 157.48" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 460.88 157.5 L 453.87 160.98 L 455.63 157.48 L 453.89 153.98 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 156px; margin-left: 415px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">mesh_snd(cmd)</div></div></div></foreignObject><text x="415" y="159" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">mesh_snd(cmd)</text></switch></g><path d="M 462 177.5 Q 462 177.5 364.37 177.73" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 359.12 177.75 L 366.11 174.23 L 364.37 177.73 L 366.13 181.23 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 178px; margin-left: 416px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">mesh_snd(reply)</div></div></div></foreignObject><text x="416" y="181" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">mesh_snd(reply)</text></switch></g><path d="M 437 355.66 C 437 352.53 450.43 350 467 350 C 474.96 350 482.59 350.6 488.21 351.66 C 493.84 352.72 497 354.16 497 355.66 L 497 384.34 C 497 387.47 483.57 390 467 390 C 450.43 390 437 387.47 437 384.34 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 497 355.66 C 497 358.78 483.57 361.31 467 361.31 C 450.43 361.31 437 358.78 437 355.66" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 375px; margin-left: 438px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><span style="">Key-Value</span><br style="" /><span style="">Key-Peers</span></div></div></div></foreignObject><text x="467" y="378" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Key-Value...</text></switch></g><rect x="435" y="390" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 405px; margin-left: 465px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">Sharded</div></div></div></foreignObject><text x="465" y="408" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Sharded</text></switch></g><path d="M 85 222.94 Q 115 222.9 115 233 Q 115 243.1 93.22 243.07" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 86.22 243.06 L 93.22 239.57 L 93.21 246.57 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 233px; margin-left: 119px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: left;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">on_reply_cb()</div></div></div></foreignObject><text x="119" y="236" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px">on_reply_cb()</text></switch></g><rect x="0" y="146" width="80" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 161px; margin-left: 40px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">caller loop</div></div></div></foreignObject><text x="40" y="165" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">caller loop</text></switch></g><rect x="1" y="103" width="80" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 118px; margin-left: 41px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">exec(cmd)</div></div></div></foreignObject><text x="41" y="122" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">exec(cmd)</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="541px" height="431px" viewBox="-0.5 -0.5 541 431" content="&lt;mxfile host=&quot;Electron&quot; modified=&quot;2024-02-18T07:23:38.785Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36&quot; etag=&quot;sTkjNX0JbNivaBym-ACI&quot; version=&quot;20.3.0&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;dmiFLDJCBrHLQ1h-NkZl&quot; name=&quot;Page-1&quot;&gt;7VtZd6M2FP41Pmf6EA4gFvM4WTpzpplp2nQ6k77kyCAbEhk5Asd2f32FEYsQjrENDk7qhwRd0MJdvk/3AgNwMV1+onDmfyUewgNd9ZYDcDnQdce22N9EsEoFljlMBRMaeKlIKwS3wb+IC1UunQceioQLY0JwHMxEoUvCELmxIIOUkoV42ZhgcdYZnCBJcOtCLEt/BF7sp9KhqRbyzyiY+NnMmsrPTGF2MRdEPvTIoiQCVwNwQQmJ06Pp8gLhRHeZXtJ+v244my+MojBu0mHh3qy0u7uvMys6u/rnS/D1QTXOLDsd5hniOb9jvtp4lamAknnooWQUdQDOF34Qo9sZdJOzC2ZzJvPjKWYtjR2OSRhzK2rJ5fIq+cKfEY3RsiTiq/6EyBTFdMUu4WcNh7vMKnMNrtFFYRDd5Ov2S8YwAL8QcieY5GMXemIHXFW7qM3qv9ryEHpRbeox1ZatqKS2C4gxokz2l08R9CQtsqiZJYfzKb4OxggHIWudzxAN2JJYR3CJufimkG3TNgOLGLIuNG9jDGdRMFrPmuifIndOo+AZ/Ymi1CyJlMzjZKaLHGvaMpWliaYyZVOZlmwpoHZmKdCyg8taetlDmuvOEFSX0UtJc1qNj+stKM5/+Pzt6ftTCKfO98XTl4ezP+6+nWknqjdNtRWzkeocNbuwfa+TtfeD0EcBHzKXf28YAYZgO0bYR8WItkmwqa/vjq+ir9vNMGLYmeL0/918k6lMsN3NrRpjdejmziu5ub6r7nRTUJ1jN3JzQ+tKceZ2xaHQ+5hkaazlYhhFgSvqSlQsc8TndSM5xbRHVz+TMwq7cd6+41euG5dLobXKWssg/pkNwo7vSvKiS9LIemz06xjSCXrRiBwjkSekmbIJyyheY6RMRhGGMYtFYRV1luMz3JCALbnwEEd0EQZy4hARmVMX8V7lZLIyUJ4jbBoo1Yw00NqR8ts+wLfkvHWKIv8+Cr0P7tT7RXY0ZoBrOEJY9C+Ig0mYOB+z7Bouk2gLWPr/kZ+YBp6XjMFgkKEfLGBxltza+mbN84F5uUNcZ2FRjeu8iMFnGZTrBHXxfqYqGhiKyXG2Ad3XUSrDZD3IeByhbiw5bECG2vskQ8uwe0aG5muR4XBX3VXIsHl+00ZqWHsPulz96IYPDyZDTqi782FKHw34cDtxOr0iTkPVRb4DjmKD/bjT1MSo1h1d0StjdUyfupxohyg+BfbUNxU6dmZPVVFt3REtcZivdE+W2QpLdvtGPCTTI9NN3J2t4DwmnP6SoaOYkkfGfpgkxBmSNRWPA4yrolKR22qpKqKK+1BNkxnSrmXIrkBeLpwmFpKR/r1YyBw6/bIQaPDI6xAa5oSakeZdSV7Ppjlx5yyekrCdZ7UbuNuDkZ/PewAnO005uV/JrFGJfWDsm8yalWTWOW4ya8ionrExRTO86jMf59HUAh8DwzqQgbO9vy326I6QDTl7HegWTpA9msFQsJr1NE+e9Z8LR5Pk/29odfb3egTed0SzU5mErS4dMBXfwIBGkltkSbG7YumohyjYnn+NUiS7HuUC6D5O1vj2e5rVZhSS0oKpWCbbwOqGBmwHqEN90MnDazAUQ9JomPkaXbGG0eCB6FFZQwD/Ui3UUcViqJKkLltywKRVKpO0lOP1hCbAUKwEMKfdjyZ0x1BUwyl+0riKcVzeMCSfzIugvSeOPKB6RRwVA3ZHHGZdMehw4qjQRJlPGg24xxo4FW2Y/80yVPVB5aszlCnXdG59SBPCObncs6tdhNO0ft6VjeSd/nu3kQm0ftnIqkPmFnd6J1Gmb1oSMPtVprc00Zl029lvr2dXBgI6OHaN3pLx/FQecech9C6L9NletCMAERO/41Ub90cTs3GBsV9oYjrie4NgXzSRYOnIBUZLLl6cTqKYh1OvEsUKF3SIJnKS34dE8a0XIKuv3rx6emfJb1O+99TBMsQK3KunDrZsIxLerxH23h19qEfZW94kNPbJhIQQXxXSOiNiNObR7wbh5HrdukxrvPmmYoSJ+yjtI0plYrNC/6oKqnsLOW1ppX68ldxbfscV6LriGGrxq3y9soHEmSbhqnQZD4SN0wK9btoXtwVSF6PahR2k69h4q4paKkg7leGAoppyvbr9TUftl1Dy1yFu9q0fJmR2GqjVAkpVv8PUDUtCqWFHKFVrGPkVTrREbn1O+XaNUtmRq6Aro7Bm8ZF3GlzFl/Lg6j8=&lt;/diagram&gt;&lt;/mxfile&gt;"><defs/><g><rect x="288" y="0" width="252" height="430" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="0" y="0" width="250" height="430" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="51" y="50" width="56" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 79 90 L 79 350" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 54px; height: 1px; padding-top: 70px; margin-left: 52px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Caller Thread</div></div></div></foreignObject><text x="79" y="74" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Caller Th...</text></switch></g><rect x="75" y="108" width="10" height="20" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="75" y="157.5" width="10" height="90.5" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="173" y="50" width="57" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 201.5 90 L 201.5 350" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 55px; height: 1px; padding-top: 70px; margin-left: 174px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Worker Thread 0</div></div></div></foreignObject><text x="202" y="74" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Worker Th...</text></switch></g><rect x="197" y="128" width="10" height="80" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="323" y="50" width="60" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 353 90 L 353 350" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 70px; margin-left: 324px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Worker Thread 0</div></div></div></foreignObject><text x="353" y="74" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Worker Thr...</text></switch></g><rect x="348" y="147" width="10" height="41" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 85 128 Q 85 128 193.13 128" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 198.38 128 L 191.38 131.5 L 193.13 128 L 191.38 124.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 129px; margin-left: 135px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">mesh_snd(cmd)</div></div></div></foreignObject><text x="135" y="132" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">mesh_snd(cmd)</text></switch></g><rect x="437" y="50" width="60" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 467 90 L 467 350" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 70px; margin-left: 438px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Worker Thread 1</div></div></div></foreignObject><text x="467" y="74" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Worker Thr...</text></switch></g><rect x="462" y="157.5" width="10" height="20" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 207 148 Q 207 148 341.63 147.05" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 346.88 147.01 L 339.91 150.56 L 341.63 147.05 L 339.86 143.56 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 146px; margin-left: 283px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">net_snd(cmd)</div></div></div></foreignObject><text x="283" y="149" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">net_snd(cmd)</text></switch></g><rect x="90" y="10" width="70" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 25px; margin-left: 125px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">Node1</div></div></div></foreignObject><text x="125" y="30" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">Node1</text></switch></g><rect x="379" y="10" width="70" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 25px; margin-left: 414px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">Node2</div></div></div></foreignObject><text x="414" y="30" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">Node2</text></switch></g><path d="M 348 188 Q 348 188 213.37 188" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 208.12 188 L 215.12 184.5 L 213.37 188 L 215.12 191.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 189px; margin-left: 281px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">net_snd(reply)</div></div></div></foreignObject><text x="281" y="192" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">net_snd(reply)</text></switch></g><path d="M 170 355.66 C 170 352.53 183.43 350 200 350 C 207.96 350 215.59 350.6 221.21 351.66 C 226.84 352.72 230 354.16 230 355.66 L 230 384.34 C 230 387.47 216.57 390 200 390 C 183.43 390 170 387.47 170 384.34 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 230 355.66 C 230 358.78 216.57 361.31 200 361.31 C 183.43 361.31 170 358.78 170 355.66" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 375px; margin-left: 171px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><span style="">Key-Value<br /></span>Pairs</div></div></div></foreignObject><text x="200" y="378" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Key-Value...</text></switch></g><path d="M 197 208 Q 197 208 90.42 208.38" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 85.17 208.4 L 92.16 204.87 L 90.42 208.38 L 92.18 211.87 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 209px; margin-left: 146px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">mesh_snd(reply)</div></div></div></foreignObject><text x="146" y="212" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">mesh_snd(reply)</text></switch></g><path d="M 323 355.66 C 323 352.53 336.43 350 353 350 C 360.96 350 368.59 350.6 374.21 351.66 C 379.84 352.72 383 354.16 383 355.66 L 383 384.34 C 383 387.47 369.57 390 353 390 C 336.43 390 323 387.47 323 384.34 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 383 355.66 C 383 358.78 369.57 361.31 353 361.31 C 336.43 361.31 323 358.78 323 355.66" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 375px; margin-left: 324px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><span style="">Key-Value</span><br style="" /><span style="">Pairs</span></div></div></div></foreignObject><text x="353" y="378" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Key-Value...</text></switch></g><rect x="170" y="390" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 405px; margin-left: 200px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">Sharded</div></div></div></foreignObject><text x="200" y="408" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Sharded</text></switch></g><rect x="321" y="390" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 405px; margin-left: 351px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">Sharded</div></div></div></foreignObject><text x="351" y="408" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Sharded</text></switch></g><path d="M 358 157.25 Q 358 157.25 455.63 157.48" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 460.88 157.5 L 453.87 160.98 L 455.63 157.48 L 453.89 153.98 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 156px; margin-left: 415px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">mesh_snd(cmd)</div></div></div></foreignObject><text x="415" y="159" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">mesh_snd(cmd)</text></switch></g><path d="M 462 177.5 Q 462 177.5 364.37 177.73" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 359.12 177.75 L 366.11 174.23 L 364.37 177.73 L 366.13 181.23 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 178px; margin-left: 416px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">mesh_snd(reply)</div></div></div></foreignObject><text x="416" y="181" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">mesh_snd(reply)</text></switch></g><path d="M 437 355.66 C 437 352.53 450.43 350 467 350 C 474.96 350 482.59 350.6 488.21 351.66 C 493.84 352.72 497 354.16 497 355.66 L 497 384.34 C 497 387.47 483.57 390 467 390 C 450.43 390 437 387.47 437 384.34 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 497 355.66 C 497 358.78 483.57 361.31 467 361.31 C 450.43 361.31 437 358.78 437 355.66" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 375px; margin-left: 438px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><span style="">Key-Value</span><br style="" />Pairs</div></div></div></foreignObject><text x="467" y="378" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Key-Value...</text></switch></g><rect x="435" y="390" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 405px; margin-left: 465px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">Sharded</div></div></div></foreignObject><text x="465" y="408" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">Sharded</text></switch></g><path d="M 85 222.94 Q 115 222.9 115 233 Q 115 243.1 93.22 243.07" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 86.22 243.06 L 93.22 239.57 L 93.21 246.57 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 233px; margin-left: 119px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: left;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">on_reply_cb()</div></div></div></foreignObject><text x="119" y="236" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px">on_reply_cb()</text></switch></g><rect x="0" y="146" width="80" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 161px; margin-left: 40px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">caller loop</div></div></div></foreignObject><text x="40" y="165" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">caller loop</text></switch></g><rect x="1" y="103" width="80" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 118px; margin-left: 41px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">exec(cmd)</div></div></div></foreignObject><text x="41" y="122" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">exec(cmd)</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg> \ No newline at end of file
diff --git a/include/swarmkv/swarmkv.h b/include/swarmkv/swarmkv.h
index a7c99b0..b869bf1 100644
--- a/include/swarmkv/swarmkv.h
+++ b/include/swarmkv/swarmkv.h
@@ -151,8 +151,8 @@ void swarmkv_ttl(struct swarmkv *db, const char *key, size_t keylen, swarmkv_on_
void swarmkv_persist(struct swarmkv *db, const char *key, size_t keylen, swarmkv_on_reply_callback_t *cb, void *cb_arg);
-void swarmkv_sadd(struct swarmkv *db, const char *key, size_t keylen, const char *member[], const size_t member_len[], size_t n_member, swarmkv_on_reply_callback_t *cb, void *cb_arg);
-void swarmkv_srem(struct swarmkv *db, const char *key, size_t keylen, const char *member[], const size_t member_len[], size_t n_member, swarmkv_on_reply_callback_t *cb, void *cb_arg);
+void swarmkv_sadd(struct swarmkv *db, const char *key, size_t keylen, const char *members[], const size_t members_len[], size_t n_members, swarmkv_on_reply_callback_t *cb, void *cb_arg);
+void swarmkv_srem(struct swarmkv *db, const char *key, size_t keylen, const char *members[], const size_t members_len[], size_t n_members, swarmkv_on_reply_callback_t *cb, void *cb_arg);
void swarmkv_sismember(struct swarmkv *db, const char *key, size_t keylen, const char *member, size_t member_len, swarmkv_on_reply_callback_t *cb, void *cb_arg);
void swarmkv_smembers(struct swarmkv *db, const char *key, size_t keylen, swarmkv_on_reply_callback_t *cb, void *cb_arg);
void swarmkv_scard(struct swarmkv *db, const char *key, size_t keylen, swarmkv_on_reply_callback_t *cb, void *cb_arg);
@@ -160,6 +160,10 @@ void swarmkv_scard(struct swarmkv *db, const char *key, size_t keylen, swarmkv_o
void swarmkv_tconsume(struct swarmkv * db, const char * key, size_t keylen, long long tokens, swarmkv_on_reply_callback_t *cb, void *cb_arg);
void swarmkv_ftconsume(struct swarmkv * db, const char * key, size_t keylen, const char * member, size_t member_len, long long weight, long long tokens, swarmkv_on_reply_callback_t *cb, void *cb_arg);
void swarmkv_btconsume(struct swarmkv * db, const char * key, size_t keylen, const char * member, size_t member_len, long long tokens, swarmkv_on_reply_callback_t *cb, void *cb_arg);
+
+void swarmkv_bfadd(struct swarmkv * db, const char * key, size_t keylen, const char *items[], const size_t items_len[], size_t n_items, swarmkv_on_reply_callback_t *cb, void *cb_arg);
+void swarmkv_bfexists(struct swarmkv * db, const char * key, size_t keylen, const char *items[], const size_t items_len[], size_t n_items, swarmkv_on_reply_callback_t *cb, void *cb_arg);
+
//Used by swarmkv-cli
size_t swarmkv_get_possible_command_name(struct swarmkv *db, const char *prefix, const char *cmd_names[], size_t sz);
char *swarmkv_get_command_hint(struct swarmkv *db, const char* cmd_name);
diff --git a/readme.md b/readme.md
index 4b20dc4..0b67a06 100644
--- a/readme.md
+++ b/readme.md
@@ -4,7 +4,7 @@
</h1>
-**SwarmKV is a embedded and distributed key-CRDT store with peer-to-peer networking .**
+**SwarmKV is an embedded and distributed key-CRDT store with peer-to-peer networking for sharing memory by communicating.**
Main Features
@@ -146,7 +146,7 @@ int main(int argc, char **argv)
# Further documentation
Here are some specific details about the SwarmKV.
-* [Commands](./docs/commands.md)
+* [Commands](./docs/command_toc.md)
* [Design](./docs/design.md)
* [Command-line interface (CLI)](./docs/cli.md)
* [Conflict-free Replicated Data Type (CRDT)](./docs/crdt.md)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 89b9f87..dbf5977 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,6 +1,6 @@
set(SWARMKV_MAJOR_VERSION 4)
-set(SWARMKV_MINOR_VERSION 0)
-set(SWARMKV_PATCH_VERSION 5)
+set(SWARMKV_MINOR_VERSION 1)
+set(SWARMKV_PATCH_VERSION 0)
set(SWARMKV_VERSION ${SWARMKV_MAJOR_VERSION}.${SWARMKV_MINOR_VERSION}.${SWARMKV_PATCH_VERSION})
message(STATUS "SwarmKV, Version: ${SWARMKV_VERSION}")
@@ -10,7 +10,7 @@ add_definitions(-fPIC)
set(SWARMKV_SRC swarmkv.c swarmkv_api.c
swarmkv_mesh.c swarmkv_rpc.c swarmkv_message.c swarmkv_net.c
swarmkv_store.c swarmkv_sync.c swarmkv_keyspace.c swarmkv_monitor.c
- t_string.c t_set.c t_token_bucket.c t_hash.c
+ t_string.c t_set.c t_token_bucket.c t_hash.c t_bloom_filter.c
swarmkv_common.c swarmkv_utils.c future_promise.c http_client.c)
set(LIB_SOURCE_FILES
diff --git a/src/swarmkv.c b/src/swarmkv.c
index 20f5d3d..0d77238 100644
--- a/src/swarmkv.c
+++ b/src/swarmkv.c
@@ -21,9 +21,9 @@
//header of data types
#include "t_string.h"
#include "t_set.h"
-#include "t_token_bucket.h"
#include "t_hash.h"
-
+#include "t_token_bucket.h"
+#include "t_bloom_filter.h"
#include "uthash.h"
@@ -1053,8 +1053,8 @@ void command_spec_init(struct swarmkv *db)
command_register(&(db->command_table), "DECR", "key",
1, 1, CMD_KEY_OW, REPLY_ERROR, AUTO_ROUTE,
decr_command, db->mod_store);
+
/* Generic commands*/
-
command_register(&(db->command_table), "DEL", "key",
1, 1, CMD_KEY_RM, REPLY_INT_0, AUTO_ROUTE,
del_command, db->mod_keyspace);
@@ -1118,7 +1118,7 @@ void command_spec_init(struct swarmkv *db)
hincrby_command, db->mod_store);
/* Token bucket commands */
- command_register(&(db->command_table), "TCFG", "key rate capacity",
+ command_register(&(db->command_table), "TCFG", "key rate capacity [PD seconds]",
3, 1, CMD_KEY_OW, REPLY_ERROR, AUTO_ROUTE,
tcfg_command, db->mod_store);
command_register(&(db->command_table), "TCONSUME", "key tokens [NORMAL|FORCE|FLEXIBLE]",
@@ -1127,7 +1127,7 @@ void command_spec_init(struct swarmkv *db)
command_register(&(db->command_table), "TINFO", "key",
1, 1, CMD_KEY_RO, REPLY_EMPTY_ARRAY, AUTO_ROUTE,
tinfo_command, db->mod_store);
- command_register(&(db->command_table), "FTCFG", "key rate capacity divisor",
+ command_register(&(db->command_table), "FTCFG", "key rate capacity divisor [PD seconds]",
4, 1, CMD_KEY_OW, REPLY_ERROR, AUTO_ROUTE,
ftcfg_command, db->mod_store);
command_register(&(db->command_table), "FTCONSUME", "key member weight tokens",
@@ -1136,7 +1136,7 @@ void command_spec_init(struct swarmkv *db)
command_register(&(db->command_table), "FTINFO", "key",
1, 1, CMD_KEY_RO, REPLY_EMPTY_ARRAY, AUTO_ROUTE,
ftinfo_command, db->mod_store);
- command_register(&(db->command_table), "BTCFG", "key rate capacity number-of-buckets",
+ command_register(&(db->command_table), "BTCFG", "key rate capacity number-of-buckets [PD seconds]",
4, 1, CMD_KEY_OW, REPLY_ERROR, AUTO_ROUTE,
btcfg_command, db->mod_store);
command_register(&(db->command_table), "BTCONSUME", "key member tokens [NORMAL|FORCE|FLEXIBLE]",
@@ -1146,6 +1146,26 @@ void command_spec_init(struct swarmkv *db)
1, 1, CMD_KEY_RO, REPLY_EMPTY_ARRAY, AUTO_ROUTE,
btinfo_command, db->mod_store);
+ /*Bloom filter commands*/
+ command_register(&(db->command_table), "BFRESERVE", "key error_rate capacity [TIME window-milliseconds slice-number]",
+ 3, 1, CMD_KEY_OW, REPLY_EMPTY_ARRAY, AUTO_ROUTE,
+ bfreserve_command, db->mod_store);
+ command_register(&(db->command_table), "BFADD", "key item [item ...]",
+ 2, 1, CMD_KEY_RW, REPLY_EMPTY_ARRAY, AUTO_ROUTE,
+ bfadd_command, db->mod_store);
+ command_register(&(db->command_table), "BFEXISTS", "key item",
+ 2, 1, CMD_KEY_RO, REPLY_INT_0, AUTO_ROUTE,
+ bfexists_command, db->mod_store);
+ command_register(&(db->command_table), "BFMEXISTS", "key item [item ...]",
+ 2, 1, CMD_KEY_RO, REPLY_EMPTY_ARRAY, AUTO_ROUTE,
+ bfmexists_command, db->mod_store);
+ command_register(&(db->command_table), "BFCARD", "key",
+ 1, 1, CMD_KEY_RO, REPLY_INT_0, AUTO_ROUTE,
+ bfcard_command, db->mod_store);
+ command_register(&(db->command_table), "BFINFO", "key",
+ 1, 1, CMD_KEY_RO, REPLY_EMPTY_ARRAY, AUTO_ROUTE,
+ bfinfo_command, db->mod_store);
+
/* Debug Commands */
command_register(&(db->command_table), "INFO", "[section]",
0, KEY_OFFSET_NONE, CMD_KEY_NA, REPLY_NA, AUTO_ROUTE,
@@ -1159,7 +1179,6 @@ void command_spec_init(struct swarmkv *db)
command_register(&(db->command_table), "COMMAND LIST", "",
0, KEY_OFFSET_NONE, CMD_KEY_NA, REPLY_NA, AUTO_ROUTE,
command_list_command, &db->module);
-
command_register(&(db->command_table), "LATENCY", "<subcommand>",
1, KEY_OFFSET_NONE, CMD_KEY_NA, REPLY_NA, NOT_AUTO_ROUTE,
latency_command, db->mod_monitor);
@@ -1197,17 +1216,17 @@ void command_spec_init(struct swarmkv *db)
command_register(&(db->command_table), "KEYSPACE RLIST", "key",
1, 2, CMD_KEY_RO, REPLY_NA, AUTO_ROUTE,
keyspace_rlist_command, db->mod_keyspace);
- command_register(&(db->command_table), "KEYSPACE RADD", "key IP:port",
+ command_register(&(db->command_table), "KEYSPACE RADD", "key [IP:port]",
1, 2, CMD_KEY_OW, REPLY_NA, AUTO_ROUTE,
keyspace_radd_command, db->mod_keyspace);
command_register(&(db->command_table), "KEYSPACE XRADD", "key IP:port",
- 1, 2, CMD_KEY_OW, REPLY_NA, AUTO_ROUTE,
+ 2, 2, CMD_KEY_OW, REPLY_NA, AUTO_ROUTE,
keyspace_xradd_command, db->mod_keyspace);
command_register(&(db->command_table), "KEYSPACE KEYS", "tid pattern",//worker-thread-id
- 1, KEY_OFFSET_TID, CMD_KEY_RO, REPLY_NA, NOT_AUTO_ROUTE,
+ 2, KEY_OFFSET_TID, CMD_KEY_RO, REPLY_NA, NOT_AUTO_ROUTE,
keyspace_keys_command, db->mod_keyspace);
command_register(&(db->command_table), "KEYSPACE RDEL", "key IP:port",
- 1, 2, CMD_KEY_RW, REPLY_NA, AUTO_ROUTE,
+ 2, 2, CMD_KEY_RW, REPLY_NA, AUTO_ROUTE,
keyspace_rdel_command, db->mod_keyspace);
command_register(&(db->command_table), "KEYSPACE EXISTS", "key",
1, 2, CMD_KEY_RO, REPLY_NA, AUTO_ROUTE,
@@ -1215,7 +1234,7 @@ void command_spec_init(struct swarmkv *db)
/* low-level keyspace reorgnization commands */
command_register(&(db->command_table), "KEYSPACE SETSLOT", "slot IMPORTING|MIGRATING|NODE|STABLE IP:port",
- 2, KEY_OFFSET_SLOTID, CMD_KEY_NA, REPLY_NA, NOT_AUTO_ROUTE,
+ 3, KEY_OFFSET_SLOTID, CMD_KEY_NA, REPLY_NA, NOT_AUTO_ROUTE,
keyspace_setslot_command, db->mod_keyspace);
command_register(&(db->command_table), "KEYSPACE GETKEYSINSLOT", "slot",
1, KEY_OFFSET_SLOTID, CMD_KEY_RO, REPLY_NA, NOT_AUTO_ROUTE,
diff --git a/src/swarmkv_api.c b/src/swarmkv_api.c
index 97e0552..f408274 100644
--- a/src/swarmkv_api.c
+++ b/src/swarmkv_api.c
@@ -218,34 +218,60 @@ void swarmkv_persist(struct swarmkv *db, const char *key, size_t keylen, swarmkv
swarmkv_cmd_free(cmd);
return;
}
-void swarmkv_sadd(struct swarmkv *db, const char* key, size_t keylen, const char *member[], const size_t member_len[], size_t n_member, swarmkv_on_reply_callback_t *cb, void *cb_arg)
+void swarmkv_sadd(struct swarmkv *db, const char* key, size_t keylen, const char *members[], const size_t members_len[], size_t n_members, swarmkv_on_reply_callback_t *cb, void *cb_arg)
{
struct swarmkv_cmd *cmd=NULL;
- cmd=swarmkv_cmd_new(2+n_member);
+ cmd=swarmkv_cmd_new(2+n_members);
cmd->argv[0]=sdsnew("sadd");
cmd->argv[1]=sdsnewlen(key, keylen);
- for(size_t i=0; i<n_member; i++)
+ for(size_t i=0; i<n_members; i++)
{
- cmd->argv[2+i]=sdsnewlen(member[i], member_len[i]);
+ cmd->argv[2+i]=sdsnewlen(members[i], members_len[i]);
}
exec_for_local(db, cmd, NULL, cb, cb_arg);
swarmkv_cmd_free(cmd);
}
-void swarmkv_srem(struct swarmkv *db, const char* key, size_t keylen, const char *member[], const size_t member_len[], size_t n_member, swarmkv_on_reply_callback_t *cb, void *cb_arg)
+void swarmkv_srem(struct swarmkv *db, const char* key, size_t keylen, const char *members[], const size_t members_len[], size_t n_members, swarmkv_on_reply_callback_t *cb, void *cb_arg)
{
struct swarmkv_cmd *cmd=NULL;
- cmd=swarmkv_cmd_new(2+n_member);
+ cmd=swarmkv_cmd_new(2+n_members);
cmd->argv[0]=sdsnew("srem");
cmd->argv[1]=sdsnewlen(key, keylen);
- for(size_t i=0; i<n_member; i++)
+ for(size_t i=0; i<n_members; i++)
{
- cmd->argv[2+i]=sdsnewlen(member[i], member_len[i]);
+ cmd->argv[2+i]=sdsnewlen(members[i], members_len[i]);
}
exec_for_local(db, cmd, NULL, cb, cb_arg);
swarmkv_cmd_free(cmd);
}
+void swarmkv_bfadd(struct swarmkv * db, const char * key, size_t keylen, const char *items[], const size_t items_len[], size_t n_items, swarmkv_on_reply_callback_t *cb, void *cb_arg)
+{
+ struct swarmkv_cmd *cmd=NULL;
+ cmd=swarmkv_cmd_new(2+n_items);
+ cmd->argv[0]=sdsnew("bfadd");
+ cmd->argv[1]=sdsnewlen(key, keylen);
+ for(size_t i=0; i<n_items; i++)
+ {
+ cmd->argv[2+i]=sdsnewlen(items[i], items_len[i]);
+ }
+ exec_for_local(db, cmd, NULL, cb, cb_arg);
+ swarmkv_cmd_free(cmd);
+}
+void swarmkv_bfexists(struct swarmkv * db, const char * key, size_t keylen, const char *items[], const size_t items_len[], size_t n_items, swarmkv_on_reply_callback_t *cb, void *cb_arg)
+{
+ struct swarmkv_cmd *cmd=NULL;
+ cmd=swarmkv_cmd_new(2+n_items);
+ cmd->argv[0]=sdsnew("bfexists");
+ cmd->argv[1]=sdsnewlen(key, keylen);
+ for(size_t i=0; i<n_items; i++)
+ {
+ cmd->argv[2+i]=sdsnewlen(items[i], items_len[i]);
+ }
+ exec_for_local(db, cmd, NULL, cb, cb_arg);
+ swarmkv_cmd_free(cmd);
+}
void swarmkv_sismember(struct swarmkv *db, const char* key, size_t keylen, const char *member, size_t member_len, swarmkv_on_reply_callback_t *cb, void *cb_arg)
{
struct swarmkv_cmd *cmd=NULL;
diff --git a/src/swarmkv_common.c b/src/swarmkv_common.c
index 649a5dd..faa1a04 100644
--- a/src/swarmkv_common.c
+++ b/src/swarmkv_common.c
@@ -454,6 +454,9 @@ struct swarmkv_reply *swarmkv_reply_dup(const struct swarmkv_reply *origin)
case SWARMKV_REPLY_INTEGER:
copy->integer=origin->integer;
break;
+ case SWARMKV_REPLY_DOUBLE:
+ copy->dval=origin->dval;
+ break;
case SWARMKV_REPLY_STRING:
case SWARMKV_REPLY_STATUS:
case SWARMKV_REPLY_ERROR:
@@ -799,3 +802,48 @@ char *str_replace(char *orig, char *rep, char *with)
strcpy(tmp, orig);
return result;
}
+int swarmkv_cmd_parse_integer(const struct swarmkv_cmd *cmd, const char *name, long long *integer)
+{
+ if(cmd->argc<2)
+ {
+ return -1;
+ }
+ for(int i=0; i<cmd->argc; i++)
+ {
+ if(0==strcasecmp(cmd->argv[i], name))
+ {
+ if(i+1<cmd->argc)
+ {
+ return str2integer(cmd->argv[i+1], integer);
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ }
+ return -1;
+}
+int swarmkv_cmd_parse_double(const struct swarmkv_cmd *cmd, const char *name, double *dval)
+{
+ if(cmd->argc<2)
+ {
+ return -1;
+ }
+ for(int i=0; i<cmd->argc; i++)
+ {
+ if(0==strcasecmp(cmd->argv[i], name))
+ {
+ if(i+1<cmd->argc)
+ {
+ *dval=strtod(cmd->argv[i+1], NULL);
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ }
+ return -1;
+} \ No newline at end of file
diff --git a/src/swarmkv_common.h b/src/swarmkv_common.h
index 1e3baae..f16edf5 100644
--- a/src/swarmkv_common.h
+++ b/src/swarmkv_common.h
@@ -14,7 +14,7 @@
#include <event2/buffer.h>
#include <event2/thread.h>
#include <event2/http.h>
-#define SWARMKV_VERSION "4.0.5"
+#define SWARMKV_VERSION "4.1.0"
enum cmd_exec_result
{
NEED_KEY_ROUTE,
@@ -93,6 +93,8 @@ struct swarmkv_cmd
struct swarmkv_cmd *swarmkv_cmd_new(size_t argc);
void swarmkv_cmd_free(struct swarmkv_cmd *p);
struct swarmkv_cmd *swarmkv_cmd_dup(const struct swarmkv_cmd *origin);
+int swarmkv_cmd_parse_integer(const struct swarmkv_cmd *cmd, const char *name, long long *ival);
+int swarmkv_cmd_parse_double(const struct swarmkv_cmd *cmd, const char *name, double *dval);
struct swarmkv_reply *swarmkv_reply_new_string(const char *str, size_t sz);
struct swarmkv_reply *swarmkv_reply_new_string_fmt(const char *format, ...);
diff --git a/src/swarmkv_error.h b/src/swarmkv_error.h
index 55eea7c..4da62e0 100644
--- a/src/swarmkv_error.h
+++ b/src/swarmkv_error.h
@@ -7,10 +7,11 @@
#define error_value_not_integer "ERR value is not an integer or out of range"
#define error_arg_not_valid_integer "ERR arg `%s` is not an integer or out of range"
#define error_arg_not_valid_address "ERR arg `%s` is not `IP:port` format"
+#define error_arg_not_valid_float "ERR arg `%s` is not a valid float or out of range"
+#define error_arg_parse_failed "ERR arg `%s` parse failed"
#define error_arg_string_should_be "ERR arg `%s` should be `%s`"
#define error_need_additional_arg "ERR arg `%s` should be fllowed by more args"
#define erorr_subcommand_syntax "ERR unknown subcommand or wrong number of arguments for '%.128s'. Try %s HELP."
-#define error_tunnel_busy "ERR node is busy tunneling for %s"
#define error_too_many_redirects "ERR too many redirects, the lastest is `%s`"
#define error_cluster_leader_not_found "ERR cluster leader not found"
diff --git a/src/swarmkv_keyspace.c b/src/swarmkv_keyspace.c
index b86d5c9..d7abec7 100644
--- a/src/swarmkv_keyspace.c
+++ b/src/swarmkv_keyspace.c
@@ -1658,7 +1658,10 @@ enum cmd_exec_result keyspace_keys_command(struct swarmkv_module *mod_keyspace,
{
continue;
}
- if(!slot_is_my_thread(i, thread_id, ks->opts->nr_worker_threads)) continue;
+ if(!slot_is_my_thread(i, thread_id, ks->opts->nr_worker_threads))
+ {
+ continue;
+ }
HASH_ITER(hh, slot_rt->keyroute_table, key_entry, tmp)
{
is_matched=stringmatchlen(pattern, sdslen(pattern), key_entry->key, sdslen(key_entry->key), 0);
diff --git a/src/swarmkv_store.c b/src/swarmkv_store.c
index 48761be..0bc7514 100644
--- a/src/swarmkv_store.c
+++ b/src/swarmkv_store.c
@@ -102,6 +102,15 @@ struct swarmkv_obj_specs sobj_specs[__SWARMKV_OBJ_TYPE_MAX] =
.obj_size=(size_t (*)(const void *))bulk_token_bucket_mem_size
},
{
+ .type=OBJ_TYPE_BLOOM_FILTER,
+ .type_name="bloom-filter",
+ .obj_free=(void (*)(void *))AP_bloom_free,
+ .obj_serialize=(void (*)(const void *, char **, size_t *))AP_bloom_serialize,
+ .obj_merge_blob=(void (*)(void *, const char *, size_t))AP_bloom_merge_blob,
+ .obj_replicate=(void * (*)(uuid_t, const char *, size_t))AP_bloom_replicate,
+ .obj_size=(size_t (*)(const void *))AP_bloom_mem_size
+ },
+ {
.type=OBJ_TYPE_UNDEFINED,
.type_name="undefined",
.obj_free=undefined_obj_free,
diff --git a/src/swarmkv_store.h b/src/swarmkv_store.h
index 29962e1..d1fc926 100644
--- a/src/swarmkv_store.h
+++ b/src/swarmkv_store.h
@@ -13,6 +13,7 @@
#include "oc_token_bucket.h"
#include "fair_token_bucket.h"
#include "bulk_token_bucket.h"
+#include "ap_bloom.h"
enum sobj_type
{
@@ -23,6 +24,7 @@ enum sobj_type
OBJ_TYPE_TOKEN_BUCKET,
OBJ_TYPE_FAIR_TOKEN_BUCKET,
OBJ_TYPE_BULK_TOKEN_BUCKET,
+ OBJ_TYPE_BLOOM_FILTER,
OBJ_TYPE_UNDEFINED,
__SWARMKV_OBJ_TYPE_MAX
};
@@ -40,6 +42,7 @@ struct sobj
struct OC_token_bucket *bucket;
struct fair_token_bucket *ftb;
struct bulk_token_bucket *btb;
+ struct AP_bloom *bloom;
void *raw;
};
};
diff --git a/src/t_bloom_filter.c b/src/t_bloom_filter.c
new file mode 100644
index 0000000..fbaad2e
--- /dev/null
+++ b/src/t_bloom_filter.c
@@ -0,0 +1,227 @@
+#include "swarmkv_common.h"
+#include "swarmkv_utils.h"
+#include "swarmkv_store.h"
+#include "swarmkv_error.h"
+#include "ap_bloom.h"
+
+#include <stdlib.h>
+#include <assert.h>
+
+enum cmd_exec_result bfreserve_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
+{
+/* BFRESERVE key error_rate capacity [TIME window-milliseconds slice-number] */
+ struct sobj *obj=NULL;
+ const sds key=cmd->argv[1];
+
+ double error_rate=0;
+ long long capacity=0, time_window_ms=0, time_slice_num=0;
+
+ int ret=0;
+ error_rate=strtod(cmd->argv[2], NULL);
+ if(error_rate < 0 || error_rate >= 1.0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_not_valid_float, cmd->argv[2]);
+ return FINISHED;
+ }
+ ret=str2integer(cmd->argv[3], &capacity);
+ if(ret<0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_not_valid_integer, cmd->argv[3]);
+ }
+ if(cmd->argc==7)
+ {
+ if(strncasecmp(cmd->argv[4], "TIME", 4)!=0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_string_should_be, cmd->argv[4], "TIME");
+ return FINISHED;
+ }
+ ret=str2integer(cmd->argv[5], &time_window_ms);
+ if(ret<0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_not_valid_integer, cmd->argv[5]);
+ return FINISHED;
+ }
+ ret=str2integer(cmd->argv[6], &time_slice_num);
+ if(ret<0 || time_slice_num<0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_not_valid_integer, cmd->argv[6]);
+ return FINISHED;
+ }
+ }
+ obj=store_lookup(mod_store, key);
+ if(!obj)
+ {
+ return NEED_KEY_ROUTE;
+ }
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ if(obj->type==OBJ_TYPE_UNDEFINED)
+ {
+ assert(obj->raw==NULL);
+ obj->bloom=AP_bloom_new(now, error_rate, capacity, time_window_ms, time_slice_num);
+ obj->type=OBJ_TYPE_BLOOM_FILTER;
+ *reply=swarmkv_reply_new_status("OK");
+ }
+ else
+ {
+ *reply=swarmkv_reply_new_array(0);
+ }
+ return FINISHED;
+}
+enum cmd_exec_result bfadd_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
+{
+/*BFADD key item [item ...]*/
+ struct sobj *obj=NULL;
+ const sds key=cmd->argv[1];
+
+ obj=store_lookup(mod_store, key);
+ if(!obj)
+ {
+ return NEED_KEY_ROUTE;
+ }
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ if(obj->type==OBJ_TYPE_UNDEFINED)
+ {
+ return handle_undefined_object(obj, reply);
+ }
+ else if(obj->type!=OBJ_TYPE_BLOOM_FILTER)
+ {
+ *reply=swarmkv_reply_new_error(error_wrong_type);
+ return FINISHED;
+ }
+
+ for(int i=0; i<cmd->argc-2; i++)
+ {
+ AP_bloom_add(obj->bloom, now, cmd->argv[i+2], sdslen(cmd->argv[i+2]));
+ }
+ *reply=swarmkv_reply_new_status("OK");
+ return FINISHED;
+}
+enum cmd_exec_result bfmexists_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
+{
+/*BFMEXISTS key item [item ...]*/
+ struct sobj *obj=NULL;
+ const sds key=cmd->argv[1];
+ obj=store_lookup(mod_store, key);
+ if(!obj)
+ {
+ return NEED_KEY_ROUTE;
+ }
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ if(obj->type==OBJ_TYPE_UNDEFINED)
+ {
+ return handle_undefined_object(obj, reply);
+ }
+ else if(obj->type!=OBJ_TYPE_BLOOM_FILTER)
+ {
+ *reply=swarmkv_reply_new_error(error_wrong_type);
+ return FINISHED;
+ }
+
+ long long exists=0;
+ *reply=swarmkv_reply_new_array(cmd->argc-2);
+ for(int i=0; i<cmd->argc-2; i++)
+ {
+ exists = AP_bloom_check(obj->bloom, now, cmd->argv[i+2], sdslen(cmd->argv[i+2]));
+ (*reply)->elements[i]=swarmkv_reply_new_integer(exists);
+ }
+ return FINISHED;
+}
+enum cmd_exec_result bfexists_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
+{
+ enum cmd_exec_result ret;
+ struct swarmkv_reply *tmp_reply=NULL;
+ ret=bfmexists_command(mod_store, cmd, &tmp_reply);
+ if(ret==FINISHED)
+ {
+ if(tmp_reply->type==SWARMKV_REPLY_ARRAY)
+ {
+ *reply=swarmkv_reply_dup(tmp_reply->elements[0]);
+ swarmkv_reply_free(tmp_reply);
+ }
+ else
+ {
+ *reply=tmp_reply;
+ }
+ }
+ return ret;
+}
+enum cmd_exec_result bfcard_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
+{
+/*BFCARD key*/
+ struct sobj *obj=NULL;
+ const sds key=cmd->argv[1];
+ obj=store_lookup(mod_store, key);
+ if(!obj)
+ {
+ return NEED_KEY_ROUTE;
+ }
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ if(obj->type==OBJ_TYPE_UNDEFINED)
+ {
+ return handle_undefined_object(obj, reply);
+ }
+ else if(obj->type!=OBJ_TYPE_BLOOM_FILTER)
+ {
+ *reply=swarmkv_reply_new_error(error_wrong_type);
+ return FINISHED;
+ }
+
+ long long cardinality=0;
+ cardinality=AP_bloom_cardinality(obj->bloom);
+ *reply=swarmkv_reply_new_integer(cardinality);
+ return FINISHED;
+}
+enum cmd_exec_result bfinfo_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
+{
+/*BFINFO key*/
+ struct sobj *obj=NULL;
+ const sds key=cmd->argv[1];
+ obj=store_lookup(mod_store, key);
+ if(!obj)
+ {
+ return NEED_KEY_ROUTE;
+ }
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ if(obj->type==OBJ_TYPE_UNDEFINED)
+ {
+ return handle_undefined_object(obj, reply);
+ }
+ else if(obj->type!=OBJ_TYPE_BLOOM_FILTER)
+ {
+ *reply=swarmkv_reply_new_error(error_wrong_type);
+ return FINISHED;
+ }
+ struct AP_bloom_info info;
+ AP_bloom_info(obj->bloom, &info);
+ int i=0;
+ *reply=swarmkv_reply_new_array(20);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("ErrorRate");
+ (*reply)->elements[i++]=swarmkv_reply_new_double(info.error);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Capacity");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(info.capacity);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("TimeWindowMs");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(info.time_window_ms);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("TimeSlices");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(info.time_slice_num);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("HashNum");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(info.hash_num);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("TotalSlices");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(info.total_slice_number);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("MaxExpansionTimes");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(info.max_expand_times);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("ApproximateItemNum");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(info.approximate_item_num);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("FillRatio");
+ (*reply)->elements[i++]=swarmkv_reply_new_double(info.fill_ratio);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("OldestItemTime");
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("%lld.%03lld", info.oldest_item_time.tv_sec, info.oldest_item_time.tv_usec/1000);
+ assert(i==20);
+ return FINISHED;
+} \ No newline at end of file
diff --git a/src/t_bloom_filter.h b/src/t_bloom_filter.h
new file mode 100644
index 0000000..0231b0c
--- /dev/null
+++ b/src/t_bloom_filter.h
@@ -0,0 +1,9 @@
+#pragma once
+#include "swarmkv_common.h"
+
+enum cmd_exec_result bfreserve_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply);
+enum cmd_exec_result bfadd_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply);
+enum cmd_exec_result bfexists_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply);
+enum cmd_exec_result bfmexists_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply);
+enum cmd_exec_result bfcard_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply);
+enum cmd_exec_result bfinfo_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply);
diff --git a/src/t_token_bucket.c b/src/t_token_bucket.c
index ab77653..eaedbf2 100644
--- a/src/t_token_bucket.c
+++ b/src/t_token_bucket.c
@@ -31,13 +31,13 @@ static int get_consume_type(sds s, enum tb_consume_type *consume_type)
}
enum cmd_exec_result tcfg_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
{
-/*TCFG key rate capacity*/
+/*TCFG key rate capacity [PD seconds]*/
struct sobj *obj=NULL;
const sds key=cmd->argv[1];
char *endptr=NULL;
- long long rate=0, capacity=0;
+ long long rate=0, capacity=0, period=1;
rate=strtol(cmd->argv[2], &endptr, 10);
if(*endptr!='\0' || rate<0)
{
@@ -51,7 +51,20 @@ enum cmd_exec_result tcfg_command(struct swarmkv_module *mod_store, const struct
*reply=swarmkv_reply_new_error(error_arg_not_valid_integer, cmd->argv[3]);
return FINISHED;
}
-
+ if(cmd->argc==6)
+ {
+ if(strcasecmp(cmd->argv[4], "PD")!=0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_string_should_be, cmd->argv[5], "PD");
+ return FINISHED;
+ }
+ period=strtol(cmd->argv[5], &endptr, 10);
+ if(*endptr!='\0' || period<0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_not_valid_integer, cmd->argv[5]);
+ return FINISHED;
+ }
+ }
obj=store_lookup(mod_store, key);
if(!obj)
{
@@ -65,13 +78,13 @@ enum cmd_exec_result tcfg_command(struct swarmkv_module *mod_store, const struct
uuid_t uuid;
assert(obj->raw==NULL);
store_get_uuid(mod_store, uuid);
- obj->bucket=OC_token_bucket_new(uuid, now, rate, capacity);
+ obj->bucket=OC_token_bucket_new(uuid, now, rate, period, capacity);
obj->type=OBJ_TYPE_TOKEN_BUCKET;
*reply=swarmkv_reply_new_status("OK");
}
else if(obj->type==OBJ_TYPE_TOKEN_BUCKET)
{
- OC_token_bucket_configure(obj->bucket, now, rate, capacity);
+ OC_token_bucket_configure(obj->bucket, now, rate, period, capacity);
sobj_need_sync(mod_store, obj);
*reply=swarmkv_reply_new_status("OK");
}
@@ -110,18 +123,20 @@ enum cmd_exec_result tinfo_command(struct swarmkv_module *mod_store, const struc
memset(&oc_info, 0, sizeof(oc_info));
OC_token_bucket_info(obj->bucket, now, &oc_info);
int i=0;
- *reply=swarmkv_reply_new_array(10);
+ *reply=swarmkv_reply_new_array(12);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Rate");
- (*reply)->elements[i++]=swarmkv_reply_new_integer(oc_info.CIR);
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(oc_info.rate);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Period");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(oc_info.period);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Capacity");
- (*reply)->elements[i++]=swarmkv_reply_new_integer(oc_info.CBS);
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(oc_info.capacity);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Consumed");
(*reply)->elements[i++]=swarmkv_reply_new_integer(oc_info.consumed);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Refilled");
(*reply)->elements[i++]=swarmkv_reply_new_integer(oc_info.refilled);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Available");
(*reply)->elements[i++]=swarmkv_reply_new_integer(oc_info.available);
- assert(i==10);
+ assert(i==12);
return FINISHED;
}
enum cmd_exec_result tconsume_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
@@ -160,7 +175,7 @@ enum cmd_exec_result tconsume_command(struct swarmkv_module *mod_store, const st
if(obj->type!=OBJ_TYPE_TOKEN_BUCKET)
{
- *reply=swarmkv_reply_new_error(error_wrong_type);
+ *reply=swarmkv_reply_new_error(error_wrong_type);
return FINISHED;
}
struct timeval now;
@@ -179,13 +194,13 @@ bool is_power_of_2(long long num)
}
enum cmd_exec_result ftcfg_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
{
-/*FTCFG key rate capacity divisor*/
+/*FTCFG key rate capacity divisor [PERIOD seconds]*/
struct sobj *obj=NULL;
const sds key=cmd->argv[1];
char *endptr=NULL;
- long long rate=0, capacity=0, divisor=0;
+ long long rate=0, capacity=0, divisor=0, period=1;
rate=strtol(cmd->argv[2], &endptr, 10);
if(*endptr!='\0' || rate<0)
{
@@ -204,6 +219,20 @@ enum cmd_exec_result ftcfg_command(struct swarmkv_module *mod_store, const struc
*reply=swarmkv_reply_new_error(error_arg_not_valid_integer, cmd->argv[4]);
return FINISHED;
}
+ if(cmd->argc==7)
+ {
+ if(strcasecmp(cmd->argv[5], "PD")!=0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_string_should_be, cmd->argv[5], "PD");
+ return FINISHED;
+ }
+ period=strtol(cmd->argv[6], &endptr, 10);
+ if(*endptr!='\0' || period<0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_not_valid_integer, cmd->argv[6]);
+ return FINISHED;
+ }
+ }
obj=store_lookup(mod_store, key);
if(!obj)
{
@@ -219,12 +248,12 @@ enum cmd_exec_result ftcfg_command(struct swarmkv_module *mod_store, const struc
store_get_uuid(mod_store, uuid);
obj->type=OBJ_TYPE_FAIR_TOKEN_BUCKET;
- obj->ftb=fair_token_bucket_new(uuid, now, rate, capacity, divisor);
+ obj->ftb=fair_token_bucket_new(uuid, now, rate, period, capacity, divisor);
*reply=swarmkv_reply_new_status("OK");
}
else if(obj->type==OBJ_TYPE_FAIR_TOKEN_BUCKET)
{
- fair_token_bucket_configure(obj->ftb, now, rate, capacity, divisor);
+ fair_token_bucket_configure(obj->ftb, now, rate, period, capacity, divisor);
sobj_need_sync(mod_store, obj);
*reply=swarmkv_reply_new_status("OK");
}
@@ -236,7 +265,7 @@ enum cmd_exec_result ftcfg_command(struct swarmkv_module *mod_store, const struc
}
enum cmd_exec_result ftconsume_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
{
-/*FTCONSUME key member weight tokens*/
+/*FTCONSUME key member weight tokens*/
struct sobj *obj=NULL;
const sds key=cmd->argv[1];
const sds member=cmd->argv[2];
@@ -306,11 +335,13 @@ enum cmd_exec_result ftinfo_command(struct swarmkv_module *mod_store, const stru
fair_token_bucket_info(obj->ftb, now, &ftb_info);
int i=0;
- *reply=swarmkv_reply_new_array(14);
- (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Rate");
- (*reply)->elements[i++]=swarmkv_reply_new_integer(ftb_info.bucket_info.CIR);
+ *reply=swarmkv_reply_new_array(16);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Refill");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(ftb_info.bucket_info.rate);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Period");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(ftb_info.bucket_info.period);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Capacity");
- (*reply)->elements[i++]=swarmkv_reply_new_integer(ftb_info.bucket_info.CBS);
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(ftb_info.bucket_info.capacity);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Consumed");
(*reply)->elements[i++]=swarmkv_reply_new_integer(ftb_info.bucket_info.consumed);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Refilled");
@@ -321,18 +352,18 @@ enum cmd_exec_result ftinfo_command(struct swarmkv_module *mod_store, const stru
(*reply)->elements[i++]=swarmkv_reply_new_integer(ftb_info.divisor);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("ActiveMembers");
(*reply)->elements[i++]=swarmkv_reply_new_integer(ftb_info.active_key_number);
- assert(i==14);
+ assert(i==16);
return FINISHED;
}
enum cmd_exec_result btcfg_command(struct swarmkv_module *mod_store, const struct swarmkv_cmd *cmd, struct swarmkv_reply **reply)
{
-/*BTCFG key rate capacity buckets*/
+/*BTCFG key rate capacity buckets [PD seconds]*/
struct sobj *obj=NULL;
const sds key=cmd->argv[1];
char *endptr=NULL;
- long long rate=0, capacity=0, buckets=0;
+ long long rate=0, capacity=0, buckets=0, period=1;
rate=strtol(cmd->argv[2], &endptr, 10);
if(*endptr!='\0' || rate<0)
{
@@ -351,6 +382,20 @@ enum cmd_exec_result btcfg_command(struct swarmkv_module *mod_store, const struc
*reply=swarmkv_reply_new_error(error_arg_not_valid_integer, cmd->argv[4]);
return FINISHED;
}
+ if(cmd->argc==7)
+ {
+ if(strcasecmp(cmd->argv[5], "PD")!=0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_string_should_be, cmd->argv[5], "PD");
+ return FINISHED;
+ }
+ period=strtol(cmd->argv[6], &endptr, 10);
+ if(*endptr!='\0' || period<0)
+ {
+ *reply=swarmkv_reply_new_error(error_arg_not_valid_integer, cmd->argv[6]);
+ return FINISHED;
+ }
+ }
obj=store_lookup(mod_store, key);
if(!obj)
{
@@ -366,12 +411,12 @@ enum cmd_exec_result btcfg_command(struct swarmkv_module *mod_store, const struc
store_get_uuid(mod_store, uuid);
obj->type=OBJ_TYPE_BULK_TOKEN_BUCKET;
- obj->btb=bulk_token_bucket_new(uuid, now, rate, capacity, buckets);
+ obj->btb=bulk_token_bucket_new(uuid, now, rate, period, capacity, buckets);
*reply=swarmkv_reply_new_status("OK");
}
else if(obj->type==OBJ_TYPE_BULK_TOKEN_BUCKET)
{
- bulk_token_bucket_configure(obj->btb, now, rate, capacity, buckets);
+ bulk_token_bucket_configure(obj->btb, now, rate, period, capacity, buckets);
sobj_need_sync(mod_store, obj);
*reply=swarmkv_reply_new_status("OK");
}
@@ -461,19 +506,21 @@ enum cmd_exec_result btinfo_command(struct swarmkv_module *mod_store, const stru
available=bulk_token_bucket_read_available(obj->btb, now, cmd->argv[2], sdslen(cmd->argv[2]));
}
int i=0;
- *reply=swarmkv_reply_new_array(12);
- (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Rate");
- (*reply)->elements[i++]=swarmkv_reply_new_integer(btb_info.CIR);
+ *reply=swarmkv_reply_new_array(14);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Refill");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(btb_info.rate);
+ (*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Period");
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(btb_info.period);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Capacity");
- (*reply)->elements[i++]=swarmkv_reply_new_integer(btb_info.CBS);
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(btb_info.capacity);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Buckets");
(*reply)->elements[i++]=swarmkv_reply_new_integer(btb_info.bucket_number);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("ActiveMembers");
- (*reply)->elements[i++]=swarmkv_reply_new_integer(btb_info.estimate_keys);
+ (*reply)->elements[i++]=swarmkv_reply_new_integer(btb_info.approximate_keys);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Collisions");
(*reply)->elements[i++]=swarmkv_reply_new_double(btb_info.collision_rate);
(*reply)->elements[i++]=swarmkv_reply_new_string_fmt("Query");
(*reply)->elements[i++]=swarmkv_reply_new_integer(available);
- assert(i==12);
+ assert(i==14);
return FINISHED;
} \ No newline at end of file
diff --git a/test/swarmkv_gtest.cpp b/test/swarmkv_gtest.cpp
index aa96902..ad6d94e 100644
--- a/test/swarmkv_gtest.cpp
+++ b/test/swarmkv_gtest.cpp
@@ -282,6 +282,80 @@ TEST_F(SwarmkvBasicTest, TypeSet)
swarmkv_reply_free(reply);
}
+TEST_F(SwarmkvBasicTest, TypeHash)
+{
+ struct swarmkv *db=SwarmkvBasicTest::db;
+ const char *key="uid001";
+ struct swarmkv_reply *reply=NULL;
+
+ reply=swarmkv_command(db, "HSET %s name zhangsan gender male age 18 gender male", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->integer, 3);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "HGET %s name", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STRING);
+ EXPECT_STREQ(reply->str, "zhangsan");
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "HMGET %s name gender not-exist", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
+ ASSERT_EQ(reply->n_element, 3);
+ EXPECT_STREQ(reply->elements[0]->str, "zhangsan");
+ EXPECT_STREQ(reply->elements[1]->str, "male");
+ EXPECT_EQ(reply->elements[2]->type, SWARMKV_REPLY_NIL);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "HINCRBY %s age 1", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->integer, 19);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "HGET %s age", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STRING);
+ EXPECT_STREQ(reply->str, "19");
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "HKEYS %s", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
+ ASSERT_EQ(reply->n_element, 3);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "HGETALL %s", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
+ ASSERT_EQ(reply->n_element, 6);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "HDEL %s gender", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
+ ASSERT_EQ(reply->integer, 1);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "HLEN %s", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
+ ASSERT_EQ(reply->integer, 2);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "DEL %s", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
+ ASSERT_EQ(reply->integer, 1);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "HGET %s name", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_NIL);
+ swarmkv_reply_free(reply);
+
+ const char *key2="uid002";
+ reply=swarmkv_command(db, "HINCRBY %s age 30", key2);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->integer, 30);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "HINCRBY %s age -1", key2);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->integer, 30-1);
+ swarmkv_reply_free(reply);
+}
TEST_F(SwarmkvBasicTest, TypeTokenBucket)
{
struct swarmkv *db=SwarmkvBasicTest::db;
@@ -313,21 +387,46 @@ TEST_F(SwarmkvBasicTest, TypeTokenBucket)
}
EXPECT_LE(allocated_tokens, (now.tv_sec -start.tv_sec)*rate+capacity);
+ long long period=10;
+ reply=swarmkv_command(db, "TCFG %s %lld %lld PD %lld", key, rate, capacity, period);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STATUS);
+ swarmkv_reply_free(reply);
+ allocated_tokens=0;
+ start=now;
+ while(now.tv_sec - start.tv_sec<period*3)
+ {
+ request_tokens=random()%(2*rate);
+ reply=swarmkv_command(db, "TCONSUME %s %lld FLEXIBLE", key, request_tokens);
+ if(reply->type==SWARMKV_REPLY_INTEGER)
+ {
+ allocated_tokens+=reply->integer;
+ }
+ swarmkv_reply_free(reply);
+ gettimeofday(&now, NULL);
+ i++;
+ }
+ EXPECT_LE(allocated_tokens, (now.tv_sec - start.tv_sec)*rate/period+capacity);
+
//Infinite tokens
- reply=swarmkv_command(db, "TCFG %s 0 0", key);
+ reply=swarmkv_command(db, "TCFG %s 1 1 PD 0", key);
EXPECT_EQ(reply->type, SWARMKV_REPLY_STATUS);
swarmkv_reply_free(reply);
- long long t=0;
+ reply=swarmkv_command(db, "TINFO %s", key);
+ ASSERT_EQ(reply->n_element, 12);
+ allocated_tokens=reply->elements[7]->integer;
+ swarmkv_reply_free(reply);
+
+ long long inf_token=0;
for(i=0; i<100; i++)
{
reply=swarmkv_command(db, "TCONSUME %s 10000", key);
- t+=reply->integer;
+ inf_token+=reply->integer;
swarmkv_reply_free(reply);
}
- EXPECT_EQ(t, 10000*i);
+ EXPECT_EQ(inf_token, 10000*i);
reply=swarmkv_command(db, "TINFO %s", key);
- ASSERT_EQ(reply->n_element, 10);
- EXPECT_EQ(reply->elements[5]->integer, allocated_tokens+t);
+ ASSERT_EQ(reply->n_element, 12);
+ EXPECT_EQ(reply->elements[7]->integer, allocated_tokens+inf_token);
swarmkv_reply_free(reply);
}
TEST_F(SwarmkvBasicTest, TypeFairTokenBucket)
@@ -362,20 +461,20 @@ TEST_F(SwarmkvBasicTest, TypeFairTokenBucket)
//Infinite tokens
- reply=swarmkv_command(db, "FTCFG %s 0 0 256", key);
+ reply=swarmkv_command(db, "FTCFG %s %lld %lld 128 PD 0", key, rate, capacity);
EXPECT_EQ(reply->type, SWARMKV_REPLY_STATUS);
swarmkv_reply_free(reply);
- long long t=0;
+ long long inf_token=0;
for(i=0; i<100; i++)
{
reply=swarmkv_command(db, "FTCONSUME %s user-001 5 10000", key);
- t+=reply->integer;
+ inf_token+=reply->integer;
swarmkv_reply_free(reply);
}
- EXPECT_EQ(t, 10000*i);
+ EXPECT_EQ(inf_token, 10000*i);
reply=swarmkv_command(db, "FTINFO %s", key);
- ASSERT_EQ(reply->n_element, 14);
- EXPECT_EQ(reply->elements[5]->integer, allocated_tokens+t);
+ ASSERT_EQ(reply->n_element, 16);
+ EXPECT_EQ(reply->elements[7]->integer, allocated_tokens+inf_token);
swarmkv_reply_free(reply);
}
TEST_F(SwarmkvBasicTest, TypeBulkTokenBucket)
@@ -408,9 +507,13 @@ TEST_F(SwarmkvBasicTest, TypeBulkTokenBucket)
i++;
}
EXPECT_LE(allocated_tokens/n_member, (now.tv_sec -start.tv_sec)*rate+capacity);
+ reply=swarmkv_command(db, "BTINFO %s", key);
+ ASSERT_EQ(reply->n_element, 14);
+ EXPECT_NEAR(reply->elements[9]->integer, n_member, n_member/5);
+ swarmkv_reply_free(reply);
//Infinite tokens
- reply=swarmkv_command(db, "BTCFG %s 0 0 256", key);
+ reply=swarmkv_command(db, "BTCFG %s 0 0 256 PD 0", key);
EXPECT_EQ(reply->type, SWARMKV_REPLY_STATUS);
swarmkv_reply_free(reply);
long long t=0;
@@ -421,83 +524,110 @@ TEST_F(SwarmkvBasicTest, TypeBulkTokenBucket)
swarmkv_reply_free(reply);
}
EXPECT_EQ(t, 10000*i);
- reply=swarmkv_command(db, "BTINFO %s", key);
- ASSERT_EQ(reply->n_element, 12);
- EXPECT_NEAR(reply->elements[7]->integer, n_member, n_member/5);
- swarmkv_reply_free(reply);
+
}
-TEST_F(SwarmkvBasicTest, TypeHash)
+
+TEST_F(SwarmkvBasicTest, TypeBloomFilter)
{
struct swarmkv *db=SwarmkvBasicTest::db;
- const char *key="uid001";
+ const char *key="bf-001";
+ const char *item[4]={"zhangsan", "lisi", "王二麻子", "Tom"};
struct swarmkv_reply *reply=NULL;
+ long long time_window_ms=600, capacity=10000;
+ double error_rate=0.001;
+ reply=swarmkv_command(db, "BFRESERVE %s %f %lld TIME %lld 12", key, error_rate, capacity, time_window_ms);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STATUS);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "BFADD %s %s %s %s %s", key, item[0], item[1], item[2], item[3]);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STATUS);
+ swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "HSET %s name zhangsan gender male age 18 gender male", key);
+ reply=swarmkv_command(db, "BFEXISTS %s %s", key, item[0]);
ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
- EXPECT_EQ(reply->integer, 3);
+ EXPECT_EQ(reply->integer, 1);
swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "HGET %s name", key);
- ASSERT_EQ(reply->type, SWARMKV_REPLY_STRING);
- EXPECT_STREQ(reply->str, "zhangsan");
+ reply=swarmkv_command(db, "BFMEXISTS %s %s %s %s %s", key, item[0], item[1], item[2], item[3]);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
+ ASSERT_EQ(reply->n_element, 4);
+ for(size_t i=0; i<reply->n_element; i++)
+ {
+ ASSERT_EQ(reply->elements[i]->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->elements[i]->integer, 1);
+ }
swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "HMGET %s name gender not-exist", key);
+ reply=swarmkv_command(db, "BFMEXISTS %s %s %s %s %s %s", key, item[0], item[1], item[2], item[3], "non-exist-item");
ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
- ASSERT_EQ(reply->n_element, 3);
- EXPECT_STREQ(reply->elements[0]->str, "zhangsan");
- EXPECT_STREQ(reply->elements[1]->str, "male");
- EXPECT_EQ(reply->elements[2]->type, SWARMKV_REPLY_NIL);
+ ASSERT_EQ(reply->n_element, 5);
+ for(size_t i=0; i<reply->n_element-1; i++)
+ {
+ ASSERT_EQ(reply->elements[i]->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->elements[i]->integer, 1);
+ }
+ ASSERT_EQ(reply->elements[4]->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->elements[4]->integer, 0);
swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "HINCRBY %s age 1", key);
+ reply=swarmkv_command(db, "BFCARD %s", key);
ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
- EXPECT_EQ(reply->integer, 19);
+ EXPECT_NEAR(reply->integer, 4, 2);
swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "HGET %s age", key);
- ASSERT_EQ(reply->type, SWARMKV_REPLY_STRING);
- EXPECT_STREQ(reply->str, "19");
- swarmkv_reply_free(reply);
-
- reply=swarmkv_command(db, "HKEYS %s", key);
+ usleep(time_window_ms*1000);
+ reply=swarmkv_command(db, "BFMEXISTS %s %s %s %s %s %s", key, item[0], item[1], item[2], item[3], "non-exist-item");
ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
- ASSERT_EQ(reply->n_element, 3);
+ ASSERT_EQ(reply->n_element, 5);
+ for(size_t i=0; i<reply->n_element; i++)
+ {
+ ASSERT_EQ(reply->elements[i]->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->elements[i]->integer, 0);
+ }
swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "HGETALL %s", key);
+ reply=swarmkv_command(db, "BFINFO %s", key);
ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
- ASSERT_EQ(reply->n_element, 6);
+ ASSERT_EQ(reply->n_element, 20);
+ ASSERT_EQ(reply->elements[1]->type, SWARMKV_REPLY_DOUBLE);
+ EXPECT_EQ(reply->elements[1]->dval, error_rate);
+ ASSERT_EQ(reply->elements[3]->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->elements[3]->integer, capacity);
swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "HDEL %s gender", key);
+ reply=swarmkv_command(db, "DEL %s", key);
ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
ASSERT_EQ(reply->integer, 1);
swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "HLEN %s", key);
- ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
- ASSERT_EQ(reply->integer, 2);
+ reply=swarmkv_command(db, "BFMEXISTS %s %s", key, item[0]);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
+ EXPECT_EQ(reply->n_element, 0);
swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "DEL %s", key);
+ reply=swarmkv_command(db, "BFEXISTS %s %s", key, item[0]);
ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
- ASSERT_EQ(reply->integer, 1);
+ EXPECT_EQ(reply->integer, 0);
swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "HGET %s name", key);
- ASSERT_EQ(reply->type, SWARMKV_REPLY_NIL);
+ //No time window
+ reply=swarmkv_command(db, "BFRESERVE %s %f %lld", key, error_rate, capacity);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STATUS);
+ swarmkv_reply_free(reply);
+
+ reply=swarmkv_command(db, "BFADD %s %s %s %s %s", key, item[0], item[1], item[2], item[3]);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STATUS);
swarmkv_reply_free(reply);
- const char *key2="uid002";
- reply=swarmkv_command(db, "HINCRBY %s age 30", key2);
+ reply=swarmkv_command(db, "BFEXISTS %s %s", key, item[0]);
ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
- EXPECT_EQ(reply->integer, 30);
+ EXPECT_EQ(reply->integer, 1);
swarmkv_reply_free(reply);
- reply=swarmkv_command(db, "HINCRBY %s age -1", key2);
+
+ reply=swarmkv_command(db, "DEL %s", key);
ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
- EXPECT_EQ(reply->integer, 30-1);
+ ASSERT_EQ(reply->integer, 1);
swarmkv_reply_free(reply);
}
TEST_F(SwarmkvBasicTest, EXPIRE_TTL)
@@ -582,7 +712,7 @@ protected:
swarmkv_options_set_cluster_port(opts[i], TWO_NODES_TEST_CLUSTER_PORT+i);
swarmkv_options_set_health_check_port(opts[i], TWO_NODES_TEST_HEALTH_PORT+i);
swarmkv_options_set_logger(opts[i], logger);
- swarmkv_options_set_sync_interval_us(opts[i], 20*1000);
+ swarmkv_options_set_sync_interval_us(opts[i], 10*1000);
swarmkv_options_set_cluster_timeout_us(opts[i], very_long_timeout_us);
swarmkv_options_set_worker_thread_number(opts[i], 2);
swarmkv_options_set_caller_thread_number(opts[i], 1);
@@ -1048,6 +1178,61 @@ TEST_F(SwarmkvTwoNodes, FromLocalReplica)
EXPECT_EQ(reply->integer, 1);
swarmkv_reply_free(reply);
}
+TEST_F(SwarmkvTwoNodes, TypeHash)
+{
+ struct swarmkv *db[2];
+ db[0]=SwarmkvTwoNodes::db1;
+ db[1]=SwarmkvTwoNodes::db2;
+
+ size_t n_test_key=100;
+ struct swarmkv_reply *reply=NULL;
+ for(size_t i=0; i<n_test_key; i++)
+ {
+ reply=swarmkv_command(db[i%2], "HSET uid-%zu name zhangsan gender male age 18 gender male", i);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->integer, 3);
+ swarmkv_reply_free(reply);
+ }
+ wait_for_sync();
+ for(size_t i=0; i<n_test_key; i++)
+ {
+ reply=swarmkv_command(db[(i+1)%2], "HGET uid-%zu name", i);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STRING);
+ EXPECT_STREQ(reply->str, "zhangsan");
+ swarmkv_reply_free(reply);
+ }
+ for(size_t i=0; i<n_test_key; i++)
+ {
+ reply=swarmkv_command(db[(i+1)%2], "HINCRBY uid-%zu age 1", i);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->integer, 19);
+ swarmkv_reply_free(reply);
+ }
+ wait_for_sync();
+ for(size_t i=0; i<n_test_key; i++)
+ {
+ reply=swarmkv_command(db[(i+1)%2], "HGET uid-%zu age", i);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STRING);
+ EXPECT_STREQ(reply->str, "19");
+ swarmkv_reply_free(reply);
+ }
+ for(size_t i=0; i<n_test_key; i++)
+ {
+ reply=swarmkv_command(db[(i+1)%2], "HSET uid-%zu gender female", i);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
+ EXPECT_EQ(reply->integer, 0);
+ swarmkv_reply_free(reply);
+ }
+ wait_for_sync();
+ for(size_t i=0; i<n_test_key; i++)
+ {
+ reply=swarmkv_command(db[(i+1)%2], "HGET uid-%zu gender", i);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STRING);
+ EXPECT_STREQ(reply->str, "female");
+ swarmkv_reply_free(reply);
+ }
+}
+
TEST_F(SwarmkvTwoNodes, TypeSet)
{
struct swarmkv *db[2];
@@ -1198,12 +1383,14 @@ TEST_F(SwarmkvTwoNodes, TypeTokenBucket)
swarmkv_reply_free(reply);
reply=NULL;
}
-struct ftb_member
+struct ftb_class
{
- long long member_id;
+ long long id;
long long weight;
long long requested_tokens;
long long got_tokens;
+ long long requested_round;
+ struct timeval last_request;
};
TEST_F(SwarmkvTwoNodes, TypeFairTokenBucket)
{
@@ -1212,7 +1399,7 @@ TEST_F(SwarmkvTwoNodes, TypeFairTokenBucket)
db[1]=SwarmkvTwoNodes::db2;
const char *key="shaping-profile-with-fairness";
- long long capacity=1024*4, rate=1024*2, divisor=1024;
+ long long capacity=1*1024*1024, rate=20*1024*1024, divisor=1024;
struct swarmkv_reply *reply=NULL;
reply=swarmkv_command(db[0], "FTCFG %s %lld %lld %lld", key, rate, capacity, divisor);
@@ -1227,26 +1414,42 @@ TEST_F(SwarmkvTwoNodes, TypeFairTokenBucket)
struct timeval start, now;
gettimeofday(&start, NULL);
gettimeofday(&now, NULL);
- size_t n_member=3;
- struct ftb_member mb[n_member]={{1, 1, 0, 0}, {2, 2, 0, 0}, {3, 3, 0, 0}};
+ size_t n_member=5;
+ struct ftb_class mb[n_member]={{.id=1, .weight=1},
+ {.id=2, .weight=2},
+ {.id=3, .weight=3},
+ {.id=4, .weight=4},
+ {.id=5, .weight=5}};
long long token=0, requested_tokens=0, allocated_tokens=0;
long long elapsed_ms=0;
long long run_second=20;
+ long long req_interval_us=0;
while(elapsed_ms/1000<run_second)
{
- token=random()%(2*rate/n_member);
+ int idx=round%n_member;
+ gettimeofday(&now, NULL);
+ if(mb[idx].last_request.tv_sec==0)
+ {
+ mb[idx].last_request=now;
+ }
+ req_interval_us=timeval_delta_us(mb[idx].last_request, now);
+ if(req_interval_us==0) continue;
+ token=rate*req_interval_us/1000000/n_member;
+ token+=random()%token;
+ mb[idx].last_request=now;
requested_tokens+=token;
- mb[round%n_member].requested_tokens+=token;
- reply=swarmkv_command(db[1], "FTCONSUME %s user-%lld %lld %lld",
+ mb[idx].requested_tokens+=token;
+ reply=swarmkv_command(db[idx%2], "FTCONSUME %s user-%lld %lld %lld",
key,
- mb[round%n_member].member_id, mb[round%n_member].weight, token);
+ mb[idx].id, mb[idx].weight, token);
ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
assert(reply->integer>=0);
- mb[round%n_member].got_tokens+=reply->integer;
+ mb[idx].got_tokens+=reply->integer;
+ mb[idx].requested_round++;
allocated_tokens+=reply->integer;
swarmkv_reply_free(reply);
- gettimeofday(&now, NULL);
+
elapsed_ms=(now.tv_sec-start.tv_sec)*1000+(now.tv_usec-start.tv_usec)/1000;
round++;
}
@@ -1255,32 +1458,34 @@ TEST_F(SwarmkvTwoNodes, TypeFairTokenBucket)
EXPECT_GE(round/(int)(now.tv_sec-start.tv_sec), 10000);
long long upper_limit=elapsed_ms*rate/1000+capacity;
double accuracy=(double)allocated_tokens/(upper_limit<requested_tokens?upper_limit:requested_tokens);
- EXPECT_NEAR(accuracy, 1, 0.04);
+ EXPECT_NEAR(accuracy, 1, 0.05);
+#ifdef DEBUG
printf("requested %lld, upper limit %lld, allocated %lld\n", requested_tokens, upper_limit, allocated_tokens);
-
+#endif
long long normalized_tokens[n_member]={0};
for(size_t i=0; i<n_member; i++)
{
normalized_tokens[i]=mb[i].got_tokens/mb[i].weight;
-
+#ifdef DEBUG
printf("member %lld, weight %lld, requested %lld, got %lld\n",
- mb[i].member_id, mb[i].weight, mb[i].requested_tokens, mb[i].got_tokens);
+ mb[i].id, mb[i].weight, mb[i].requested_tokens, mb[i].got_tokens);
+#endif
}
double deviation=stddev(normalized_tokens, n_member);
EXPECT_NEAR(deviation/mb[0].got_tokens, 0, 0.1);
- wait_for_sync();
+ //wait_for_sync();
reply=swarmkv_command(db[0], "FTINFO %s", key);
ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
- ASSERT_EQ(reply->n_element, 14);
- EXPECT_NEAR(reply->elements[13]->integer, n_member, n_member/5);
+ ASSERT_EQ(reply->n_element, 16);
+ EXPECT_NEAR(reply->elements[15]->integer, n_member, (double)n_member/5);
swarmkv_reply_free(reply);
reply=swarmkv_command(db[1], "FTINFO %s", key);
ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
- ASSERT_EQ(reply->n_element, 14);
- EXPECT_NEAR(reply->elements[13]->integer, n_member, n_member/5);
+ ASSERT_EQ(reply->n_element, 16);
+ EXPECT_NEAR(reply->elements[15]->integer, n_member, (double)n_member/5);
swarmkv_reply_free(reply);
reply=swarmkv_command(db[0], "DEL %s", key);
@@ -1303,10 +1508,10 @@ TEST_F(SwarmkvTwoNodes, TypeBulkTokenBucket)
db[1]=SwarmkvTwoNodes::db2;
const char *key="shaping-profile-everyone-has-10Mbps";
- long long capacity=15*1024*1024, rate=10*1024*1024, buckets=8192;
+ long long period=5, capacity=15*1024*1024, rate=10*1024*1024*period, buckets=8192;
struct swarmkv_reply *reply=NULL;
- reply=swarmkv_command(db[0], "BTCFG %s %lld %lld %lld", key, rate, capacity, buckets);
+ reply=swarmkv_command(db[0], "BTCFG %s %lld %lld %lld PD %lld", key, rate, capacity, buckets, period);
ASSERT_EQ(reply->type, SWARMKV_REPLY_STATUS);
EXPECT_STREQ(reply->str, "OK");
swarmkv_reply_free(reply);
@@ -1322,7 +1527,7 @@ TEST_F(SwarmkvTwoNodes, TypeBulkTokenBucket)
while(now.tv_sec - start.tv_sec<30)
{
- token=random()%(2*rate/n_member);
+ token=random()%(2*rate/n_member/period);
requested_tokens+=token;
reply=swarmkv_command(db[round%2], "BTCONSUME %s user-%lld %lld", key, member_id, token);
@@ -1335,25 +1540,25 @@ TEST_F(SwarmkvTwoNodes, TypeBulkTokenBucket)
round++;
}
printf("consume round %d, speed %d ops\n", round, round/(int)(now.tv_sec-start.tv_sec));
- EXPECT_GE(round/(int)(now.tv_sec-start.tv_sec), 10000);
- long long upper_limit=(now.tv_sec-start.tv_sec)*rate+capacity;
+ EXPECT_GE(round/(int)(now.tv_sec-start.tv_sec), 5000);
+ long long upper_limit=(now.tv_sec-start.tv_sec)*rate/period+capacity;
upper_limit=upper_limit*n_member;
double accuracy=(double)allocated_tokens/(upper_limit<requested_tokens?upper_limit:requested_tokens);
EXPECT_NEAR(accuracy, 1, 0.035);
- wait_for_sync();
+ //wait_for_sync();
reply=swarmkv_command(db[0], "BTINFO %s", key);
ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
- ASSERT_EQ(reply->n_element, 12);
- EXPECT_NEAR(reply->elements[7]->integer, n_member, n_member/5);
+ ASSERT_EQ(reply->n_element, 14);
+ EXPECT_NEAR(reply->elements[9]->integer, n_member, n_member/5);
swarmkv_reply_free(reply);
reply=swarmkv_command(db[1], "BTINFO %s user-001", key);
ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
- ASSERT_EQ(reply->n_element, 12);
- EXPECT_NEAR(reply->elements[7]->integer, n_member, n_member/5);
- EXPECT_LE(reply->elements[9]->integer, capacity);
+ ASSERT_EQ(reply->n_element, 14);
+ EXPECT_NEAR(reply->elements[9]->integer, n_member, n_member/5);
+ EXPECT_LE(reply->elements[11]->integer, capacity);
swarmkv_reply_free(reply);
reply=swarmkv_command(db[0], "DEL %s", key);
@@ -1389,70 +1594,44 @@ TEST_F(SwarmkvTwoNodes, TypeBulkTokenBucket)
swarmkv_reply_free(reply);
}
-TEST_F(SwarmkvTwoNodes, Info)
+TEST_F(SwarmkvTwoNodes, BloomFilter)
{
struct swarmkv *db[2];
db[0]=SwarmkvTwoNodes::db1;
db[1]=SwarmkvTwoNodes::db2;
struct swarmkv_reply *reply=NULL;
- for(size_t i=0; i<2; i++)
+ const char *key="bloom-filter-001";
+ const char *item[3]={"hello", "world", "bloom"};
+ reply=swarmkv_command(db[0], "BFRESERVE %s 0.0001 1000000 time 3000000 13", key);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STATUS);
+ EXPECT_STREQ(reply->str, "OK");
+ swarmkv_reply_free(reply);
+
+ for(int i=0; i<3; i++)
{
- reply=swarmkv_command(db[i%2], "INFO");
+ reply=swarmkv_command(db[i%2], "BFADD %s %s", key, item[i]);
ASSERT_EQ(reply->type, SWARMKV_REPLY_STATUS);
swarmkv_reply_free(reply);
}
+ reply=swarmkv_command(db[1], "BFMEXISTS %s %s %s %s", key, item[0], item[1], item[2]);
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_ARRAY);
+ ASSERT_EQ(reply->n_element, 3);
+ for(int i=0; i<3; i++)
+ {
+ EXPECT_EQ(reply->elements[i]->integer, 1);
+ }
+ swarmkv_reply_free(reply);
}
-TEST_F(SwarmkvTwoNodes, TypeHash)
+TEST_F(SwarmkvTwoNodes, Info)
{
struct swarmkv *db[2];
db[0]=SwarmkvTwoNodes::db1;
db[1]=SwarmkvTwoNodes::db2;
-
- size_t n_test_key=100;
struct swarmkv_reply *reply=NULL;
- for(size_t i=0; i<n_test_key; i++)
- {
- reply=swarmkv_command(db[i%2], "HSET uid-%zu name zhangsan gender male age 18 gender male", i);
- ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
- EXPECT_EQ(reply->integer, 3);
- swarmkv_reply_free(reply);
- }
- wait_for_sync();
- for(size_t i=0; i<n_test_key; i++)
- {
- reply=swarmkv_command(db[(i+1)%2], "HGET uid-%zu name", i);
- ASSERT_EQ(reply->type, SWARMKV_REPLY_STRING);
- EXPECT_STREQ(reply->str, "zhangsan");
- swarmkv_reply_free(reply);
- }
- for(size_t i=0; i<n_test_key; i++)
- {
- reply=swarmkv_command(db[(i+1)%2], "HINCRBY uid-%zu age 1", i);
- ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
- EXPECT_EQ(reply->integer, 19);
- swarmkv_reply_free(reply);
- }
- wait_for_sync();
- for(size_t i=0; i<n_test_key; i++)
- {
- reply=swarmkv_command(db[(i+1)%2], "HGET uid-%zu age", i);
- ASSERT_EQ(reply->type, SWARMKV_REPLY_STRING);
- EXPECT_STREQ(reply->str, "19");
- swarmkv_reply_free(reply);
- }
- for(size_t i=0; i<n_test_key; i++)
- {
- reply=swarmkv_command(db[(i+1)%2], "HSET uid-%zu gender female", i);
- ASSERT_EQ(reply->type, SWARMKV_REPLY_INTEGER);
- EXPECT_EQ(reply->integer, 0);
- swarmkv_reply_free(reply);
- }
- wait_for_sync();
- for(size_t i=0; i<n_test_key; i++)
+ for(size_t i=0; i<2; i++)
{
- reply=swarmkv_command(db[(i+1)%2], "HGET uid-%zu gender", i);
- ASSERT_EQ(reply->type, SWARMKV_REPLY_STRING);
- EXPECT_STREQ(reply->str, "female");
+ reply=swarmkv_command(db[i%2], "INFO");
+ ASSERT_EQ(reply->type, SWARMKV_REPLY_STATUS);
swarmkv_reply_free(reply);
}
}
diff --git a/test/test_utils.h b/test/test_utils.h
index c89fc33..740443f 100644
--- a/test/test_utils.h
+++ b/test/test_utils.h
@@ -12,6 +12,9 @@ extern "C"
#define ALLOC(type, number) ((type *)calloc(sizeof(type), number))
+#define timeval_delta_ms(start, end) (((end).tv_sec-(start).tv_sec)*1000 + ((end).tv_usec-(start).tv_usec)/1000)
+#define timeval_delta_us(start, end) (((end).tv_sec-(start).tv_sec)*1000*1000 + ((end).tv_usec-(start).tv_usec))
+
int swarmkv_cli_create_cluster(const char* cluster_name, const char *node_string);
int swarmkv_cli_add_slot_owner(const char *cluster_name, const char *node_string);