From 94b30cb69c6bc3a716602ee9b398de50235b1de1 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Sun, 25 Sep 2016 15:06:09 -0400 Subject: [PATCH 01/57] redis RESP support --- Connection.cc | 2 + Connection.h | 4 ++ ConnectionOptions.h | 1 + Protocol.cc | 105 ++++++++++++++++++++++++++++++++++++++++++++ Protocol.h | Bin 1803 -> 71680 bytes cmdline.ggo | 1 + mutilate.cc | 1 + 7 files changed, 114 insertions(+) diff --git a/Connection.cc b/Connection.cc index ea02899..069291e 100644 --- a/Connection.cc +++ b/Connection.cc @@ -48,6 +48,8 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, if (options.binary) { prot = new ProtocolBinary(options, this, bev); + } else if (options.redis) { + prot = new ProtocolRESP(options, this, bev); } else { prot = new ProtocolAscii(options, this, bev); } diff --git a/Connection.h b/Connection.h index fea451e..f19bbe0 100644 --- a/Connection.h +++ b/Connection.h @@ -114,11 +114,15 @@ class Connection { // protocol fucntions int set_request_ascii(const char* key, const char* value, int length); int set_request_binary(const char* key, const char* value, int length); + int set_request_resp(const char* key, const char* value, int length); + int get_request_ascii(const char* key); int get_request_binary(const char* key); + int get_request_resp(const char* key); bool consume_binary_response(evbuffer *input); bool consume_ascii_line(evbuffer *input, bool &done); + bool consume_resp_line(evbuffer *input, bool &done); }; #endif diff --git a/ConnectionOptions.h b/ConnectionOptions.h index ba3d70c..e044e16 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -11,6 +11,7 @@ typedef struct { int records; bool binary; + bool redis; bool sasl; char username[32]; char password[32]; diff --git a/Protocol.cc b/Protocol.cc index 6d346b8..2970d29 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -19,6 +19,109 @@ #define unlikely(x) __builtin_expect((x),0) +/** + * + * First we build a RESP Array: + * 1. * character as the first byte + * 2. the number of elements in the array as a decimal number + * 3. CRLF + * 4. The actual RESP element we are putting into the array + * + * All Redis commands are sent as arrays of bulk strings. + * For example, the command “SET mykey ‘my value’” would be written and sent as: + * *3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$8\r\nmy value\r\n + * + * Then package command as a RESP Bulk String to the server + * + * Bulk String is the defined by the following: + * 1."$" byte followed by the number of bytes composing the + * string (a prefixed length), terminated by CRLF. + * 2. The actual string data. + * 3. A final CRLF. + * + */ +int ProtocolRESP::set_request(const char* key, const char* value, int value_len) { + + int l; + l = evbuffer_add_printf(bufferevent_get_output(bev), + "*3\r\n$3\r\nSET\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n", + strlen(key),key,strlen(value),value); + //if (read_state == IDLE) read_state = WAITING_FOR_END; + return l; + + //size_t req_size = strlen("*3$$$") + 7*strlen("\r\n") + strlen(key) + strlen(value); + + //char *req = (char*)malloc(req_size*(sizeof(char))); + //snprintf(req,req_size,"*%d\r\n$%dSET\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n", + // 3,3,strlen(key),key,strlen(value),value); + // + //bufferevent_write(bev, req, req_size); + //if (read_state == IDLE) read_state = WAITING_FOR_END; + //return req_size; +} + +/** + * Send a RESP get request. + */ +int ProtocolRESP::get_request(const char* key) { + int l; + l = evbuffer_add_printf(bufferevent_get_output(bev), + "*2\r\n$3\r\nGET\r\n$%d\r\n%s\r\n",strlen(key),key); + //if (read_state == IDLE) read_state = WAITING_FOR_END; + return l; +} + +/** + * Handle a RESP response. + * + * In RESP, the type of data depends on the first byte: + * + * Simple Strings the first byte of the reply is "+" + * Errors the first byte of the reply is "-" + * Integers the first byte of the reply is ":" + * Bulk Strings the first byte of the reply is "$" + * Arrays the first byte of the reply is "*" + * + * Right now we are only implementing GET response + * so the RESP type will be bulk string. + * + * + */ +bool ProtocolRESP::handle_response(evbuffer *input, bool &done) { + char *resp = NULL; + char *resp_bytes = NULL; + size_t n_read_out; //first read number of bytes + size_t m_read_out; //second read number of bytes + + //A bulk string resp: "$6\r\nfoobar\r\n" + //1. Consume the first "$##\r\n", evbuffer_readln returns + // "$##", where ## is the number of bytes in the bulk string + resp_bytes = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); + resp_bytes++; //consume the "$" + + //2. Consume the next "foobar" + resp = evbuffer_readln(input,&m_read_out,EVBUFFER_EOL_CRLF_STRICT); + + //make sure we got a results + if (resp == NULL || resp_bytes == NULL) return false; + + //keep track of recieved stats + conn->stats.rx_bytes += n_read_out + m_read_out; + + //if nil we have a miss, keep waiting? (check if this is correct action) + if (strncmp(resp, "nil", 3) { + conn->stats.get_misses++; + done = true; + } else { + //we have a proper respsone + done = true; + } + evbuffer_drain(input,n_read_out + m_read_out + 4) //4 = 2*(CRLF bytes) + free(resp); + free(resp_bytes); + return true; +} + /** * Send an ascii get request. */ @@ -146,6 +249,8 @@ int ProtocolBinary::get_request(const char* key) { return 24 + keylen; } + + /** * Send a binary set request. */ diff --git a/Protocol.h b/Protocol.h index da7b2539147221f2d6394a7dd8bb74ec84d5f806..931afe0668dfe66a2f827ecaafbe9f07554b827e 100644 GIT binary patch literal 71680 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

g_>v!oEV63v?hXmdP}bhIENoaEiobYGG$ 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 B?gan< literal 1803 zcmdT^O>5gg5WVlO7-A^0V#lS&xGlJ`n;JqKf}uyTmPYncd8O`$N-*hf&+JOJl*&!x zLJz$N?P%uB%zIBe8o@9eLVSKc+~x_o1M7)YCYZwA{bISeTHHimSdY#^Vmm}>GR`d3 zyvGuyc{f_wG)3hP7n_f)NjSU8rNr2XTrLXt)EApG#;g`{4U%tB7d%GLCK)ppXIyJ& zDh{DLm76@yGuW}ht}+o{GJt(B$SdH;Kt!*4&_W0yifMoWv<9Tu3d_EO0B{9?FY6D$ zr3Su$4wMu4MA$u!Dfj20U*AV4R0?G*&tR3)Xc~lXc*9i)-|*ph0ZsI~0>A44q2!sx0rfVJq3nS9^)_54 z84{i@s5kyPGV$d|c$4H3YXDY|YQzO5j_O9FnsvuZBXtO#eHVNt6{#?0#DDFLy#^B< z3jvoOEyL-s8l0(q;>B?D12=OBeViL~JcN$vUcX6ANq6Spi= zjc|f*b_8pzl3W z$#;AY=o`S-$$UA#y^cOF?xXA3@@O!cPL`AIm)Y&q1uM_*L$y{Sm$Ev-dF9K|HfNR?;grTr0fac&0k>v diff --git a/cmdline.ggo b/cmdline.ggo index 331dd21..1c5f7d6 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -13,6 +13,7 @@ text "\nBasic options:" option "server" s "Memcached server hostname[:port]. \ Repeat to specify multiple servers." string multiple option "binary" - "Use binary memcached protocol instead of ASCII." +option "redis" - "Use Redis RESP protocol instead of memchached." option "qps" q "Target aggregate QPS. 0 = peak QPS." int default="0" option "time" t "Maximum time to run (seconds)." int default="5" diff --git a/mutilate.cc b/mutilate.cc index 426fd05..3ea2897 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -1059,6 +1059,7 @@ void args_to_options(options_t* options) { options->records = args.records_arg / options->server_given; options->binary = args.binary_given; + options->redis = args.redis_given; options->sasl = args.username_given; if (args.password_given) From 6845e5b784312069ac63b1334426339534db9140 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Sun, 25 Sep 2016 22:03:13 -0400 Subject: [PATCH 02/57] tested resp protocol --- Protocol.cc | 94 ++++++++++++++++++++++++++--------------------- Protocol.h | Bin 71680 -> 2434 bytes README.md | 53 +++++++++++++------------- update_readme.sh | 0 4 files changed, 79 insertions(+), 68 deletions(-) mode change 100644 => 100755 update_readme.sh diff --git a/Protocol.cc b/Protocol.cc index 2970d29..2f77558 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -46,18 +46,9 @@ int ProtocolRESP::set_request(const char* key, const char* value, int value_len) l = evbuffer_add_printf(bufferevent_get_output(bev), "*3\r\n$3\r\nSET\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n", strlen(key),key,strlen(value),value); - //if (read_state == IDLE) read_state = WAITING_FOR_END; + if (read_state == IDLE) read_state = WAITING_FOR_END; return l; - //size_t req_size = strlen("*3$$$") + 7*strlen("\r\n") + strlen(key) + strlen(value); - - //char *req = (char*)malloc(req_size*(sizeof(char))); - //snprintf(req,req_size,"*%d\r\n$%dSET\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n", - // 3,3,strlen(key),key,strlen(value),value); - // - //bufferevent_write(bev, req, req_size); - //if (read_state == IDLE) read_state = WAITING_FOR_END; - //return req_size; } /** @@ -67,7 +58,8 @@ int ProtocolRESP::get_request(const char* key) { int l; l = evbuffer_add_printf(bufferevent_get_output(bev), "*2\r\n$3\r\nGET\r\n$%d\r\n%s\r\n",strlen(key),key); - //if (read_state == IDLE) read_state = WAITING_FOR_END; + + if (read_state == IDLE) read_state = WAITING_FOR_GET; return l; } @@ -88,38 +80,56 @@ int ProtocolRESP::get_request(const char* key) { * */ bool ProtocolRESP::handle_response(evbuffer *input, bool &done) { - char *resp = NULL; - char *resp_bytes = NULL; - size_t n_read_out; //first read number of bytes - size_t m_read_out; //second read number of bytes - - //A bulk string resp: "$6\r\nfoobar\r\n" - //1. Consume the first "$##\r\n", evbuffer_readln returns - // "$##", where ## is the number of bytes in the bulk string - resp_bytes = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); - resp_bytes++; //consume the "$" - - //2. Consume the next "foobar" - resp = evbuffer_readln(input,&m_read_out,EVBUFFER_EOL_CRLF_STRICT); - - //make sure we got a results - if (resp == NULL || resp_bytes == NULL) return false; - - //keep track of recieved stats - conn->stats.rx_bytes += n_read_out + m_read_out; - - //if nil we have a miss, keep waiting? (check if this is correct action) - if (strncmp(resp, "nil", 3) { - conn->stats.get_misses++; - done = true; - } else { - //we have a proper respsone - done = true; + char *buf = NULL; + int len; + size_t n_read_out; + + switch (read_state) { + + case WAITING_FOR_GET: + case WAITING_FOR_END: + buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); + if (buf == NULL) return false; + + conn->stats.rx_bytes += n_read_out + 2; //don't forget CRLF + + //this is checking if we have gotten the "END" data message + //in memcached it is simply END, but in redis everything + //is sent as a bulk string, so we check for a $ + if (buf[0] != '$') { + //if (read_state == WAITING_FOR_GET) conn->stats.get_misses++; + read_state = WAITING_FOR_GET; + done = true; + free(buf); + } + else { + //A bulk string resp: "$6\r\nfoobar\r\n" + //1. Consume the first "$##\r\n", evbuffer_readln returns + // "$##", where ## is the number of bytes in the bulk string + sscanf(buf, "$%d", &len); + data_length = len; + free(buf); + + //2. Consume the next "foobar" + buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); + conn->stats.rx_bytes += n_read_out + 2; //don't forget CRLF + + //check if it was nil => miss + if (!strncmp(buf,"nil",3)) { + if (read_state == WAITING_FOR_GET) conn->stats.get_misses++; + read_state = WAITING_FOR_GET; + done = true; + } + free(buf); + done = true; + } + return true; + + + default: printf("state: %d\n", read_state); DIE("Unimplemented!"); } - evbuffer_drain(input,n_read_out + m_read_out + 4) //4 = 2*(CRLF bytes) - free(resp); - free(resp_bytes); - return true; + + DIE("Shouldn't ever reach here..."); } /** diff --git a/Protocol.h b/Protocol.h index 931afe0668dfe66a2f827ecaafbe9f07554b827e..41f4e75074e6d94d0d7a210153b3ae75c99cad3d 100644 GIT binary patch literal 2434 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

g_>v!oEV63v?hXmdP}bhIENoaEiobYGG$ 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 B?gan< diff --git a/README.md b/README.md index e599886..dd9c8c1 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ client-side queuing delay adulterating the latency measurements. Command-line Options ==================== - mutilate3 0.1 + mutilate 0.1 Usage: mutilate -s server[:port] [options] @@ -129,19 +129,20 @@ Command-line Options --quiet Disable log messages. Basic options: - -s, --server=STRING Memcached server hostname[:port]. Repeat to + -s, --server=STRING Memcached server hostname[:port]. Repeat to specify multiple servers. --binary Use binary memcached protocol instead of ASCII. - -q, --qps=INT Target aggregate QPS. 0 = peak QPS. + --redis Use Redis RESP protocol instead of memchached. + -q, --qps=INT Target aggregate QPS. 0 = peak QPS. (default=`0') -t, --time=INT Maximum time to run (seconds). (default=`5') - -K, --keysize=STRING Length of memcached keys (distribution). + -K, --keysize=STRING Length of memcached keys (distribution). (default=`30') - -V, --valuesize=STRING Length of memcached values (distribution). + -V, --valuesize=STRING Length of memcached values (distribution). (default=`200') - -r, --records=INT Number of memcached records to use. If - multiple memcached servers are given, this - number is divided by the number of servers. + -r, --records=INT Number of memcached records to use. If + multiple memcached servers are given, this + number is divided by the number of servers. (default=`10000') -u, --update=FLOAT Ratio of set:get commands. (default=`0.0') @@ -150,34 +151,34 @@ Command-line Options -P, --password=STRING Password to use for SASL authentication. -T, --threads=INT Number of threads to spawn. (default=`1') --affinity Set CPU affinity for threads, round-robin - -c, --connections=INT Connections to establish per server. + -c, --connections=INT Connections to establish per server. (default=`1') - -d, --depth=INT Maximum depth to pipeline requests. + -d, --depth=INT Maximum depth to pipeline requests. (default=`1') - -R, --roundrobin Assign threads to servers in round-robin - fashion. By default, each thread connects to + -R, --roundrobin Assign threads to servers in round-robin + fashion. By default, each thread connects to every server. - -i, --iadist=STRING Inter-arrival distribution (distribution). - Note: The distribution will automatically be - adjusted to match the QPS given by --qps. + -i, --iadist=STRING Inter-arrival distribution (distribution). + Note: The distribution will automatically be + adjusted to match the QPS given by --qps. (default=`exponential') - -S, --skip Skip transmissions if previous requests are - late. This harms the long-term QPS average, - but reduces spikes in QPS after long latency + -S, --skip Skip transmissions if previous requests are + late. This harms the long-term QPS average, + but reduces spikes in QPS after long latency requests. - --moderate Enforce a minimum delay of ~1/lambda between + --moderate Enforce a minimum delay of ~1/lambda between requests. --noload Skip database loading. --loadonly Load database and then exit. -B, --blocking Use blocking epoll(). May increase latency. --no_nodelay Don't use TCP_NODELAY. -w, --warmup=INT Warmup time before starting measurement. - -W, --wait=INT Time to wait after startup to start + -W, --wait=INT Time to wait after startup to start measurement. --save=STRING Record latency samples to given file. - --search=N:X Search for the QPS where N-order statistic < - Xus. (i.e. --search 95:1000 means find the - QPS where 95% of requests are faster than + --search=N:X Search for the QPS where N-order statistic < + Xus. (i.e. --search 95:1000 means find the + QPS where 95% of requests are faster than 1000us). --scan=min:max:step Scan latency across QPS rates from min to max. @@ -185,11 +186,11 @@ Command-line Options -A, --agentmode Run client in agent mode. -a, --agent=host Enlist remote agent. -p, --agent_port=STRING Agent port. (default=`5556') - -l, --lambda_mul=INT Lambda multiplier. Increases share of QPS for + -l, --lambda_mul=INT Lambda multiplier. Increases share of QPS for this client. (default=`1') - -C, --measure_connections=INT Master client connections per server, overrides + -C, --measure_connections=INT Master client connections per server, overrides --connections. - -Q, --measure_qps=INT Explicitly set master client QPS, spread across + -Q, --measure_qps=INT Explicitly set master client QPS, spread across threads and connections. -D, --measure_depth=INT Set master client connection depth. diff --git a/update_readme.sh b/update_readme.sh old mode 100644 new mode 100755 From 8439416c258fc30622ba21fe07d11fae35412bca Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Sun, 25 Sep 2016 23:31:20 -0400 Subject: [PATCH 03/57] tested with facebook etc --- Protocol.cc | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Protocol.cc b/Protocol.cc index 2f77558..297a303 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -44,7 +44,7 @@ int ProtocolRESP::set_request(const char* key, const char* value, int value_len) int l; l = evbuffer_add_printf(bufferevent_get_output(bev), - "*3\r\n$3\r\nSET\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n", + "*3\r\n$3\r\nSET\r\n$%lu\r\n%s\r\n$%lu\r\n%s\r\n", strlen(key),key,strlen(value),value); if (read_state == IDLE) read_state = WAITING_FOR_END; return l; @@ -57,7 +57,7 @@ int ProtocolRESP::set_request(const char* key, const char* value, int value_len) int ProtocolRESP::get_request(const char* key) { int l; l = evbuffer_add_printf(bufferevent_get_output(bev), - "*2\r\n$3\r\nGET\r\n$%d\r\n%s\r\n",strlen(key),key); + "*2\r\n$3\r\nGET\r\n$%lu\r\n%s\r\n",strlen(key),key); if (read_state == IDLE) read_state = WAITING_FOR_GET; return l; @@ -99,8 +99,8 @@ bool ProtocolRESP::handle_response(evbuffer *input, bool &done) { if (buf[0] != '$') { //if (read_state == WAITING_FOR_GET) conn->stats.get_misses++; read_state = WAITING_FOR_GET; - done = true; free(buf); + done = true; } else { //A bulk string resp: "$6\r\nfoobar\r\n" @@ -108,20 +108,24 @@ bool ProtocolRESP::handle_response(evbuffer *input, bool &done) { // "$##", where ## is the number of bytes in the bulk string sscanf(buf, "$%d", &len); data_length = len; - free(buf); - - //2. Consume the next "foobar" - buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); - conn->stats.rx_bytes += n_read_out + 2; //don't forget CRLF - - //check if it was nil => miss - if (!strncmp(buf,"nil",3)) { + //null response => miss + if (data_length == -1) { if (read_state == WAITING_FOR_GET) conn->stats.get_misses++; read_state = WAITING_FOR_GET; done = true; + free(buf); + } + else + { + free(buf); + + //2. Consume the next "foobar" + buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); + conn->stats.rx_bytes += n_read_out + 2; //don't forget CRLF + + free(buf); + done = true; } - free(buf); - done = true; } return true; From 6830e85ace54a2428ab8075fbf239f23105c075d Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Tue, 25 Oct 2016 23:56:36 -0400 Subject: [PATCH 04/57] updated to work with getset mode (cache style) --- Connection.cc | 47 ++++++++++++++++++++++++++++++++++++++++----- Connection.h | 1 + ConnectionOptions.h | 1 + Protocol.cc | 21 +++++++++++++------- Protocol.h | 8 ++++---- README.md | 4 ++++ cmdline.ggo | 3 +++ mutilate.cc | 4 ++++ 8 files changed, 73 insertions(+), 16 deletions(-) diff --git a/Connection.cc b/Connection.cc index 069291e..d934ec9 100644 --- a/Connection.cc +++ b/Connection.cc @@ -134,6 +134,23 @@ void Connection::issue_something(double now) { } } + +/** + * Get/Set Style + * Issue a get first, if not found then set + */ +void Connection::issue_getset(double now) { + char key[256]; + // FIXME: generate key distribution here! + string keystr = keygen->generate(lrand48() % options.records); + strcpy(key, keystr.c_str()); + + //true if success, false if not found in db + issue_get(key, now); + +} + + /** * Issue a get request to the server. */ @@ -325,7 +342,11 @@ void Connection::drive_write_machine(double now) { return; } - issue_something(now); + if (options.getset) + issue_getset(now); + else + issue_something(now); + last_tx = now; stats.log_op(op_queue.size()); next_time += iagen->generate(); @@ -370,7 +391,10 @@ void Connection::read_callback() { struct evbuffer *input = bufferevent_get_input(bev); Operation *op = NULL; - bool done, full_read; + bool done, found, full_read; + + //initially assume found (for sets that may come through here) + found = true; if (op_queue.size() == 0) V("Spurious read callback."); @@ -383,23 +407,36 @@ void Connection::read_callback() { case WAITING_FOR_GET: assert(op_queue.size() > 0); - full_read = prot->handle_response(input, done); + full_read = prot->handle_response(input, done, found); + if (!full_read) { return; } else if (done) { + + //if not found and in getset mode, issue set + if (!found && options.getset) + { + char key[256]; + string keystr = op->key; + strcpy(key, keystr.c_str()); + int index = lrand48() % (1024 * 1024); + issue_set(key, &random_char[index], valuesize->generate()); + } + finish_op(op); // sets read_state = IDLE + } break; case WAITING_FOR_SET: assert(op_queue.size() > 0); - if (!prot->handle_response(input, done)) return; + if (!prot->handle_response(input, done, found)) return; finish_op(op); break; case LOADING: assert(op_queue.size() > 0); - if (!prot->handle_response(input, done)) return; + if (!prot->handle_response(input, done, found)) return; loader_completed++; pop_op(); diff --git a/Connection.h b/Connection.h index f19bbe0..36ea758 100644 --- a/Connection.h +++ b/Connection.h @@ -103,6 +103,7 @@ class Connection { void pop_op(); void finish_op(Operation *op); void issue_something(double now = 0.0); + void issue_getset(double now = 0.0); void drive_write_machine(double now = 0.0); // request functions diff --git a/ConnectionOptions.h b/ConnectionOptions.h index e044e16..c7d125c 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -12,6 +12,7 @@ typedef struct { bool binary; bool redis; + bool getset; bool sasl; char username[32]; char password[32]; diff --git a/Protocol.cc b/Protocol.cc index 297a303..c513db0 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -79,7 +79,7 @@ int ProtocolRESP::get_request(const char* key) { * * */ -bool ProtocolRESP::handle_response(evbuffer *input, bool &done) { +bool ProtocolRESP::handle_response(evbuffer *input, bool &done, bool &found) { char *buf = NULL; int len; size_t n_read_out; @@ -110,7 +110,10 @@ bool ProtocolRESP::handle_response(evbuffer *input, bool &done) { data_length = len; //null response => miss if (data_length == -1) { - if (read_state == WAITING_FOR_GET) conn->stats.get_misses++; + if (read_state == WAITING_FOR_GET) { + conn->stats.get_misses++; + found = false; + } read_state = WAITING_FOR_GET; done = true; free(buf); @@ -164,7 +167,7 @@ int ProtocolAscii::set_request(const char* key, const char* value, int len) { /** * Handle an ascii response. */ -bool ProtocolAscii::handle_response(evbuffer *input, bool &done) { +bool ProtocolAscii::handle_response(evbuffer *input, bool &done, bool &found) { char *buf = NULL; int len; size_t n_read_out; @@ -179,7 +182,10 @@ bool ProtocolAscii::handle_response(evbuffer *input, bool &done) { conn->stats.rx_bytes += n_read_out; if (!strncmp(buf, "END", 3)) { - if (read_state == WAITING_FOR_GET) conn->stats.get_misses++; + if (read_state == WAITING_FOR_GET) { + conn->stats.get_misses++; + found = false; + } read_state = WAITING_FOR_GET; done = true; } else if (!strncmp(buf, "VALUE", 5)) { @@ -243,8 +249,8 @@ bool ProtocolBinary::setup_connection_w() { bool ProtocolBinary::setup_connection_r(evbuffer* input) { if (!opts.sasl) return true; - bool b; - return handle_response(input, b); + bool b,c; + return handle_response(input, b, c); } /** @@ -288,7 +294,7 @@ int ProtocolBinary::set_request(const char* key, const char* value, int len) { * @param input evBuffer to read response from * @return true if consumed, false if not enough data in buffer. */ -bool ProtocolBinary::handle_response(evbuffer *input, bool &done) { +bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found) { // Read the first 24 bytes as a header int length = evbuffer_get_length(input); if (length < 24) return false; @@ -303,6 +309,7 @@ bool ProtocolBinary::handle_response(evbuffer *input, bool &done) { // If something other than success, count it as a miss if (h->opcode == CMD_GET && h->status) { conn->stats.get_misses++; + found = true; } if (unlikely(h->opcode == CMD_SASL)) { diff --git a/Protocol.h b/Protocol.h index 41f4e75..20012e6 100644 --- a/Protocol.h +++ b/Protocol.h @@ -20,7 +20,7 @@ class Protocol { virtual bool setup_connection_r(evbuffer* input) = 0; virtual int get_request(const char* key) = 0; virtual int set_request(const char* key, const char* value, int len) = 0; - virtual bool handle_response(evbuffer* input, bool &done) = 0; + virtual bool handle_response(evbuffer* input, bool &done, bool &found) = 0; protected: options_t opts; @@ -41,7 +41,7 @@ class ProtocolAscii : public Protocol { virtual bool setup_connection_r(evbuffer* input) { return true; } virtual int get_request(const char* key); virtual int set_request(const char* key, const char* value, int len); - virtual bool handle_response(evbuffer* input, bool &done); + virtual bool handle_response(evbuffer* input, bool &done, bool &found); private: enum read_fsm { @@ -65,7 +65,7 @@ class ProtocolBinary : public Protocol { virtual bool setup_connection_r(evbuffer* input); virtual int get_request(const char* key); virtual int set_request(const char* key, const char* value, int len); - virtual bool handle_response(evbuffer* input, bool &done); + virtual bool handle_response(evbuffer* input, bool &done, bool &found); }; class ProtocolRESP : public Protocol { @@ -78,7 +78,7 @@ class ProtocolRESP : public Protocol { virtual bool setup_connection_r(evbuffer* input) { return true; } virtual int get_request(const char* key); virtual int set_request(const char* key, const char* value, int len); - virtual bool handle_response(evbuffer* input, bool &done); + virtual bool handle_response(evbuffer* input, bool &done, bool &found); private: enum read_fsm { diff --git a/README.md b/README.md index dd9c8c1..ae7f40a 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,10 @@ Command-line Options specify multiple servers. --binary Use binary memcached protocol instead of ASCII. --redis Use Redis RESP protocol instead of memchached. + --getset Use getset mode, in getset mode we first issue + a GET and if the response is MISS, then issue + a SET for on that + key following distribution value. -q, --qps=INT Target aggregate QPS. 0 = peak QPS. (default=`0') -t, --time=INT Maximum time to run (seconds). (default=`5') diff --git a/cmdline.ggo b/cmdline.ggo index 1c5f7d6..0cafaab 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -14,6 +14,9 @@ option "server" s "Memcached server hostname[:port]. \ Repeat to specify multiple servers." string multiple option "binary" - "Use binary memcached protocol instead of ASCII." option "redis" - "Use Redis RESP protocol instead of memchached." +option "getset" - "Use getset mode, in getset mode we first issue \ +a GET and if the response is MISS, then issue a SET for on that +key following distribution value." option "qps" q "Target aggregate QPS. 0 = peak QPS." int default="0" option "time" t "Maximum time to run (seconds)." int default="5" diff --git a/mutilate.cc b/mutilate.cc index 3ea2897..f02ca8d 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -1060,6 +1060,10 @@ void args_to_options(options_t* options) { options->binary = args.binary_given; options->redis = args.redis_given; + + //getset mode (first issue get, then set same key if miss) + options->getset = args.getset_given; + options->sasl = args.username_given; if (args.password_given) From 052602f2f204e83d91fe8a76baac9c0b9542adea Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Sat, 5 Nov 2016 17:26:23 -0400 Subject: [PATCH 05/57] finally working with a file as key value input --- Connection.cc | 74 +++++++++++++++++++++++++++++++++++++++++---- Connection.h | 6 ++++ ConnectionOptions.h | 7 ++++- ConnectionStats.h | 8 +++-- Generator.h | 4 +-- Protocol.cc | 5 ++- README.md | 9 ++++++ cmdline.ggo | 10 ++++++ mutilate.cc | 8 +++++ 9 files changed, 118 insertions(+), 13 deletions(-) diff --git a/Connection.cc b/Connection.cc index d934ec9..4fb0d81 100644 --- a/Connection.cc +++ b/Connection.cc @@ -15,6 +15,8 @@ #include "mutilate.h" #include "binary_protocol.h" #include "util.h" +#include +#include /** * Create a new connection to a server endpoint. @@ -27,7 +29,10 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, { valuesize = createGenerator(options.valuesize); keysize = createGenerator(options.keysize); + keygen = new KeyGenerator(keysize, options.records); + if (options.read_file && options.getset) + kvfile.open(options.file_name); if (options.lambda <= 0) { iagen = createGenerator("0"); @@ -140,13 +145,35 @@ void Connection::issue_something(double now) { * Issue a get first, if not found then set */ void Connection::issue_getset(double now) { - char key[256]; // FIXME: generate key distribution here! - string keystr = keygen->generate(lrand48() % options.records); - strcpy(key, keystr.c_str()); + if (!options.read_file && !kvfile.is_open()) + { + string keystr; + char key[256]; + keystr = keygen->generate(lrand48() % options.records); + strcpy(key, keystr.c_str()); + + issue_get(key, now); + } + else + { + string line; + string rKey; + string rvaluelen; + getline(kvfile,line); + stringstream ss(line); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + + + //save valuelen + key_len[rKey] = rvaluelen; + + char key[256]; + strcpy(key, rKey.c_str()); + issue_get(key, now); + } - //true if success, false if not found in db - issue_get(key, now); } @@ -255,6 +282,22 @@ void Connection::finish_op(Operation *op) { last_rx = now; pop_op(); + + //lets check if we should output stats for the window + //Do the binning for percentile outputs + //crude at start + if ((options.misswindow != 0) && ( ((stats.window_gets) % options.misswindow) == 0)) + { + if (stats.window_gets != 0) + { + printf("%lu,%.2f\n",(stats.gets), + ((double)stats.window_get_misses/(double)stats.window_gets)); + stats.window_gets = 0; + stats.window_get_misses = 0; + stats.window_sets = 0; + } + } + drive_write_machine(); } @@ -414,13 +457,32 @@ void Connection::read_callback() { } else if (done) { //if not found and in getset mode, issue set - if (!found && options.getset) + if (!found && options.getset && options.read_file) + { + char key[256]; + char vlen[256]; + string valuelen; + + string keystr = op->key; + strcpy(key, keystr.c_str()); + + int index = lrand48() % (1024 * 1024); + + valuelen = key_len[keystr]; + strcpy(vlen, valuelen.c_str()); + int vl = atoi(vlen); + issue_set(key, &random_char[index], vl); + found = true; + } + else if (!found && options.getset) { char key[256]; string keystr = op->key; strcpy(key, keystr.c_str()); int index = lrand48() % (1024 * 1024); + issue_set(key, &random_char[index], valuesize->generate()); + found = true; } finish_op(op); // sets read_state = IDLE diff --git a/Connection.h b/Connection.h index 36ea758..d40c6f5 100644 --- a/Connection.h +++ b/Connection.h @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -18,6 +20,7 @@ #include "Operation.h" #include "util.h" + #include "Protocol.h" using namespace std; @@ -89,6 +92,9 @@ class Connection { read_state_enum read_state; write_state_enum write_state; + //need to keep value length for read key + map key_len; + ifstream kvfile; // Parameters to track progress of the data loader. int loader_issued, loader_completed; diff --git a/ConnectionOptions.h b/ConnectionOptions.h index c7d125c..8ff5b59 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -9,7 +9,11 @@ typedef struct { double lambda; int qps; int records; - + int misswindow; + int queries; + + char file_name[256]; + bool read_file; bool binary; bool redis; bool getset; @@ -17,6 +21,7 @@ typedef struct { char username[32]; char password[32]; + char hashtype[256]; char keysize[32]; char valuesize[32]; // int keysize; diff --git a/ConnectionStats.h b/ConnectionStats.h index e957c19..9b492b3 100644 --- a/ConnectionStats.h +++ b/ConnectionStats.h @@ -29,7 +29,8 @@ class ConnectionStats { get_sampler(200), set_sampler(200), op_sampler(100), #endif rx_bytes(0), tx_bytes(0), gets(0), sets(0), - get_misses(0), skips(0), sampling(_sampling) {} + get_misses(0), window_gets(0), window_sets(0), + window_get_misses(0), skips(0), sampling(_sampling) {} #ifdef USE_ADAPTIVE_SAMPLER AdaptiveSampler get_sampler; @@ -47,14 +48,15 @@ class ConnectionStats { uint64_t rx_bytes, tx_bytes; uint64_t gets, sets, get_misses; + uint64_t window_gets, window_sets, window_get_misses; uint64_t skips; double start, stop; bool sampling; - void log_get(Operation& op) { if (sampling) get_sampler.sample(op); gets++; } - void log_set(Operation& op) { if (sampling) set_sampler.sample(op); sets++; } + void log_get(Operation& op) { if (sampling) get_sampler.sample(op); window_gets++; gets++; } + void log_set(Operation& op) { if (sampling) set_sampler.sample(op); window_sets++; sets++; } void log_op (double op) { if (sampling) op_sampler.sample(op); } double get_qps() { diff --git a/Generator.h b/Generator.h index eb598b1..16de214 100644 --- a/Generator.h +++ b/Generator.h @@ -197,8 +197,8 @@ class KeyGenerator { double U = (double) h / ULLONG_MAX; double G = g->generate(U); int keylen = MAX(round(G), floor(log10(max)) + 1); - char key[256]; - snprintf(key, 256, "%0*" PRIu64, keylen, ind); + char key[250]; //memcached limit of 255 chars + snprintf(key, 250, "%0*" PRIu64, keylen, ind); // D("%d = %s", ind, key); return std::string(key); diff --git a/Protocol.cc b/Protocol.cc index c513db0..36413cc 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -112,6 +112,7 @@ bool ProtocolRESP::handle_response(evbuffer *input, bool &done, bool &found) { if (data_length == -1) { if (read_state == WAITING_FOR_GET) { conn->stats.get_misses++; + conn->stats.window_get_misses++; found = false; } read_state = WAITING_FOR_GET; @@ -184,6 +185,7 @@ bool ProtocolAscii::handle_response(evbuffer *input, bool &done, bool &found) { if (!strncmp(buf, "END", 3)) { if (read_state == WAITING_FOR_GET) { conn->stats.get_misses++; + conn->stats.window_get_misses++; found = false; } read_state = WAITING_FOR_GET; @@ -309,7 +311,8 @@ bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found) { // If something other than success, count it as a miss if (h->opcode == CMD_GET && h->status) { conn->stats.get_misses++; - found = true; + conn->stats.window_get_misses++; + found = false; } if (unlikely(h->opcode == CMD_SASL)) { diff --git a/README.md b/README.md index ae7f40a..91b7caf 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ Command-line Options -q, --qps=INT Target aggregate QPS. 0 = peak QPS. (default=`0') -t, --time=INT Maximum time to run (seconds). (default=`5') + --read_file=STRING Read keys from file. (default=`') -K, --keysize=STRING Length of memcached keys (distribution). (default=`30') -V, --valuesize=STRING Length of memcached values (distribution). @@ -148,6 +149,14 @@ Command-line Options multiple memcached servers are given, this number is divided by the number of servers. (default=`10000') + -m, --misswindow=INT Window for recording misses, used to find + steady state, no window by default, which + gives us summary stats in total + (default=`0') + -N, --queries=INT Number of queries to make. 0 is unlimited + (default) If multiple memcached servers are + given, this number is divided by the number + of servers. (default=`0') -u, --update=FLOAT Ratio of set:get commands. (default=`0.0') Advanced options: diff --git a/cmdline.ggo b/cmdline.ggo index 0cafaab..5e0bbac 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -20,6 +20,8 @@ key following distribution value." option "qps" q "Target aggregate QPS. 0 = peak QPS." int default="0" option "time" t "Maximum time to run (seconds)." int default="5" +option "read_file" - "Read keys from file." string default="" + option "keysize" K "Length of memcached keys (distribution)." string default="30" option "valuesize" V "Length of memcached values (distribution)." @@ -29,6 +31,14 @@ option "records" r "Number of memcached records to use. \ If multiple memcached servers are given, this number is divided \ by the number of servers." int default="10000" +option "misswindow" m "Window for recording misses, used to find \ + steady state, no window by default, which \ + gives us summary stats in total" int default="0" + +option "queries" N "Number of queries to make. 0 is unlimited (default) \ +If multiple memcached servers are given, this number is divided \ +by the number of servers." int default="0" + option "update" u "Ratio of set:get commands." float default="0.0" text "\nAdvanced options:" diff --git a/mutilate.cc b/mutilate.cc index f02ca8d..5efbab3 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -1058,8 +1058,16 @@ void args_to_options(options_t* options) { // else options->records = args.records_arg / options->server_given; + options->queries = args.queries_arg / options->server_given; + + options->misswindow = args.misswindow_arg; + options->binary = args.binary_given; options->redis = args.redis_given; + + options->read_file = args.read_file_given; + if (args.read_file_given) + strcpy(options->file_name, args.read_file_arg); //getset mode (first issue get, then set same key if miss) options->getset = args.getset_given; From 1be01698a51008fdca80b9ae84f3452a5922a9d9 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 14 Dec 2016 13:22:03 -0500 Subject: [PATCH 06/57] updated protocol, fixed a nice bug --- Connection.cc | 127 +++++++++++++++++----------- Connection.h | 4 + ConnectionOptions.h | 3 +- Generator.h | 2 +- Protocol.cc | 196 +++++++++++++++++++++++++++++++------------- Protocol.h | 4 +- cmdline.ggo | 7 ++ mutilate.cc | 10 ++- 8 files changed, 242 insertions(+), 111 deletions(-) diff --git a/Connection.cc b/Connection.cc index 4fb0d81..b5ada88 100644 --- a/Connection.cc +++ b/Connection.cc @@ -47,6 +47,8 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, last_tx = last_rx = 0.0; + last_miss = 0; + bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev, bev_read_cb, bev_write_cb, bev_event_cb, this); bufferevent_enable(bev, EV_READ | EV_WRITE); @@ -145,33 +147,68 @@ void Connection::issue_something(double now) { * Issue a get first, if not found then set */ void Connection::issue_getset(double now) { - // FIXME: generate key distribution here! - if (!options.read_file && !kvfile.is_open()) + + //if the last request was miss we need to issue a set + if (last_miss) { - string keystr; - char key[256]; - keystr = keygen->generate(lrand48() % options.records); - strcpy(key, keystr.c_str()); - - issue_get(key, now); + //if not found and in getset mode, issue set + if (options.read_file) + { + char key[256]; + char vlen[256]; + string valuelen; + + string keystr = string(last_key); + strcpy(key, keystr.c_str()); + + int index = lrand48() % (1024 * 1024); + valuelen = key_len[keystr]; + strcpy(vlen, valuelen.c_str()); + int vl = atoi(vlen); + + issue_set(key, &random_char[index], vl); + } + else + { + char key[256]; + string keystr = string(last_key); + strcpy(key, keystr.c_str()); + + int index = lrand48() % (1024 * 1024); + + issue_set(key, &random_char[index], valuesize->generate()); + } + last_miss = 0; } else { - string line; - string rKey; - string rvaluelen; - getline(kvfile,line); - stringstream ss(line); - getline( ss, rKey, ',' ); - getline( ss, rvaluelen, ',' ); - - - //save valuelen - key_len[rKey] = rvaluelen; - - char key[256]; - strcpy(key, rKey.c_str()); - issue_get(key, now); + if (!options.read_file && !kvfile.is_open()) + { + string keystr; + char key[256]; + keystr = keygen->generate(lrand48() % options.records); + strcpy(key, keystr.c_str()); + + issue_get(key, now); + } + else + { + string line; + string rKey; + string rvaluelen; + getline(kvfile,line); + stringstream ss(line); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + + + //save valuelen + key_len[rKey] = rvaluelen; + + char key[256]; + strcpy(key, rKey.c_str()); + issue_get(key, now); + } } @@ -290,7 +327,7 @@ void Connection::finish_op(Operation *op) { { if (stats.window_gets != 0) { - printf("%lu,%.2f\n",(stats.gets), + printf("%lu,%.4f\n",(stats.gets), ((double)stats.window_get_misses/(double)stats.window_gets)); stats.window_gets = 0; stats.window_get_misses = 0; @@ -301,14 +338,24 @@ void Connection::finish_op(Operation *op) { drive_write_machine(); } + /** * Check if our testing is done and we should exit. */ bool Connection::check_exit_condition(double now) { if (read_state == INIT_READ) return false; if (now == 0.0) now = get_time(); - if (now > start_time + options.time) return true; + if ((options.queries == 0) && + (now > start_time + options.time)) + { + return true; + } if (options.loadonly && read_state == IDLE) return true; + if (options.queries != 0 && + (((long unsigned)options.queries) == stats.gets)) + { + return true; + } return false; } @@ -456,41 +503,25 @@ void Connection::read_callback() { return; } else if (done) { - //if not found and in getset mode, issue set - if (!found && options.getset && options.read_file) + if (!found && options.getset && stats.gets >= 1) { - char key[256]; - char vlen[256]; - string valuelen; - string keystr = op->key; - strcpy(key, keystr.c_str()); - - int index = lrand48() % (1024 * 1024); - - valuelen = key_len[keystr]; - strcpy(vlen, valuelen.c_str()); - int vl = atoi(vlen); - issue_set(key, &random_char[index], vl); - found = true; + strcpy(last_key, keystr.c_str()); + last_miss = 1; } - else if (!found && options.getset) + else if (options.getset) { - char key[256]; string keystr = op->key; - strcpy(key, keystr.c_str()); - int index = lrand48() % (1024 * 1024); - - issue_set(key, &random_char[index], valuesize->generate()); - found = true; + strcpy(last_key, keystr.c_str()); + last_miss = 0; } - finish_op(op); // sets read_state = IDLE } break; case WAITING_FOR_SET: + assert(op_queue.size() > 0); if (!prot->handle_response(input, done, found)) return; finish_op(op); diff --git a/Connection.h b/Connection.h index d40c6f5..d880295 100644 --- a/Connection.h +++ b/Connection.h @@ -98,6 +98,10 @@ class Connection { // Parameters to track progress of the data loader. int loader_issued, loader_completed; + //was the last op a miss + char last_key[256]; + int last_miss; + Protocol *prot; Generator *valuesize; Generator *keysize; diff --git a/ConnectionOptions.h b/ConnectionOptions.h index 8ff5b59..88ce5fd 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -11,10 +11,11 @@ typedef struct { int records; int misswindow; int queries; - + int assoc; char file_name[256]; bool read_file; bool binary; + bool use_assoc; bool redis; bool getset; bool sasl; diff --git a/Generator.h b/Generator.h index 16de214..72a0156 100644 --- a/Generator.h +++ b/Generator.h @@ -198,7 +198,7 @@ class KeyGenerator { double G = g->generate(U); int keylen = MAX(round(G), floor(log10(max)) + 1); char key[250]; //memcached limit of 255 chars - snprintf(key, 250, "%0*" PRIu64, keylen, ind); + snprintf(key, keylen, "%lu" , ind); // D("%d = %s", ind, key); return std::string(key); diff --git a/Protocol.cc b/Protocol.cc index 36413cc..edf4b0a 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -39,27 +39,124 @@ * 2. The actual string data. * 3. A final CRLF. * + * DBG code + * fprintf(stderr,"--\n"); + * fprintf(stderr,"*3\r\n$3\r\nSET\r\n$%lu\r\n%s\r\n$%d\r\n%s\r\n", + * strlen(key),key,len,val); + * fprintf(stderr,"--\n"); + * + */ +int ProtocolRESP::set_request(const char* key, const char* value, int len) { + + //need to make the real value + char *val = (char*)malloc(len*sizeof(char)+1); + memset(val, 'a', len); + val[len] = '\0'; + + //check if we should use assoc + if (opts.use_assoc && strlen(key) > ((unsigned int)(opts.assoc+1)) ) + { + int l = hset_request(key,val,len); + free(val); + return l; + } + + else + { + int l; + l = evbuffer_add_printf(bufferevent_get_output(bev), + "*3\r\n$3\r\nSET\r\n$%lu\r\n%s\r\n$%d\r\n%s\r\n", + strlen(key),key,len,val); + l += len + 2; + if (read_state == IDLE) read_state = WAITING_FOR_END; + free(val); + return l; + } + +} + +/** + * Send a RESP get request. + */ +int ProtocolRESP::get_request(const char* key) { + + //check if we should use assoc + if (opts.use_assoc && strlen(key) > ((unsigned int)(opts.assoc+1)) ) + return hget_request(key); + else + { + int l; + l = evbuffer_add_printf(bufferevent_get_output(bev), + "*2\r\n$3\r\nGET\r\n$%lu\r\n%s\r\n",strlen(key),key); + + if (read_state == IDLE) read_state = WAITING_FOR_GET; + return l; + } +} + +/** + * RESP HSET + * HSET myhash field1 "Hello" + * We break the key by last assoc bytes for now... + * We are guarenteed a key of at least assoc+1 bytes...but + * the vast vast majority are going to be 20 bytes. + * + * DBG code + * fprintf(stderr,"--\n"); + * fprintf(stderr,"*4\r\n$4\r\nHSET\r\n$%lu\r\n%s\r\n$%lu\r\n%s\r\n$%d\r\n%s\r\n", + * strlen(hash),hash,strlen(field),field,len,value); + * fprintf(stderr,"--\n"); */ -int ProtocolRESP::set_request(const char* key, const char* value, int value_len) { + +int ProtocolRESP::hset_request(const char* key, const char* value, int len) { int l; + //hash is first n-assoc bytes + //field is last assoc bytes + //value is value + int assoc = opts.assoc; + char* hash = (char*)malloc(sizeof(char)*((strlen(key)-assoc)+1)); + char* field = (char*)malloc(sizeof(char)*(assoc+1)); + strncpy(hash, key, strlen(key)-assoc); + strncpy(field,key+strlen(key)-assoc,assoc); + hash[strlen(key)-assoc] = '\0'; + field[assoc] = '\0'; l = evbuffer_add_printf(bufferevent_get_output(bev), - "*3\r\n$3\r\nSET\r\n$%lu\r\n%s\r\n$%lu\r\n%s\r\n", - strlen(key),key,strlen(value),value); + "*4\r\n$4\r\nHSET\r\n$%lu\r\n%s\r\n$%lu\r\n%s\r\n$%d\r\n%s\r\n", + strlen(hash),hash,strlen(field),field,len,value); + l += len + 2; if (read_state == IDLE) read_state = WAITING_FOR_END; + free(hash); + free(field); return l; } /** - * Send a RESP get request. + * RESP HGET + * HGET myhash field1 + * We break the key by last assoc bytes for now... + * We are guarenteed a key of at least assoc+1 bytes...but + * the vast vast majority are going to be 20 bytes. */ -int ProtocolRESP::get_request(const char* key) { +int ProtocolRESP::hget_request(const char* key) { int l; + //hash is first n-assoc bytes + //field is last assoc bytes + int assoc = opts.assoc; + char* hash = (char*)malloc(sizeof(char)*((strlen(key)-assoc)+1)); + char* field = (char*)malloc(sizeof(char)*(assoc+1)); + strncpy(hash, key, strlen(key)-assoc); + strncpy(field,key+strlen(key)-assoc,assoc); + hash[strlen(key)-assoc] = '\0'; + field[assoc] = '\0'; l = evbuffer_add_printf(bufferevent_get_output(bev), - "*2\r\n$3\r\nGET\r\n$%lu\r\n%s\r\n",strlen(key),key); + "*3\r\n$4\r\nHGET\r\n$%lu\r\n%s\r\n$%lu\r\n%s\r\n", + strlen(hash),hash,strlen(field),field); if (read_state == IDLE) read_state = WAITING_FOR_GET; + free(hash); + free(field); return l; } @@ -81,63 +178,46 @@ int ProtocolRESP::get_request(const char* key) { */ bool ProtocolRESP::handle_response(evbuffer *input, bool &done, bool &found) { char *buf = NULL; - int len; size_t n_read_out; - switch (read_state) { - - case WAITING_FOR_GET: - case WAITING_FOR_END: - buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); - if (buf == NULL) return false; - - conn->stats.rx_bytes += n_read_out + 2; //don't forget CRLF - - //this is checking if we have gotten the "END" data message - //in memcached it is simply END, but in redis everything - //is sent as a bulk string, so we check for a $ - if (buf[0] != '$') { - //if (read_state == WAITING_FOR_GET) conn->stats.get_misses++; - read_state = WAITING_FOR_GET; - free(buf); + buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); + if (buf == NULL) + { + done = false; + return false; + } + conn->stats.rx_bytes += n_read_out; + + //RESP null response => miss + if (!strncmp(buf,"$-1",3)) + { + conn->stats.get_misses++; + conn->stats.window_get_misses++; + found = false; + + } + //HSET or SET response was good, just consume the input and move on + //with our lives + else if (!strncmp(buf,"+OK",3) || !strncmp(buf,":1",2) || !strncmp(buf,":0",2) ) + { + found = false; done = true; - } - else { - //A bulk string resp: "$6\r\nfoobar\r\n" - //1. Consume the first "$##\r\n", evbuffer_readln returns - // "$##", where ## is the number of bytes in the bulk string - sscanf(buf, "$%d", &len); - data_length = len; - //null response => miss - if (data_length == -1) { - if (read_state == WAITING_FOR_GET) { - conn->stats.get_misses++; - conn->stats.window_get_misses++; - found = false; - } - read_state = WAITING_FOR_GET; - done = true; - free(buf); - } - else - { - free(buf); - - //2. Consume the next "foobar" - buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); - conn->stats.rx_bytes += n_read_out + 2; //don't forget CRLF - - free(buf); - done = true; - } - } - return true; - - - default: printf("state: %d\n", read_state); DIE("Unimplemented!"); } + //else we got a hit + else + { + if (buf) + free(buf); + // Consume the next "foobar" + buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); + conn->stats.rx_bytes += n_read_out; + found = true; + } + done = true; + free(buf); + return true; - DIE("Shouldn't ever reach here..."); + } /** diff --git a/Protocol.h b/Protocol.h index 20012e6..78d0cdd 100644 --- a/Protocol.h +++ b/Protocol.h @@ -78,6 +78,8 @@ class ProtocolRESP : public Protocol { virtual bool setup_connection_r(evbuffer* input) { return true; } virtual int get_request(const char* key); virtual int set_request(const char* key, const char* value, int len); + virtual int hget_request(const char* key); + virtual int hset_request(const char* key, const char* value, int len); virtual bool handle_response(evbuffer* input, bool &done, bool &found); private: @@ -85,7 +87,7 @@ class ProtocolRESP : public Protocol { IDLE, WAITING_FOR_GET, WAITING_FOR_GET_DATA, - WAITING_FOR_END, + WAITING_FOR_END }; read_fsm read_state; diff --git a/cmdline.ggo b/cmdline.ggo index 5e0bbac..c89cf33 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -17,6 +17,13 @@ option "redis" - "Use Redis RESP protocol instead of memchached." option "getset" - "Use getset mode, in getset mode we first issue \ a GET and if the response is MISS, then issue a SET for on that key following distribution value." + +option "assoc" - "We create hash tables by taking the truncating the \ + key by b bytes. The n-b bytes are the key for redis, in the original \ + (key,value). The value is a hash table and we acess field \ + b to get the value. Essentially this makes redis n-way \ + associative cache. Only works in redis mode. For small key \ + sizes we just use normal method of (key,value) store. No hash table." int default="4" option "qps" q "Target aggregate QPS. 0 = peak QPS." int default="0" option "time" t "Maximum time to run (seconds)." int default="5" diff --git a/mutilate.cc b/mutilate.cc index 5efbab3..09ff3f0 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -1061,10 +1061,16 @@ void args_to_options(options_t* options) { options->queries = args.queries_arg / options->server_given; options->misswindow = args.misswindow_arg; - + + options->use_assoc = args.assoc_given; + options->assoc = args.assoc_arg; + options->binary = args.binary_given; options->redis = args.redis_given; - + + if (options->use_assoc && !options->redis) + DIE("assoc must be used with redis"); + options->read_file = args.read_file_given; if (args.read_file_given) strcpy(options->file_name, args.read_file_arg); From 393b78b5c6037b71ea3e0077fa43fdfdf5888261 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Tue, 25 Apr 2017 13:39:01 -0400 Subject: [PATCH 07/57] added delete option --- Connection.cc | 47 +++++++++++++++++++++++++++++++++++++++++++++ Connection.h | 2 ++ ConnectionOptions.h | 1 + Operation.h | 2 +- Protocol.cc | 30 +++++++++++++++++++++++++++++ Protocol.h | 7 ++++++- cmdline.ggo | 4 ++++ mutilate.cc | 4 ++++ 8 files changed, 95 insertions(+), 2 deletions(-) diff --git a/Connection.cc b/Connection.cc index b5ada88..9474ea2 100644 --- a/Connection.cc +++ b/Connection.cc @@ -147,6 +147,15 @@ void Connection::issue_something(double now) { * Issue a get first, if not found then set */ void Connection::issue_getset(double now) { + + if (options.queries != 0 && + (((long unsigned)options.queries/2) == stats.gets) && + options.delete90 && + stats.gets > 1) + { + issue_delete90(now); + } + //if the last request was miss we need to issue a set if (last_miss) @@ -247,6 +256,37 @@ void Connection::issue_get(const char* key, double now) { if (read_state != LOADING) stats.tx_bytes += l; } +/** + * Issue a delete90 request to the server. + */ +void Connection::issue_delete90(double now) { + Operation op; + int l; + +#if HAVE_CLOCK_GETTIME + op.start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + op.start_time = tv_to_double(&now_tv); +#else + op.start_time = get_time(); +#endif + } else { + op.start_time = now; + } +#endif + + op.type = Operation::DELETE; + op_queue.push(op); + + if (read_state == IDLE) read_state = WAITING_FOR_DELETE; + l = prot->delete90_request(); + if (read_state != LOADING) stats.tx_bytes += l; +} + /** * Issue a set request to the server. */ @@ -287,6 +327,7 @@ void Connection::pop_op() { switch (op.type) { case Operation::GET: read_state = WAITING_FOR_GET; break; case Operation::SET: read_state = WAITING_FOR_SET; break; + case Operation::DELETE: read_state = WAITING_FOR_DELETE; break; default: DIE("Not implemented."); } } @@ -314,6 +355,7 @@ void Connection::finish_op(Operation *op) { switch (op->type) { case Operation::GET: stats.log_get(*op); break; case Operation::SET: stats.log_set(*op); break; + case Operation::DELETE: break; default: DIE("Not implemented."); } @@ -526,6 +568,11 @@ void Connection::read_callback() { if (!prot->handle_response(input, done, found)) return; finish_op(op); break; + + case WAITING_FOR_DELETE: + if (!prot->handle_response(input,done,found)) return; + finish_op(op); + break; case LOADING: assert(op_queue.size() > 0); diff --git a/Connection.h b/Connection.h index d880295..587e8ce 100644 --- a/Connection.h +++ b/Connection.h @@ -78,6 +78,7 @@ class Connection { IDLE, WAITING_FOR_GET, WAITING_FOR_SET, + WAITING_FOR_DELETE, MAX_READ_STATE, }; @@ -121,6 +122,7 @@ class Connection { void issue_get(const char* key, double now = 0.0); void issue_set(const char* key, const char* value, int length, double now = 0.0); + void issue_delete90(double now = 0.0); // protocol fucntions int set_request_ascii(const char* key, const char* value, int length); diff --git a/ConnectionOptions.h b/ConnectionOptions.h index 88ce5fd..fa60868 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -18,6 +18,7 @@ typedef struct { bool use_assoc; bool redis; bool getset; + bool delete90; bool sasl; char username[32]; char password[32]; diff --git a/Operation.h b/Operation.h index b594b17..a0d8517 100644 --- a/Operation.h +++ b/Operation.h @@ -11,7 +11,7 @@ class Operation { double start_time, end_time; enum type_enum { - GET, SET, SASL + GET, SET, DELETE, SASL }; type_enum type; diff --git a/Protocol.cc b/Protocol.cc index edf4b0a..7cf7031 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -160,6 +160,18 @@ int ProtocolRESP::hget_request(const char* key) { return l; } +/** + * RESP DELETE 90 - delete 90 percent of keys in DB + */ +int ProtocolRESP::delete90_request() { + int l; + l = evbuffer_add_printf(bufferevent_get_output(bev), + "*1\r\n$8\r\nFLUSHALL\r\n"); + + if (read_state == IDLE) read_state = WAITING_FOR_DELETE; + return l; +} + /** * Handle a RESP response. * @@ -245,6 +257,15 @@ int ProtocolAscii::set_request(const char* key, const char* value, int len) { return l; } +/** WARNING UNIMPLEMENTED **/ +int ProtocolAscii::delete90_request() { + int l; + l = evbuffer_add_printf(bufferevent_get_output(bev), + "*1\r\n$8\r\nFLUSHALL\r\n"); + + return l; +} + /** * Handle an ascii response. */ @@ -370,6 +391,15 @@ int ProtocolBinary::set_request(const char* key, const char* value, int len) { return 24 + ntohl(h.body_len); } +/** WARNING UNIMPLEMENTED **/ +int ProtocolBinary::delete90_request() { + int l; + l = evbuffer_add_printf(bufferevent_get_output(bev), + "*1\r\n$8\r\nFLUSHALL\r\n"); + + return l; +} + /** * Tries to consume a binary response (in its entirety) from an evbuffer. * diff --git a/Protocol.h b/Protocol.h index 78d0cdd..91e480a 100644 --- a/Protocol.h +++ b/Protocol.h @@ -20,6 +20,7 @@ class Protocol { virtual bool setup_connection_r(evbuffer* input) = 0; virtual int get_request(const char* key) = 0; virtual int set_request(const char* key, const char* value, int len) = 0; + virtual int delete90_request() = 0; virtual bool handle_response(evbuffer* input, bool &done, bool &found) = 0; protected: @@ -41,6 +42,7 @@ class ProtocolAscii : public Protocol { virtual bool setup_connection_r(evbuffer* input) { return true; } virtual int get_request(const char* key); virtual int set_request(const char* key, const char* value, int len); + virtual int delete90_request(); virtual bool handle_response(evbuffer* input, bool &done, bool &found); private: @@ -48,7 +50,7 @@ class ProtocolAscii : public Protocol { IDLE, WAITING_FOR_GET, WAITING_FOR_GET_DATA, - WAITING_FOR_END, + WAITING_FOR_END }; read_fsm read_state; @@ -65,6 +67,7 @@ class ProtocolBinary : public Protocol { virtual bool setup_connection_r(evbuffer* input); virtual int get_request(const char* key); virtual int set_request(const char* key, const char* value, int len); + virtual int delete90_request(); virtual bool handle_response(evbuffer* input, bool &done, bool &found); }; @@ -80,6 +83,7 @@ class ProtocolRESP : public Protocol { virtual int set_request(const char* key, const char* value, int len); virtual int hget_request(const char* key); virtual int hset_request(const char* key, const char* value, int len); + virtual int delete90_request(); virtual bool handle_response(evbuffer* input, bool &done, bool &found); private: @@ -87,6 +91,7 @@ class ProtocolRESP : public Protocol { IDLE, WAITING_FOR_GET, WAITING_FOR_GET_DATA, + WAITING_FOR_DELETE, WAITING_FOR_END }; diff --git a/cmdline.ggo b/cmdline.ggo index c89cf33..a6e4c35 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -17,6 +17,10 @@ option "redis" - "Use Redis RESP protocol instead of memchached." option "getset" - "Use getset mode, in getset mode we first issue \ a GET and if the response is MISS, then issue a SET for on that key following distribution value." +option "delete90" - "Delete 90 percent of keys after halfway through \ + the workload, used to model Rumbel et. al. USENIX \ + FAST '14 workloads. MUST BE IN GETSET MODE and + have a set number of queries" option "assoc" - "We create hash tables by taking the truncating the \ key by b bytes. The n-b bytes are the key for redis, in the original \ diff --git a/mutilate.cc b/mutilate.cc index 09ff3f0..e54f24b 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -1077,6 +1077,10 @@ void args_to_options(options_t* options) { //getset mode (first issue get, then set same key if miss) options->getset = args.getset_given; + //delete 90 percent of keys after halfway + //model workload in Rumble and Ousterhout - log structured memory + //for dram based storage + options->delete90 = args.delete90_given; options->sasl = args.username_given; From e76e69d93932f09d6ebe0791d80695ab97edf1a1 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Tue, 10 Oct 2017 15:05:52 -0400 Subject: [PATCH 08/57] updated for trace output --- Connection.cc | 49 ++++++++++++++++++++++++++++++++++++++++++--- Connection.h | 9 +++++++++ ConnectionOptions.h | 1 + SConstruct | 2 +- cmdline.ggo | 1 + mutilate.cc | 3 +++ 6 files changed, 61 insertions(+), 4 deletions(-) diff --git a/Connection.cc b/Connection.cc index 9474ea2..6077741 100644 --- a/Connection.cc +++ b/Connection.cc @@ -17,6 +17,8 @@ #include "util.h" #include #include +#include +#include /** * Create a new connection to a server endpoint. @@ -128,10 +130,13 @@ void Connection::start_loading() { * Issue either a get or set request to the server according to our probability distribution. */ void Connection::issue_something(double now) { + char skey[256]; char key[256]; // FIXME: generate key distribution here! string keystr = keygen->generate(lrand48() % options.records); - strcpy(key, keystr.c_str()); + strcpy(skey, keystr.c_str()); + strcpy(key, options.prefix); + strcat(key,skey); if (drand48() < options.update) { int index = lrand48() % (1024 * 1024); @@ -174,6 +179,7 @@ void Connection::issue_getset(double now) { valuelen = key_len[keystr]; strcpy(vlen, valuelen.c_str()); int vl = atoi(vlen); + issue_set(key, &random_char[index], vl); } @@ -195,8 +201,16 @@ void Connection::issue_getset(double now) { { string keystr; char key[256]; + char skey[256]; keystr = keygen->generate(lrand48() % options.records); - strcpy(key, keystr.c_str()); + strcpy(skey, keystr.c_str()); + strcpy(key,options.prefix); + strcat(key,skey); + + char log[256]; + int length = valuesize->generate(); + sprintf(log,"%s,%d\n",key,length); + write(2,log,strlen(log)); issue_get(key, now); } @@ -215,7 +229,10 @@ void Connection::issue_getset(double now) { key_len[rKey] = rvaluelen; char key[256]; - strcpy(key, rKey.c_str()); + char skey[256]; + strcpy(skey, rKey.c_str()); + strcpy(key,options.prefix); + strcat(key,skey); issue_get(key, now); } } @@ -247,6 +264,9 @@ void Connection::issue_get(const char* key, double now) { } #endif + //record before rx + //r_vsize = stats.rx_bytes % 100000; + op.key = string(key); op.type = Operation::GET; op_queue.push(op); @@ -302,9 +322,18 @@ void Connection::issue_set(const char* key, const char* value, int length, else op.start_time = now; #endif + //record value size + //r_vsize = length; + //r_appid = key[0] - '0'; + //const char* kptr = key; + //kptr += 2; + //r_key = atoi(kptr); + //r_ksize = strlen(kptr); + op.type = Operation::SET; op_queue.push(op); + if (read_state == IDLE) read_state = WAITING_FOR_SET; l = prot->set_request(key, value, length); if (read_state != LOADING) stats.tx_bytes += l; @@ -557,6 +586,13 @@ void Connection::read_callback() { strcpy(last_key, keystr.c_str()); last_miss = 0; } + + + //char log[256]; + //sprintf(log,"%f,%d,%d,%d,%d,%d,%d\n", + // r_time,r_appid,r_type,r_ksize,r_vsize,r_key,r_hit); + //write(2,log,strlen(log)); + finish_op(op); // sets read_state = IDLE } @@ -566,6 +602,13 @@ void Connection::read_callback() { assert(op_queue.size() > 0); if (!prot->handle_response(input, done, found)) return; + + + //char log[256]; + //sprintf(log,"%f,%d,%d,%d,%d,%d,%d\n", + // r_time,r_appid,r_type,r_ksize,r_vsize,r_key,r_hit); + //write(2,log,strlen(log)); + finish_op(op); break; diff --git a/Connection.h b/Connection.h index 587e8ce..d346d11 100644 --- a/Connection.h +++ b/Connection.h @@ -103,6 +103,15 @@ class Connection { char last_key[256]; int last_miss; + //trace format variables + double r_time; // time in seconds + int r_appid; // prefix minus ':' char + int r_type; //1 = get, 2 = set + int r_ksize; //key size + int r_vsize; //-1 or size of value if hit + int r_key; //op->key as int + int r_hit; //1 if hit, 0 if miss + Protocol *prot; Generator *valuesize; Generator *keysize; diff --git a/ConnectionOptions.h b/ConnectionOptions.h index fa60868..83efd90 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -23,6 +23,7 @@ typedef struct { char username[32]; char password[32]; + char prefix[256]; char hashtype[256]; char keysize[32]; char valuesize[32]; diff --git a/SConstruct b/SConstruct index 57d0054..d220aee 100644 --- a/SConstruct +++ b/SConstruct @@ -8,7 +8,7 @@ env['HAVE_POSIX_BARRIER'] = True env.Append(CPPPATH = ['/usr/local/include', '/opt/local/include']) env.Append(LIBPATH = ['/opt/local/lib']) -env.Append(CCFLAGS = '-std=c++11 -D_GNU_SOURCE') +env.Append(CCFLAGS = '-std=c++11 -D_GNU_SOURCE ') if sys.platform == 'darwin': env['CC'] = 'clang' env['CXX'] = 'clang++' diff --git a/cmdline.ggo b/cmdline.ggo index a6e4c35..c9fe0f5 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -17,6 +17,7 @@ option "redis" - "Use Redis RESP protocol instead of memchached." option "getset" - "Use getset mode, in getset mode we first issue \ a GET and if the response is MISS, then issue a SET for on that key following distribution value." +option "prefix" - "Prefix all keys with a string (helps with multi-tennant eval)" string option "delete90" - "Delete 90 percent of keys after halfway through \ the workload, used to model Rumbel et. al. USENIX \ FAST '14 workloads. MUST BE IN GETSET MODE and diff --git a/mutilate.cc b/mutilate.cc index e54f24b..0b86597 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -1075,6 +1075,9 @@ void args_to_options(options_t* options) { if (args.read_file_given) strcpy(options->file_name, args.read_file_arg); + if (args.prefix_given) + strcpy(options->prefix,args.prefix_arg); + //getset mode (first issue get, then set same key if miss) options->getset = args.getset_given; //delete 90 percent of keys after halfway From a14df422413fec30ea0ed09063b39f49e31c727c Mon Sep 17 00:00:00 2001 From: junyaoy Date: Fri, 22 Feb 2019 09:27:02 -0500 Subject: [PATCH 09/57] updated nil values --- Connection.cc | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Connection.cc b/Connection.cc index 6077741..3bdcf20 100644 --- a/Connection.cc +++ b/Connection.cc @@ -132,11 +132,14 @@ void Connection::start_loading() { void Connection::issue_something(double now) { char skey[256]; char key[256]; + memset(key,0,256); + memset(skey,0,256); // FIXME: generate key distribution here! string keystr = keygen->generate(lrand48() % options.records); - strcpy(skey, keystr.c_str()); - strcpy(key, options.prefix); - strcat(key,skey); + strncpy(skey, keystr.c_str(),strlen(keystr.c_str())); + if (args.prefix_given) + strncpy(key, options.prefix,strlen(options.prefix)); + strncat(key,skey,strlen(skey)); if (drand48() < options.update) { int index = lrand48() % (1024 * 1024); @@ -202,10 +205,13 @@ void Connection::issue_getset(double now) { string keystr; char key[256]; char skey[256]; + memset(key,0,256); + memset(skey,0,256); keystr = keygen->generate(lrand48() % options.records); - strcpy(skey, keystr.c_str()); - strcpy(key,options.prefix); - strcat(key,skey); + strncpy(skey, keystr.c_str(),strlen(keystr.c_str())); + if (args.prefix_given) + strncpy(key, options.prefix,strlen(options.prefix)); + strncat(key,skey,strlen(skey)); char log[256]; int length = valuesize->generate(); @@ -230,9 +236,12 @@ void Connection::issue_getset(double now) { char key[256]; char skey[256]; - strcpy(skey, rKey.c_str()); - strcpy(key,options.prefix); - strcat(key,skey); + memset(key,0,256); + memset(skey,0,256); + strncpy(skey, rKey.c_str(),strlen(rKey.c_str())); + if (args.prefix_given) + strncpy(key, options.prefix,strlen(options.prefix)); + strncat(key,skey,strlen(skey)); issue_get(key, now); } } From ba0349cb0512533e1d8d44d57e47831d8e70256b Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Fri, 22 Feb 2019 09:28:13 -0500 Subject: [PATCH 10/57] updated samples --- AdaptiveSampler.h | 11 +-- Connection.cc | 80 ++++++++++++------- ConnectionStats.h | 41 +++++----- Operation.h | 1 - Protocol.cc | 197 +++++++++++++++++++++++++++++++++++++--------- Protocol.h | 8 +- SConstruct | 6 +- 7 files changed, 244 insertions(+), 100 deletions(-) diff --git a/AdaptiveSampler.h b/AdaptiveSampler.h index e6efbc5..e2f08a6 100644 --- a/AdaptiveSampler.h +++ b/AdaptiveSampler.h @@ -73,8 +73,8 @@ template class AdaptiveSampler { } void print_header() { - printf("#%-6s %6s %8s %8s %8s %8s %8s %8s\n", "type", "size", - "min", "max", "avg", "90th", "95th", "99th"); + printf("#%-6s %6s %8s %8s %8s %8s %8s %8s %8s %8s\n", "type", "size", + "min", "max", "avg", "50th", "90th", "95th", "99th", "99.9th"); } void print_stats(const char *type, const char *size) { @@ -82,17 +82,18 @@ template class AdaptiveSampler { size_t l = samples_copy.size(); if (l == 0) { - printf("%-7s %6s %8.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", type, size, + printf("%-7s %6s %8.1f %8.1f %8.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", type, size, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); return; } sort(samples_copy.begin(), samples_copy.end()); - printf("%-7s %6s %8.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", type, size, + printf("%-7s %6s %8.1f %8.1f% 8.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", type, size, samples_copy[0], samples_copy[l-1], average(), + samples_copy[(l*50)/100], samples_copy[(l*90)/100], samples_copy[(l*95)/100], - samples_copy[(l*99)/100]); + samples_copy[(l*99)/100], samples_copy[(l*99.9)/100]); } }; diff --git a/Connection.cc b/Connection.cc index 6077741..8457b1c 100644 --- a/Connection.cc +++ b/Connection.cc @@ -130,13 +130,10 @@ void Connection::start_loading() { * Issue either a get or set request to the server according to our probability distribution. */ void Connection::issue_something(double now) { - char skey[256]; char key[256]; // FIXME: generate key distribution here! string keystr = keygen->generate(lrand48() % options.records); - strcpy(skey, keystr.c_str()); - strcpy(key, options.prefix); - strcat(key,skey); + strcpy(key, keystr.c_str()); if (drand48() < options.update) { int index = lrand48() % (1024 * 1024); @@ -201,11 +198,8 @@ void Connection::issue_getset(double now) { { string keystr; char key[256]; - char skey[256]; keystr = keygen->generate(lrand48() % options.records); - strcpy(skey, keystr.c_str()); - strcpy(key,options.prefix); - strcat(key,skey); + strcpy(key, keystr.c_str()); char log[256]; int length = valuesize->generate(); @@ -229,10 +223,7 @@ void Connection::issue_getset(double now) { key_len[rKey] = rvaluelen; char key[256]; - char skey[256]; - strcpy(skey, rKey.c_str()); - strcpy(key,options.prefix); - strcat(key,skey); + strcpy(key, rKey.c_str()); issue_get(key, now); } } @@ -555,6 +546,9 @@ void Connection::read_callback() { bool done, found, full_read; //initially assume found (for sets that may come through here) + //is this correct? do we want to assume true in case that + //GET was found, but wrong value size (i.e. update value) + // found = true; if (op_queue.size() == 0) V("Spurious read callback."); @@ -568,31 +562,59 @@ void Connection::read_callback() { case WAITING_FOR_GET: assert(op_queue.size() > 0); - full_read = prot->handle_response(input, done, found); + + int obj_size; + full_read = prot->handle_response(input, done, found, obj_size); if (!full_read) { return; } else if (done) { - if (!found && options.getset && stats.gets >= 1) - { - string keystr = op->key; - strcpy(last_key, keystr.c_str()); - last_miss = 1; - } - else if (options.getset) - { - string keystr = op->key; - strcpy(last_key, keystr.c_str()); - last_miss = 0; - } + if (!found && options.getset) + { + string keystr = op->key; + strcpy(last_key, keystr.c_str()); + last_miss = 1; + } + else if (found && options.getset) + { + string keystr = op->key; + strcpy(last_key, keystr.c_str()); + + + char vlen[256]; + string valuelen = key_len[keystr]; + strcpy(vlen, valuelen.c_str()); + size_t vl = atoi(vlen); + + //char log[256]; + //sprintf(log,"key %s, resp size: %d, last GET size %lu\n",keystr.c_str(),obj_size, vl); + //write(2,log,strlen(log)); + if (obj_size != (int)vl) + { + + stats.window_get_misses++; + stats.get_misses++; + //char log[256]; + //sprintf(log,"update key %s\n",keystr.c_str()); + //write(2,log,strlen(log)); + last_miss = 1; + } + else + { + //char log[256]; + //sprintf(log,"same key %s\n",keystr.c_str()); + //write(2,log,strlen(log)); + last_miss = 0; + } + } //char log[256]; //sprintf(log,"%f,%d,%d,%d,%d,%d,%d\n", // r_time,r_appid,r_type,r_ksize,r_vsize,r_key,r_hit); //write(2,log,strlen(log)); - + finish_op(op); // sets read_state = IDLE } @@ -601,7 +623,7 @@ void Connection::read_callback() { case WAITING_FOR_SET: assert(op_queue.size() > 0); - if (!prot->handle_response(input, done, found)) return; + if (!prot->handle_response(input, done, found, obj_size)) return; //char log[256]; @@ -613,13 +635,13 @@ void Connection::read_callback() { break; case WAITING_FOR_DELETE: - if (!prot->handle_response(input,done,found)) return; + if (!prot->handle_response(input,done,found, obj_size)) return; finish_op(op); break; case LOADING: assert(op_queue.size() > 0); - if (!prot->handle_response(input, done, found)) return; + if (!prot->handle_response(input, done, found, obj_size)) return; loader_completed++; pop_op(); diff --git a/ConnectionStats.h b/ConnectionStats.h index 9b492b3..8bd5dd6 100644 --- a/ConnectionStats.h +++ b/ConnectionStats.h @@ -125,9 +125,9 @@ class ConnectionStats { } static void print_header() { - printf("%-7s %7s %7s %7s %7s %7s %7s %7s %7s\n", + printf("%-7s %7s %7s %7s %7s %7s %7s %7s %7s %7s %7s\n", "#type", "avg", "std", "min", /*"1st",*/ "5th", "10th", - "90th", "95th", "99th"); + "50th", "90th", "95th", "99th", "99.9th"); } #ifdef USE_ADAPTIVE_SAMPLER @@ -139,18 +139,18 @@ class ConnectionStats { size_t l = copy.size(); if (l == 0) { - printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", - tag, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", + tag, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); if (newline) printf("\n"); return; } sort(copy.begin(), copy.end()); - printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", + printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", tag, std::accumulate(copy.begin(), copy.end(), 0.0) / l, - copy[0], copy[(l*1) / 100], copy[(l*5) / 100], copy[(l*10) / 100], - copy[(l*90) / 100], copy[(l*95) / 100], copy[(l*99) / 100] + copy[0], copy[(l*1) / 100], copy[(l*5) / 100], copy[(l*10) / 100], copy[(l*50) / 100], + copy[(l*90) / 100], copy[(l*95) / 100], copy[(l*99) / 100], copy[(l*99.9) / 100] ); if (newline) printf("\n"); } @@ -166,10 +166,10 @@ class ConnectionStats { sort(copy.begin(), copy.end()); - printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", + printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", tag, std::accumulate(copy.begin(), copy.end(), 0.0) / l, - copy[0], copy[(l*1) / 100], copy[(l*5) / 100], copy[(l*10) / 100], - copy[(l*90) / 100], copy[(l*95) / 100], copy[(l*99) / 100] + copy[0], copy[(l*1) / 100], copy[(l*5) / 100], copy[(l*10) / 100], copy[(l*50) / 100], + copy[(l*90) / 100], copy[(l*95) / 100], copy[(l*99) / 100], copy[(l*99.9) / 100] ); if (newline) printf("\n"); } @@ -177,8 +177,8 @@ class ConnectionStats { void print_stats(const char *tag, HistogramSampler &sampler, bool newline = true) { if (sampler.total() == 0) { - printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", - tag, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", + tag, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); if (newline) printf("\n"); return; } @@ -186,8 +186,8 @@ class ConnectionStats { printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", tag, sampler.average(), sampler.get_nth(0), sampler.get_nth(1), sampler.get_nth(5), - sampler.get_nth(10), sampler.get_nth(90), - sampler.get_nth(95), sampler.get_nth(99)); + sampler.get_nth(10), sampler.get_nth(50), sampler.get_nth(90), + sampler.get_nth(95), sampler.get_nth(99), sampler.get_nth(99.9)); if (newline) printf("\n"); } @@ -195,17 +195,18 @@ class ConnectionStats { void print_stats(const char *tag, LogHistogramSampler &sampler, bool newline = true) { if (sampler.total() == 0) { - printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", - tag, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", + tag, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); if (newline) printf("\n"); return; } - printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", + printf("%-7s %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f %7.1f", tag, sampler.average(), sampler.stddev(), - sampler.get_nth(0), /*sampler.get_nth(1),*/ sampler.get_nth(5), - sampler.get_nth(10), sampler.get_nth(90), - sampler.get_nth(95), sampler.get_nth(99)); + sampler.get_nth(0), sampler.get_nth(5), + sampler.get_nth(10), sampler.get_nth(50), + sampler.get_nth(90), sampler.get_nth(95), + sampler.get_nth(99), sampler.get_nth(99.9) ); if (newline) printf("\n"); } diff --git a/Operation.h b/Operation.h index a0d8517..d56be55 100644 --- a/Operation.h +++ b/Operation.h @@ -17,7 +17,6 @@ class Operation { type_enum type; string key; - // string value; double time() const { return (end_time - start_time) * 1000000; } }; diff --git a/Protocol.cc b/Protocol.cc index 7cf7031..294eb41 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -68,7 +68,7 @@ int ProtocolRESP::set_request(const char* key, const char* value, int len) { "*3\r\n$3\r\nSET\r\n$%lu\r\n%s\r\n$%d\r\n%s\r\n", strlen(key),key,len,val); l += len + 2; - if (read_state == IDLE) read_state = WAITING_FOR_END; + if (read_state == IDLE) read_state = WAITING_FOR_GET; free(val); return l; } @@ -188,50 +188,168 @@ int ProtocolRESP::delete90_request() { * * */ -bool ProtocolRESP::handle_response(evbuffer *input, bool &done, bool &found) { +bool ProtocolRESP::handle_response(evbuffer *input, bool &done, bool &found, int &obj_size) { + char *buf = NULL; + char *databuf = NULL; + char *obj_size_str = NULL; + int len; size_t n_read_out; - buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); - if (buf == NULL) - { - done = false; - return false; - } - conn->stats.rx_bytes += n_read_out; - - //RESP null response => miss - if (!strncmp(buf,"$-1",3)) - { - conn->stats.get_misses++; - conn->stats.window_get_misses++; - found = false; + switch (read_state) { + + case WAITING_FOR_GET: - } - //HSET or SET response was good, just consume the input and move on - //with our lives - else if (!strncmp(buf,"+OK",3) || !strncmp(buf,":1",2) || !strncmp(buf,":0",2) ) - { + buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF); + if (buf == NULL) return false; + + obj_size_str = buf+1; + obj_size = atoi(obj_size_str); + + conn->stats.rx_bytes += n_read_out; + + databuf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF); + //fprintf(stderr,"--------------------\n"); + //fprintf(stderr,"resp size %lu\n",n_read_out); + //fprintf(stderr,"data size %d\n",obj_size); + //fprintf(stderr,"-------header---------\n"); + //fprintf(stderr,"%s\n",buf); + //fprintf(stderr,"-------data-----------\n"); + //fprintf(stderr,"%s\n",databuf); + + conn->stats.rx_bytes += n_read_out; + + if (!strncmp(buf,"$-1",3)) { + conn->stats.get_misses++; + conn->stats.window_get_misses++; + found = false; + done = true; + } else if ((int)n_read_out != obj_size) { + + + // FIXME: check key name to see if it corresponds to the op at + // the head of the op queue? This will be necessary to + // support "gets" where there may be misses. + + data_length = obj_size; + read_state = WAITING_FOR_GET_DATA; + done = false; + } else if (!strncmp(buf,"+OK",3) || !strncmp(buf,":1",2) || !strncmp(buf,":0",2) ) { found = false; done = true; + } else { + // got all the data.. + found = true; + done = true; + } + if (databuf) + free(databuf); + free(buf); + return true; + + case WAITING_FOR_GET_DATA: + + len = evbuffer_get_length(input); + + //finally got all data... + if (len >= data_length + 2) { + evbuffer_drain(input, data_length + 2); + conn->stats.rx_bytes += data_length + 2; + read_state = WAITING_FOR_GET; + obj_size = data_length; + found = true; + done = true; + return true; + } + return false; + + default: printf("state: %d\n", read_state); DIE("Unimplemented!"); } - //else we got a hit - else - { - if (buf) - free(buf); - // Consume the next "foobar" - buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF_STRICT); - conn->stats.rx_bytes += n_read_out; - found = true; - } - done = true; - free(buf); - return true; - + DIE("Shouldn't ever reach here..."); } + //char *buf = NUL; //for initial readline + //char *dbuf = NULL; //for data readline + //size_t n_read_out; + + //buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF); + //if (buf == NULL) + //{ + // done = false; + // return false; + //} + //conn->stats.rx_bytes += n_read_out; + // + //size_t len = evbuffer_get_length(input); + + //fprintf(stderr,"--------------------\n"); + //fprintf(stderr,"resp size %lu\n",n_read_out); + //fprintf(stderr,"ev len %lu\n",len); + //fprintf(stderr,"--------------------\n"); + //fprintf(stderr,"%s\n",buf); + ////RESP null response => miss + //if (!strncmp(buf,"$-1",3)) + //{ + // conn->stats.get_misses++; + // conn->stats.window_get_misses++; + // found = false; + // + //} + ////HSET or SET response was good, just consume the input and move on + ////with our lives + //else if (!strncmp(buf,"+OK",3) || !strncmp(buf,":1",2) || !strncmp(buf,":0",2) ) + //{ + // found = false; + // done = true; + //} + ////else we got a hit + //else + //{ + // char* nlen = buf+1; + // //fprintf(stderr,"%s\n",nlen); + // obj_size = atoi(nlen); + // // Consume the next "foobar" + // //size_t len = evbuffer_get_length(input); + // //dbuf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF); + // //if (!dbuf) + // //{ + // // fprintf(stderr,"--------------------\n"); + // // fprintf(stderr,"next foobar (null) %lu\n",n_read_out); + // // fprintf(stderr,"ev len %lu\n",len); + // // fprintf(stderr,"--------------------\n"); + // // fprintf(stderr,"%s\n",dbuf); + + // // //read_state = WAITING_FOR_GET_DATA; + // // //done = false; + // // //return false; + // //} + // //else + // //{ + + // // fprintf(stderr,"--------------------\n"); + // // fprintf(stderr,"next foobar (null) %lu\n",n_read_out); + // // fprintf(stderr,"ev len %lu\n",len); + // // fprintf(stderr,"--------------------\n"); + // // fprintf(stderr,"%s\n",dbuf); + // //} + + // //conn->stats.rx_bytes += n_read_out; + // found = true; + //} + ////read_state = WAITING_FOR_GET; + ////fprintf(stderr,"--------------------\n"); + ////fprintf(stderr,"read_state %u\n",read_state); + ////fprintf(stderr,"--------------------\n"); + //done = true; + ////if (dbuf) + //// free(dbuf); + //free(buf); + //return true; + + +//} + /** * Send an ascii get request. */ @@ -269,7 +387,7 @@ int ProtocolAscii::delete90_request() { /** * Handle an ascii response. */ -bool ProtocolAscii::handle_response(evbuffer *input, bool &done, bool &found) { +bool ProtocolAscii::handle_response(evbuffer *input, bool &done, bool &found, int &obj_size) { char *buf = NULL; int len; size_t n_read_out; @@ -299,6 +417,7 @@ bool ProtocolAscii::handle_response(evbuffer *input, bool &done, bool &found) { // support "gets" where there may be misses. data_length = len; + obj_size = len; read_state = WAITING_FOR_GET_DATA; done = false; } else { @@ -353,7 +472,8 @@ bool ProtocolBinary::setup_connection_r(evbuffer* input) { if (!opts.sasl) return true; bool b,c; - return handle_response(input, b, c); + int obj_size; + return handle_response(input, b, c, obj_size); } /** @@ -406,7 +526,7 @@ int ProtocolBinary::delete90_request() { * @param input evBuffer to read response from * @return true if consumed, false if not enough data in buffer. */ -bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found) { +bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, int &obj_size) { // Read the first 24 bytes as a header int length = evbuffer_get_length(input); if (length < 24) return false; @@ -418,6 +538,7 @@ bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found) { int targetLen = 24 + ntohl(h->body_len); if (length < targetLen) return false; + obj_size = ntohl(h->body_len); // If something other than success, count it as a miss if (h->opcode == CMD_GET && h->status) { conn->stats.get_misses++; diff --git a/Protocol.h b/Protocol.h index 91e480a..dd29d49 100644 --- a/Protocol.h +++ b/Protocol.h @@ -21,7 +21,7 @@ class Protocol { virtual int get_request(const char* key) = 0; virtual int set_request(const char* key, const char* value, int len) = 0; virtual int delete90_request() = 0; - virtual bool handle_response(evbuffer* input, bool &done, bool &found) = 0; + virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size) = 0; protected: options_t opts; @@ -43,7 +43,7 @@ class ProtocolAscii : public Protocol { virtual int get_request(const char* key); virtual int set_request(const char* key, const char* value, int len); virtual int delete90_request(); - virtual bool handle_response(evbuffer* input, bool &done, bool &found); + virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size); private: enum read_fsm { @@ -68,7 +68,7 @@ class ProtocolBinary : public Protocol { virtual int get_request(const char* key); virtual int set_request(const char* key, const char* value, int len); virtual int delete90_request(); - virtual bool handle_response(evbuffer* input, bool &done, bool &found); + virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size); }; class ProtocolRESP : public Protocol { @@ -84,7 +84,7 @@ class ProtocolRESP : public Protocol { virtual int hget_request(const char* key); virtual int hset_request(const char* key, const char* value, int len); virtual int delete90_request(); - virtual bool handle_response(evbuffer* input, bool &done, bool &found); + virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size); private: enum read_fsm { diff --git a/SConstruct b/SConstruct index d220aee..d386fe0 100644 --- a/SConstruct +++ b/SConstruct @@ -8,7 +8,7 @@ env['HAVE_POSIX_BARRIER'] = True env.Append(CPPPATH = ['/usr/local/include', '/opt/local/include']) env.Append(LIBPATH = ['/opt/local/lib']) -env.Append(CCFLAGS = '-std=c++11 -D_GNU_SOURCE ') +env.Append(CCFLAGS = '-std=c++11 -D_GNU_SOURCE') if sys.platform == 'darwin': env['CC'] = 'clang' env['CXX'] = 'clang++' @@ -37,8 +37,8 @@ if not conf.CheckFunc('pthread_barrier_init'): env = conf.Finish() -env.Append(CFLAGS = ' -O3 -Wall -g') -env.Append(CPPFLAGS = ' -O3 -Wall -g') +env.Append(CFLAGS = ' -O0 -Wall -g') +env.Append(CPPFLAGS = ' -O0 -Wall -g') env.Command(['cmdline.cc', 'cmdline.h'], 'cmdline.ggo', 'gengetopt < $SOURCE') From 0ae51a8148ae934bb5266516ec73d061e7122ca7 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 3 Feb 2021 15:26:32 -0500 Subject: [PATCH 11/57] updated --- AgentStats.h | 2 +- Connection.cc | 443 +++++++++++++++++++++++++++++++------------- Connection.h | 5 +- ConnectionOptions.h | 3 +- ConnectionStats.h | 25 ++- Generator.h | 96 ++++++++++ Operation.h | 1 + Protocol.cc | 58 +++++- Protocol.h | 4 +- README.md | 23 +++ SConstruct | 4 +- cmdline.ggo | 2 + distributions.cc | 1 + mutilate.cc | 13 +- 14 files changed, 529 insertions(+), 151 deletions(-) diff --git a/AgentStats.h b/AgentStats.h index 50e016b..69a5435 100644 --- a/AgentStats.h +++ b/AgentStats.h @@ -5,7 +5,7 @@ class AgentStats { public: uint64_t rx_bytes, tx_bytes; - uint64_t gets, sets, get_misses; + uint64_t gets, sets, accesses, get_misses; uint64_t skips; double start, stop; diff --git a/Connection.cc b/Connection.cc index 89c36f4..e976e1f 100644 --- a/Connection.cc +++ b/Connection.cc @@ -20,6 +20,9 @@ #include #include +extern ifstream kvfile; +extern pthread_mutex_t flock; + /** * Create a new connection to a server endpoint. */ @@ -33,8 +36,10 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, keysize = createGenerator(options.keysize); keygen = new KeyGenerator(keysize, options.records); - if (options.read_file && options.getset) - kvfile.open(options.file_name); + //if (options.read_file && (options.getset || options.getsetorset)) { + // kvfile.open(options.file_name); + // + //} if (options.lambda <= 0) { iagen = createGenerator("0"); @@ -156,99 +161,209 @@ void Connection::issue_something(double now) { */ void Connection::issue_getset(double now) { - if (options.queries != 0 && - (((long unsigned)options.queries/2) == stats.gets) && - options.delete90 && - stats.gets > 1) - { - issue_delete90(now); - } - - - //if the last request was miss we need to issue a set - if (last_miss) - { - //if not found and in getset mode, issue set - if (options.read_file) + if (!options.read_file && !kvfile.is_open()) { + string keystr; char key[256]; - char vlen[256]; - string valuelen; - - string keystr = string(last_key); - strcpy(key, keystr.c_str()); + char skey[256]; + memset(key,0,256); + memset(skey,0,256); + keystr = keygen->generate(lrand48() % options.records); + strncpy(skey, keystr.c_str(),strlen(keystr.c_str())); + if (args.prefix_given) { + strncpy(key, options.prefix,strlen(options.prefix)); + } + strncat(key,skey,strlen(skey)); - int index = lrand48() % (1024 * 1024); - valuelen = key_len[keystr]; - strcpy(vlen, valuelen.c_str()); - int vl = atoi(vlen); - - - issue_set(key, &random_char[index], vl); + char log[256]; + int length = valuesize->generate(); + sprintf(log,"%s,%d\n",key,length); + write(2,log,strlen(log)); + + issue_get_with_len(key, length, now); } - else + else { + string line; + string rT; + string rApp; + string rReq; + string rKey; + string rvaluelen; + + pthread_mutex_lock(&flock); + getline(kvfile,line); + pthread_mutex_unlock(&flock); + stringstream ss(line); + getline( ss, rT, ','); + getline( ss, rApp, ','); + getline( ss, rReq, ','); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + + int vl = atoi(rvaluelen.c_str()); + char key[256]; - string keystr = string(last_key); - strcpy(key, keystr.c_str()); + char skey[256]; + memset(key,0,256); + memset(skey,0,256); + strncpy(skey, rKey.c_str(),strlen(rKey.c_str())); + if (args.prefix_given) + strncpy(key, options.prefix,strlen(options.prefix)); + strncat(key,skey,strlen(skey)); + issue_get_with_len(key, vl, now); + } - int index = lrand48() % (1024 * 1024); +} - issue_set(key, &random_char[index], valuesize->generate()); - } - last_miss = 0; +/** + * Get/Set or Set Style + * If a GET command: Issue a get first, if not found then set + * If trace file (or prob. write) says to set, then set it + */ +int Connection::issue_getsetorset(double now) { + + int ret = 0; + + if (!options.read_file && !kvfile.is_open()) + { + string keystr; + char key[256]; + char skey[256]; + memset(key,0,256); + memset(skey,0,256); + keystr = keygen->generate(lrand48() % options.records); + strncpy(skey, keystr.c_str(),strlen(keystr.c_str())); + if (args.prefix_given) + strncpy(key, options.prefix,strlen(options.prefix)); + + strncat(key,skey,strlen(skey)); + + char log[256]; + int length = valuesize->generate(); + sprintf(log,"%s,%d\n",key,length); + write(2,log,strlen(log)); + + issue_get_with_len(key, length, now); } else { - if (!options.read_file && !kvfile.is_open()) - { - string keystr; - char key[256]; - char skey[256]; - memset(key,0,256); - memset(skey,0,256); - keystr = keygen->generate(lrand48() % options.records); - strncpy(skey, keystr.c_str(),strlen(keystr.c_str())); - if (args.prefix_given) - strncpy(key, options.prefix,strlen(options.prefix)); - strncat(key,skey,strlen(skey)); - - char log[256]; - int length = valuesize->generate(); - sprintf(log,"%s,%d\n",key,length); - write(2,log,strlen(log)); - - issue_get(key, now); - } - else - { - string line; - string rKey; - string rvaluelen; - getline(kvfile,line); - stringstream ss(line); - getline( ss, rKey, ',' ); - getline( ss, rvaluelen, ',' ); - - - //save valuelen - key_len[rKey] = rvaluelen; - - char key[256]; - char skey[256]; - memset(key,0,256); - memset(skey,0,256); - strncpy(skey, rKey.c_str(),strlen(rKey.c_str())); - if (args.prefix_given) - strncpy(key, options.prefix,strlen(options.prefix)); - strncat(key,skey,strlen(skey)); - issue_get(key, now); - } - } + string line; + string rT; + string rApp; + string rOp; + string rKey; + string rKeySize; + string rvaluelen; + + pthread_mutex_lock(&flock); + if (kvfile.good()) { + getline(kvfile,line); + pthread_mutex_unlock(&flock); + } + else { + pthread_mutex_unlock(&flock); + return 1; + } + stringstream ss(line); + int Op = 0; + + if (options.twitter_trace == 1) { + getline( ss, rT, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rKeySize, ',' ); + getline( ss, rvaluelen, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + if (rOp.compare("get") == 0) { + Op = 1; + } else if (rOp.compare("set") == 0) { + Op = 2; + } else { + Op = 1; + } + + } else { + getline( ss, rT, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + if (rOp.compare("read") == 0) + Op = 1; + if (rOp.compare("write") == 0) + Op = 2; + } + int vl = atoi(rvaluelen.c_str()); + if (vl < 8) vl = 8; + char key[1024]; + char skey[1024]; + memset(key,0,1024); + memset(skey,0,1024); + strncpy(skey, rKey.c_str(),strlen(rKey.c_str())); + + if (args.prefix_given) { + strncpy(key, options.prefix,strlen(options.prefix)); + } + strncat(key,skey,strlen(skey)); + + //if (strcmp(key,"100004781") == 0) { + // fprintf(stderr,"ready!\n"); + //} + switch(Op) + { + case 1: + issue_get_with_len(key, vl, now); + break; + case 2: + int index = lrand48() % (1024 * 1024); + issue_set(key, &random_char[index], vl, now,true); + break; + } + } + + return ret; } +/** + * Issue a get request to the server. + */ +void Connection::issue_get_with_len(const char* key, int valuelen, double now) { + Operation op; + int l; + +#if HAVE_CLOCK_GETTIME + op.start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + op.start_time = tv_to_double(&now_tv); +#else + op.start_time = get_time(); +#endif + } else { + op.start_time = now; + } +#endif + + //record before rx + //r_vsize = stats.rx_bytes % 100000; + + op.key = string(key); + op.valuelen = valuelen; + op.type = Operation::GET; + op_queue.push(op); + + if (read_state == IDLE) read_state = WAITING_FOR_GET; + l = prot->get_request(key); + if (read_state != LOADING) stats.tx_bytes += l; + + stats.log_access(op); +} /** * Issue a get request to the server. @@ -283,6 +398,8 @@ void Connection::issue_get(const char* key, double now) { if (read_state == IDLE) read_state = WAITING_FOR_GET; l = prot->get_request(key); if (read_state != LOADING) stats.tx_bytes += l; + + stats.log_access(op); } /** @@ -320,7 +437,7 @@ void Connection::issue_delete90(double now) { * Issue a set request to the server. */ void Connection::issue_set(const char* key, const char* value, int length, - double now) { + double now, bool is_access) { Operation op; int l; @@ -346,6 +463,9 @@ void Connection::issue_set(const char* key, const char* value, int length, if (read_state == IDLE) read_state = WAITING_FOR_SET; l = prot->set_request(key, value, length); if (read_state != LOADING) stats.tx_bytes += l; + + if (is_access) + stats.log_access(op); } /** @@ -403,15 +523,16 @@ void Connection::finish_op(Operation *op) { //lets check if we should output stats for the window //Do the binning for percentile outputs //crude at start - if ((options.misswindow != 0) && ( ((stats.window_gets) % options.misswindow) == 0)) + if ((options.misswindow != 0) && ( ((stats.window_accesses) % options.misswindow) == 0)) { if (stats.window_gets != 0) { - printf("%lu,%.4f\n",(stats.gets), - ((double)stats.window_get_misses/(double)stats.window_gets)); + //printf("%lu,%.4f\n",(stats.accesses), + // ((double)stats.window_get_misses/(double)stats.window_accesses)); stats.window_gets = 0; stats.window_get_misses = 0; stats.window_sets = 0; + stats.window_accesses = 0; } } @@ -425,17 +546,37 @@ void Connection::finish_op(Operation *op) { bool Connection::check_exit_condition(double now) { if (read_state == INIT_READ) return false; if (now == 0.0) now = get_time(); - if ((options.queries == 0) && - (now > start_time + options.time)) - { - return true; - } - if (options.loadonly && read_state == IDLE) return true; - if (options.queries != 0 && - (((long unsigned)options.queries) == stats.gets)) - { - return true; + + if (options.read_file) { + pthread_mutex_lock(&flock); + int eof = kvfile.eof(); + pthread_mutex_unlock(&flock); + if (eof) { + return true; + } + else if ((options.queries == 1) && + (now > start_time + options.time)) + { + return true; + } + else { + return false; + } + + } else { + if (options.queries != 0 && + (((long unsigned)options.queries) == (stats.accesses))) + { + return true; + } + if ((options.queries == 0) && + (now > start_time + options.time)) + { + return true; + } + if (options.loadonly && read_state == IDLE) return true; } + return false; } @@ -463,9 +604,64 @@ void Connection::event_callback(short events) { } else if (events & BEV_EVENT_ERROR) { int err = bufferevent_socket_get_dns_error(bev); if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); + + //stats.print_header(); + //stats.print_stats("read", stats.get_sampler); + //stats.print_stats("update", stats.set_sampler); + //stats.print_stats("op_q", stats.op_sampler); + + //int total = stats.gets + stats.sets; + + //printf("\nTotal QPS = %.1f (%d / %.1fs)\n", + // total / (stats.stop - stats.start), + // total, stats.stop - stats.start); + + + //printf("\n"); + + //printf("Misses = %" PRIu64 " (%.1f%%)\n", stats.get_misses, + // (double) stats.get_misses/stats.gets*100); + + //printf("Skipped TXs = %" PRIu64 " (%.1f%%)\n\n", stats.skips, + // (double) stats.skips / total * 100); + + //printf("RX %10" PRIu64 " bytes : %6.1f MB/s\n", + // stats.rx_bytes, + // (double) stats.rx_bytes / 1024 / 1024 / (stats.stop - stats.start)); + //printf("TX %10" PRIu64 " bytes : %6.1f MB/s\n", + // stats.tx_bytes, + // (double) stats.tx_bytes / 1024 / 1024 / (stats.stop - stats.start)); + DIE("BEV_EVENT_ERROR: %s", strerror(errno)); } else if (events & BEV_EVENT_EOF) { + //stats.print_header(); + //stats.print_stats("read", stats.get_sampler); + //stats.print_stats("update", stats.set_sampler); + //stats.print_stats("op_q", stats.op_sampler); + + //int total = stats.gets + stats.sets; + + //printf("\nTotal QPS = %.1f (%d / %.1fs)\n", + // total / (stats.stop - stats.start), + // total, stats.stop - stats.start); + + + //printf("\n"); + + //printf("Misses = %" PRIu64 " (%.1f%%)\n", stats.get_misses, + // (double) stats.get_misses/stats.gets*100); + + //printf("Skipped TXs = %" PRIu64 " (%.1f%%)\n\n", stats.skips, + // (double) stats.skips / total * 100); + + //printf("RX %10" PRIu64 " bytes : %6.1f MB/s\n", + // stats.rx_bytes, + // (double) stats.rx_bytes / 1024 / 1024 / (stats.stop - stats.start)); + //printf("TX %10" PRIu64 " bytes : %6.1f MB/s\n", + // stats.tx_bytes, + // (double) stats.tx_bytes / 1024 / 1024 / (stats.stop - stats.start)); + DIE("Unexpected EOF from server."); } } @@ -514,6 +710,10 @@ void Connection::drive_write_machine(double now) { if (options.getset) issue_getset(now); + else if (options.getsetorset) { + int ret = issue_getsetorset(now); + if (ret) return; //if at EOF + } else issue_something(now); @@ -588,44 +788,26 @@ void Connection::read_callback() { return; } else if (done) { - if (!found && options.getset) - { - string keystr = op->key; - strcpy(last_key, keystr.c_str()); - last_miss = 1; + if ((!found && options.getset) || + (!found && options.getsetorset)) { + char key[256]; + string keystr = op->key; + strcpy(key, keystr.c_str()); + int valuelen = op->valuelen; + + finish_op(op); // sets read_state = IDLE + //if not found and in getset mode, issue set + if (options.read_file) { + int index = lrand48() % (1024 * 1024); + issue_set(key, &random_char[index], valuelen); } - else if (found && options.getset) - { - string keystr = op->key; - strcpy(last_key, keystr.c_str()); - - - char vlen[256]; - string valuelen = key_len[keystr]; - strcpy(vlen, valuelen.c_str()); - size_t vl = atoi(vlen); - - //char log[256]; - //sprintf(log,"key %s, resp size: %d, last GET size %lu\n",keystr.c_str(),obj_size, vl); - //write(2,log,strlen(log)); - if (obj_size != (int)vl) - { - - stats.window_get_misses++; - stats.get_misses++; - //char log[256]; - //sprintf(log,"update key %s\n",keystr.c_str()); - //write(2,log,strlen(log)); - last_miss = 1; - } - else - { - //char log[256]; - //sprintf(log,"same key %s\n",keystr.c_str()); - //write(2,log,strlen(log)); - last_miss = 0; - } + else { + int index = lrand48() % (1024 * 1024); + issue_set(key, &random_char[index], valuelen); } + } else { + finish_op(op); + } //char log[256]; @@ -633,7 +815,6 @@ void Connection::read_callback() { // r_time,r_appid,r_type,r_ksize,r_vsize,r_key,r_hit); //write(2,log,strlen(log)); - finish_op(op); // sets read_state = IDLE } break; diff --git a/Connection.h b/Connection.h index d346d11..07e45bc 100644 --- a/Connection.h +++ b/Connection.h @@ -95,7 +95,6 @@ class Connection { //need to keep value length for read key map key_len; - ifstream kvfile; // Parameters to track progress of the data loader. int loader_issued, loader_completed; @@ -124,13 +123,15 @@ class Connection { void finish_op(Operation *op); void issue_something(double now = 0.0); void issue_getset(double now = 0.0); + int issue_getsetorset(double now = 0.0); void drive_write_machine(double now = 0.0); // request functions void issue_sasl(); void issue_get(const char* key, double now = 0.0); + void issue_get_with_len(const char* key, int valuelen, double now = 0.0); void issue_set(const char* key, const char* value, int length, - double now = 0.0); + double now = 0.0, bool is_access = false); void issue_delete90(double now = 0.0); // protocol fucntions diff --git a/ConnectionOptions.h b/ConnectionOptions.h index 83efd90..5e600e9 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -18,6 +18,7 @@ typedef struct { bool use_assoc; bool redis; bool getset; + bool getsetorset; bool delete90; bool sasl; char username[32]; @@ -33,7 +34,7 @@ typedef struct { // qps_per_connection // iadist - + int twitter_trace; double update; int time; bool loadonly; diff --git a/ConnectionStats.h b/ConnectionStats.h index 8bd5dd6..c2d1d42 100644 --- a/ConnectionStats.h +++ b/ConnectionStats.h @@ -22,33 +22,36 @@ class ConnectionStats { public: ConnectionStats(bool _sampling = true) : #ifdef USE_ADAPTIVE_SAMPLER - get_sampler(100000), set_sampler(100000), op_sampler(100000), + get_sampler(100000), set_sampler(100000), access_sampler(100000), op_sampler(100000), #elif defined(USE_HISTOGRAM_SAMPLER) - get_sampler(10000,1), set_sampler(10000,1), op_sampler(1000,1), + get_sampler(10000,1), set_sampler(10000,1), access_sampler(10000,1), op_sampler(1000,1), #else - get_sampler(200), set_sampler(200), op_sampler(100), + get_sampler(200), set_sampler(200), access_sampler(200), op_sampler(100), #endif - rx_bytes(0), tx_bytes(0), gets(0), sets(0), - get_misses(0), window_gets(0), window_sets(0), + rx_bytes(0), tx_bytes(0), gets(0), sets(0), accesses(0), + get_misses(0), window_gets(0), window_sets(0), window_accesses(0), window_get_misses(0), skips(0), sampling(_sampling) {} #ifdef USE_ADAPTIVE_SAMPLER AdaptiveSampler get_sampler; AdaptiveSampler set_sampler; + AdaptiveSampler access_sampler; AdaptiveSampler op_sampler; #elif defined(USE_HISTOGRAM_SAMPLER) HistogramSampler get_sampler; HistogramSampler set_sampler; + HistogramSampler access_sampler; HistogramSampler op_sampler; #else LogHistogramSampler get_sampler; LogHistogramSampler set_sampler; + LogHistogramSampler access_sampler; LogHistogramSampler op_sampler; #endif uint64_t rx_bytes, tx_bytes; - uint64_t gets, sets, get_misses; - uint64_t window_gets, window_sets, window_get_misses; + uint64_t gets, sets, accesses, get_misses; + uint64_t window_gets, window_sets, window_accesses, window_get_misses; uint64_t skips; double start, stop; @@ -57,6 +60,8 @@ class ConnectionStats { void log_get(Operation& op) { if (sampling) get_sampler.sample(op); window_gets++; gets++; } void log_set(Operation& op) { if (sampling) set_sampler.sample(op); window_sets++; sets++; } + void log_access(Operation& op) { //if (sampling) access_sampler.sample(op); + window_accesses++; accesses++; } void log_op (double op) { if (sampling) op_sampler.sample(op); } double get_qps() { @@ -73,6 +78,8 @@ class ConnectionStats { samples.push_back(s.time()); // (s.end_time - s.start_time) * 1000000); for (auto s: set_sampler.samples) samples.push_back(s.time()); // (s.end_time - s.start_time) * 1000000); + for (auto s: access_sampler.samples) + samples.push_back(s.time()); // (s.end_time - s.start_time) * 1000000); sort(samples.begin(), samples.end()); @@ -94,10 +101,12 @@ class ConnectionStats { #ifdef USE_ADAPTIVE_SAMPLER for (auto i: cs.get_sampler.samples) get_sampler.sample(i); //log_get(i); for (auto i: cs.set_sampler.samples) set_sampler.sample(i); //log_set(i); + for (auto i: cs.access_sampler.samples) access_sampler.sample(i); //log_access(i); for (auto i: cs.op_sampler.samples) op_sampler.sample(i); //log_op(i); #else get_sampler.accumulate(cs.get_sampler); set_sampler.accumulate(cs.set_sampler); + access_sampler.accumulate(cs.access_sampler); op_sampler.accumulate(cs.op_sampler); #endif @@ -105,6 +114,7 @@ class ConnectionStats { tx_bytes += cs.tx_bytes; gets += cs.gets; sets += cs.sets; + accesses += cs.accesses; get_misses += cs.get_misses; skips += cs.skips; @@ -117,6 +127,7 @@ class ConnectionStats { tx_bytes += as.tx_bytes; gets += as.gets; sets += as.sets; + accesses += as.accesses; get_misses += as.get_misses; skips += as.skips; diff --git a/Generator.h b/Generator.h index 72a0156..bf57e2a 100644 --- a/Generator.h +++ b/Generator.h @@ -119,6 +119,102 @@ class Exponential : public Generator { double lambda; }; +class Zipfian : public Generator { +public: + Zipfian(double _alpha = 1.0, unsigned int _m = 100) : + alpha(_alpha), m(_m) { + int i; + // Compute normalization constant + for (i = 1; i <= m; i++) + c = c + (1.0 / pow((double) i, alpha)); + c = 1.0 / c; + + sum_probs = (double*)malloc((m+1)*sizeof(double)); + sum_probs[0] = 0; + for (i = 1; i <= m; i++) { + sum_probs[i] = sum_probs[i-1] + c / pow((double) i, alpha); + } + + D("Zipfian(alpha=%f, m=%u)", alpha, m); + } + + virtual double generate() { + double z; // Uniform random number (0 < z < 1) + int zipf_value; // Computed exponential value to be returned + int low, high, mid; // Binary-search bounds + + // Pull a uniform random number (0 < z < 1) + do + { + z = rand_val(0); + } + while ((z == 0) || (z == 1)); + + // Map z to the value + low = 1, high = m, mid = (m/2); + do { + mid = floor((low+high)/2); + if (sum_probs[mid] >= z && sum_probs[mid-1] < z) { + zipf_value = mid; + break; + } else if (sum_probs[mid] >= z) { + high = mid-1; + } else { + low = mid+1; + } + } while (low <= high); + + // Assert that zipf_value is between 1 and M + assert((zipf_value >=1) && (zipf_value <= m)); + + return(zipf_value); + } + + //========================================================================= + //= Multiplicative LCG for generating uniform(0.0, 1.0) random numbers = + //= - x_n = 7^5*x_(n-1)mod(2^31 - 1) = + //= - With x seeded to 1 the 10000th x value should be 1043618065 = + //= - From R. Jain, "The Art of Computer Systems Performance Analysis," = + //= John Wiley & Sons, 1991. (Page 443, Figure 26.2) = + //========================================================================= + static double rand_val(int seed) { + const long a = 16807; // Multiplier + const long m = 2147483647; // Modulus + const long q = 127773; // m div a + const long r = 2836; // m mod a + static long x; // Random int value + long x_div_q; // x divided by q + long x_mod_q; // x modulo q + long x_new; // New x value + + // Set the seed if argument is non-zero and then return zero + if (seed > 0) + { + x = seed; + return(0.0); + } + + // RNG using integer arithmetic + x_div_q = x / q; + x_mod_q = x % q; + x_new = (a * x_mod_q) - (r * x_div_q); + if (x_new > 0) + x = x_new; + else + x = x_new + m; + + // Return a random value between 0.0 and 1.0 + return((double) x / m); + } + + +private: + double alpha; + double m; + double c; + double *sum_probs; // Pre-calculated sum of probabilities +}; + class GPareto : public Generator { public: GPareto(double _loc = 0.0, double _scale = 1.0, double _shape = 1.0) : diff --git a/Operation.h b/Operation.h index d56be55..9e499e3 100644 --- a/Operation.h +++ b/Operation.h @@ -17,6 +17,7 @@ class Operation { type_enum type; string key; + int valuelen; double time() const { return (end_time - start_time) * 1000000; } }; diff --git a/Protocol.cc b/Protocol.cc index 294eb41..29f079e 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -357,7 +357,9 @@ int ProtocolAscii::get_request(const char* key) { int l; l = evbuffer_add_printf( bufferevent_get_output(bev), "get %s\r\n", key); - if (read_state == IDLE) read_state = WAITING_FOR_GET; + if (read_state == IDLE) { + read_state = WAITING_FOR_GET; + } return l; } @@ -368,10 +370,18 @@ int ProtocolAscii::set_request(const char* key, const char* value, int len) { int l; l = evbuffer_add_printf(bufferevent_get_output(bev), "set %s 0 0 %d\r\n", key, len); - bufferevent_write(bev, value, len); + + char *val = (char*)malloc(len*sizeof(char)+1); + memset(val, 'a', len); + val[len] = '\0'; + + bufferevent_write(bev, val, len); bufferevent_write(bev, "\r\n", 2); l += len + 2; - if (read_state == IDLE) read_state = WAITING_FOR_END; + if (read_state == IDLE) { + read_state = WAITING_FOR_END; + } + free(val); return l; } @@ -409,6 +419,9 @@ bool ProtocolAscii::handle_response(evbuffer *input, bool &done, bool &found, in } read_state = WAITING_FOR_GET; done = true; + } else if (!strncmp(buf, "STORED", 6)) { + read_state = WAITING_FOR_GET; + done = true; } else if (!strncmp(buf, "VALUE", 5)) { sscanf(buf, "VALUE %*s %*d %d", &len); @@ -417,7 +430,6 @@ bool ProtocolAscii::handle_response(evbuffer *input, bool &done, bool &found, in // support "gets" where there may be misses. data_length = len; - obj_size = len; read_state = WAITING_FOR_GET_DATA; done = false; } else { @@ -438,6 +450,42 @@ bool ProtocolAscii::handle_response(evbuffer *input, bool &done, bool &found, in } return false; + /* + case WAITING_FOR_GETSET: + buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF); + if (buf == NULL) return false; + + conn->stats.rx_bytes += n_read_out; + if (!strncmp(buf, "END", 3)) { + conn->stats.get_misses++; + conn->stats.window_get_misses++; + found = false; + done = true; + read_state = WAITING_FOR_SET; + return true; + } else if (!strncmp(buf, "STORED", 6)) { + done = true; + read_state = WAITING_FOR_GET; + return true; + } + + + case WAITING_FOR_SET: + buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF); + if (buf == NULL) return false; + + conn->stats.rx_bytes += n_read_out; + + if (!strncmp(buf, "STORED", 6)) { + done = true; + read_state = IDLE; + return true; + } else { + done = false; + return true; + } + */ + default: printf("state: %d\n", read_state); DIE("Unimplemented!"); } @@ -538,7 +586,7 @@ bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, i int targetLen = 24 + ntohl(h->body_len); if (length < targetLen) return false; - obj_size = ntohl(h->body_len); + obj_size = ntohl(h->body_len)-4; // If something other than success, count it as a miss if (h->opcode == CMD_GET && h->status) { conn->stats.get_misses++; diff --git a/Protocol.h b/Protocol.h index dd29d49..cd4c776 100644 --- a/Protocol.h +++ b/Protocol.h @@ -50,7 +50,9 @@ class ProtocolAscii : public Protocol { IDLE, WAITING_FOR_GET, WAITING_FOR_GET_DATA, - WAITING_FOR_END + WAITING_FOR_END, + WAITING_FOR_SET, + WAITING_FOR_GETSET }; read_fsm read_state; diff --git a/README.md b/README.md index 91b7caf..24bec9d 100644 --- a/README.md +++ b/README.md @@ -137,10 +137,33 @@ Command-line Options a GET and if the response is MISS, then issue a SET for on that key following distribution value. + --getsetorset Use getset mode and allow for direct writes + (with optype == 2). + --prefix=STRING Prefix all keys with a string (helps with + multi-tennant eval) + --delete90 Delete 90 percent of keys after halfway through + the workload, used to model Rumbel et. al. + USENIX FAST '14 + workloads. MUST BE IN GETSET MODE and + have a set number of + queries + --assoc=INT We create hash tables by taking the truncating + the key by b bytes. The + n-b bytes are the key for redis, in the + original (key,value). The + value is a hash table and we acess field + b to get the value. Essentially this makes + redis n-way associative + cache. Only works in redis mode. For small + key sizes we just use + normal method of (key,value) store. No hash + table. (default=`4') -q, --qps=INT Target aggregate QPS. 0 = peak QPS. (default=`0') -t, --time=INT Maximum time to run (seconds). (default=`5') --read_file=STRING Read keys from file. (default=`') + --twitter_trace=INT use twitter memcached trace format from file. + (default=`0') -K, --keysize=STRING Length of memcached keys (distribution). (default=`30') -V, --valuesize=STRING Length of memcached values (distribution). diff --git a/SConstruct b/SConstruct index d386fe0..57d0054 100644 --- a/SConstruct +++ b/SConstruct @@ -37,8 +37,8 @@ if not conf.CheckFunc('pthread_barrier_init'): env = conf.Finish() -env.Append(CFLAGS = ' -O0 -Wall -g') -env.Append(CPPFLAGS = ' -O0 -Wall -g') +env.Append(CFLAGS = ' -O3 -Wall -g') +env.Append(CPPFLAGS = ' -O3 -Wall -g') env.Command(['cmdline.cc', 'cmdline.h'], 'cmdline.ggo', 'gengetopt < $SOURCE') diff --git a/cmdline.ggo b/cmdline.ggo index c9fe0f5..97a46e9 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -17,6 +17,7 @@ option "redis" - "Use Redis RESP protocol instead of memchached." option "getset" - "Use getset mode, in getset mode we first issue \ a GET and if the response is MISS, then issue a SET for on that key following distribution value." +option "getsetorset" - "Use getset mode and allow for direct writes (with optype == 2)." option "prefix" - "Prefix all keys with a string (helps with multi-tennant eval)" string option "delete90" - "Delete 90 percent of keys after halfway through \ the workload, used to model Rumbel et. al. USENIX \ @@ -33,6 +34,7 @@ option "qps" q "Target aggregate QPS. 0 = peak QPS." int default="0" option "time" t "Maximum time to run (seconds)." int default="5" option "read_file" - "Read keys from file." string default="" +option "twitter_trace" - "use twitter memcached trace format from file." int default="0" option "keysize" K "Length of memcached keys (distribution)." string default="30" diff --git a/distributions.cc b/distributions.cc index ce939e7..6cb9532 100644 --- a/distributions.cc +++ b/distributions.cc @@ -32,3 +32,4 @@ double generate_uniform(double lambda) { if (lambda <= 0.0) return 0; return 1.0 / lambda; } + diff --git a/mutilate.cc b/mutilate.cc index 0b86597..f3f0873 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -42,6 +42,9 @@ using namespace std; +ifstream kvfile; +pthread_mutex_t flock = PTHREAD_MUTEX_INITIALIZER; + gengetopt_args_info args; char random_char[2 * 1024 * 1024]; // Buffer used to generate random values. @@ -60,6 +63,8 @@ struct thread_data { }; // struct evdns_base *evdns; + +pthread_t pt[1024]; pthread_barrier_t barrier; @@ -651,8 +656,12 @@ void go(const vector& servers, options_t& options, } #endif + if (options.read_file && (options.getset || options.getsetorset)) { + kvfile.open(options.file_name); + + } + if (options.threads > 1) { - pthread_t pt[options.threads]; struct thread_data td[options.threads]; #ifdef __clang__ vector* ts = static_cast*>(alloca(sizeof(vector) * options.threads)); @@ -1064,6 +1073,7 @@ void args_to_options(options_t* options) { options->use_assoc = args.assoc_given; options->assoc = args.assoc_arg; + options->twitter_trace = args.twitter_trace_arg; options->binary = args.binary_given; options->redis = args.redis_given; @@ -1080,6 +1090,7 @@ void args_to_options(options_t* options) { //getset mode (first issue get, then set same key if miss) options->getset = args.getset_given; + options->getsetorset = args.getsetorset_given; //delete 90 percent of keys after halfway //model workload in Rumble and Ousterhout - log structured memory //for dram based storage From c73b4edd40aa508cf6becdb7a02d1001ba9223ca Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Mon, 15 Feb 2021 09:53:15 -0500 Subject: [PATCH 12/57] added unix socket option --- Connection.cc | 35 ++++++++++++++++++++++++++++------- ConnectionOptions.h | 1 + cmdline.ggo | 1 + mutilate.cc | 7 ++++++- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/Connection.cc b/Connection.cc index e976e1f..fd78174 100644 --- a/Connection.cc +++ b/Connection.cc @@ -1,4 +1,7 @@ #include +#include +#include +#include #include #include @@ -68,10 +71,24 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, prot = new ProtocolAscii(options, this, bev); } - if (bufferevent_socket_connect_hostname(bev, evdns, AF_UNSPEC, - hostname.c_str(), - atoi(port.c_str()))) { - DIE("bufferevent_socket_connect_hostname()"); + if (options.unix_socket) { + struct sockaddr_un sin; + memset(&sin, 0, sizeof(sin)); + sin.sun_family = AF_LOCAL; + strcpy(sin.sun_path, hostname.c_str()); + + static int addrlen; + + addrlen = sizeof(sin); + if (bufferevent_socket_connect(bev, (struct sockaddr*)&sin, addrlen) < 0) { + DIE("bufferevent_socket_connect()"); + } + } else { + if (bufferevent_socket_connect_hostname(bev, evdns, AF_UNSPEC, + hostname.c_str(), + atoi(port.c_str()))) { + DIE("bufferevent_socket_connect_hostname()"); + } } timer = evtimer_new(base, timer_cb, this); @@ -603,8 +620,10 @@ void Connection::event_callback(short events) { } else if (events & BEV_EVENT_ERROR) { int err = bufferevent_socket_get_dns_error(bev); - if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); - + //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); + if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); + return; + //stats.print_header(); //stats.print_stats("read", stats.get_sampler); //stats.print_stats("update", stats.set_sampler); @@ -662,7 +681,9 @@ void Connection::event_callback(short events) { // stats.tx_bytes, // (double) stats.tx_bytes / 1024 / 1024 / (stats.stop - stats.start)); - DIE("Unexpected EOF from server."); + //DIE("Unexpected EOF from server."); + fprintf(stderr,"Unexpected EOF from server."); + return; } } diff --git a/ConnectionOptions.h b/ConnectionOptions.h index 5e600e9..2792834 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -15,6 +15,7 @@ typedef struct { char file_name[256]; bool read_file; bool binary; + bool unix_socket; bool use_assoc; bool redis; bool getset; diff --git a/cmdline.ggo b/cmdline.ggo index 97a46e9..2e8a78b 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -12,6 +12,7 @@ text "\nBasic options:" option "server" s "Memcached server hostname[:port]. \ Repeat to specify multiple servers." string multiple +option "unix_socket" - "Use UNIX socket instead of TCP." option "binary" - "Use binary memcached protocol instead of ASCII." option "redis" - "Use Redis RESP protocol instead of memchached." option "getset" - "Use getset mode, in getset mode we first issue \ diff --git a/mutilate.cc b/mutilate.cc index f3f0873..6f1c0ea 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -162,7 +162,11 @@ void agent() { zmq::context_t context(1); zmq::socket_t socket(context, ZMQ_REP); - socket.bind((string("tcp://*:")+string(args.agent_port_arg)).c_str()); + if (atoi(args.agent_port_arg) == -1) { + socket.bind(string("ipc:///tmp/memcached.sock").c_str()); + } else { + socket.bind((string("tcp://*:")+string(args.agent_port_arg)).c_str()); + } while (true) { zmq::message_t request; @@ -1075,6 +1079,7 @@ void args_to_options(options_t* options) { options->assoc = args.assoc_arg; options->twitter_trace = args.twitter_trace_arg; + options->unix_socket = args.unix_socket_given; options->binary = args.binary_given; options->redis = args.redis_given; From 6bb7aa2d0889d0a1d2b55bb3f77270054b097884 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Mon, 15 Feb 2021 10:09:22 -0500 Subject: [PATCH 13/57] fixed unix sockets --- Connection.cc | 2 +- mutilate.cc | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Connection.cc b/Connection.cc index fd78174..4cb6ca9 100644 --- a/Connection.cc +++ b/Connection.cc @@ -606,7 +606,7 @@ void Connection::event_callback(short events) { int fd = bufferevent_getfd(bev); if (fd < 0) DIE("bufferevent_getfd"); - if (!options.no_nodelay) { + if (!options.no_nodelay && !options.unix_socket) { int one = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *) &one, sizeof(one)) < 0) diff --git a/mutilate.cc b/mutilate.cc index 6f1c0ea..9b706b8 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -479,8 +479,13 @@ int main(int argc, char **argv) { pthread_barrier_init(&barrier, NULL, options.threads); vector servers; - for (unsigned int s = 0; s < args.server_given; s++) - servers.push_back(name_to_ipaddr(string(args.server_arg[s]))); + for (unsigned int s = 0; s < args.server_given; s++) { + if (options.unix_socket) { + servers.push_back(string(args.server_arg[s])); + } else { + servers.push_back(name_to_ipaddr(string(args.server_arg[s]))); + } + } ConnectionStats stats; From 807b350841c1ad0feb089ebb99a45f254819e864 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Mon, 1 Mar 2021 09:31:39 -0500 Subject: [PATCH 14/57] update --- Connection.cc | 3 +++ mutilate.cc | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Connection.cc b/Connection.cc index 4cb6ca9..32748f8 100644 --- a/Connection.cc +++ b/Connection.cc @@ -78,6 +78,9 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, strcpy(sin.sun_path, hostname.c_str()); static int addrlen; + srand(time(NULL)); + int s = rand() % 10; + sleep(s); addrlen = sizeof(sin); if (bufferevent_socket_connect(bev, (struct sockaddr*)&sin, addrlen) < 0) { diff --git a/mutilate.cc b/mutilate.cc index 9b706b8..6449d6b 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,7 @@ struct thread_data { #ifdef HAVE_LIBZMQ zmq::socket_t *socket; #endif + int id; }; // struct evdns_base *evdns; @@ -684,6 +686,7 @@ void go(const vector& servers, options_t& options, for (int t = 0; t < options.threads; t++) { td[t].options = &options; + td[t].id = t; #ifdef HAVE_LIBZMQ td[t].socket = socket; #endif @@ -729,6 +732,7 @@ void go(const vector& servers, options_t& options, if (pthread_create(&pt[t], &attr, thread_main, &td[t])) DIE("pthread_create() failed"); + usleep(t); } for (int t = 0; t < options.threads; t++) { @@ -764,9 +768,23 @@ void go(const vector& servers, options_t& options, #endif } +int stick_this_thread_to_core(int core_id) { + int num_cores = sysconf(_SC_NPROCESSORS_ONLN); + if (core_id < 0 || core_id >= num_cores) + return EINVAL; + + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(core_id, &cpuset); + + pthread_t current_thread = pthread_self(); + return pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset); +} + void* thread_main(void *arg) { struct thread_data *td = (struct thread_data *) arg; - + + stick_this_thread_to_core(td->id); ConnectionStats *cs = new ConnectionStats(); do_mutilate(*td->servers, *td->options, *cs, td->master From b94c0bc958e48555694ec7ce004fa1881543ea14 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Mon, 1 Mar 2021 15:44:09 -0500 Subject: [PATCH 15/57] should finally work --- Connection.cc | 100 +++++++++++++++++++++++++++++--------------- Connection.h | 3 +- ConnectionOptions.h | 1 + cmdline.ggo | 1 + mutilate.cc | 3 +- 5 files changed, 72 insertions(+), 36 deletions(-) diff --git a/Connection.cc b/Connection.cc index 32748f8..e8847a0 100644 --- a/Connection.cc +++ b/Connection.cc @@ -59,35 +59,58 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, last_miss = 0; - bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); - bufferevent_setcb(bev, bev_read_cb, bev_write_cb, bev_event_cb, this); - bufferevent_enable(bev, EV_READ | EV_WRITE); - - if (options.binary) { - prot = new ProtocolBinary(options, this, bev); - } else if (options.redis) { - prot = new ProtocolRESP(options, this, bev); - } else { - prot = new ProtocolAscii(options, this, bev); - } - if (options.unix_socket) { - struct sockaddr_un sin; - memset(&sin, 0, sizeof(sin)); - sin.sun_family = AF_LOCAL; - strcpy(sin.sun_path, hostname.c_str()); - - static int addrlen; srand(time(NULL)); - int s = rand() % 10; - sleep(s); + int tries = 10000; + int connected = 0; + int s = 1; + for (int i = 0; i < tries; i++) { + + bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, bev_read_cb, bev_write_cb, bev_event_cb, this); + bufferevent_enable(bev, EV_READ | EV_WRITE); - addrlen = sizeof(sin); - if (bufferevent_socket_connect(bev, (struct sockaddr*)&sin, addrlen) < 0) { - DIE("bufferevent_socket_connect()"); + + struct sockaddr_un sin; + memset(&sin, 0, sizeof(sin)); + sin.sun_family = AF_LOCAL; + strcpy(sin.sun_path, hostname.c_str()); + + static int addrlen; + addrlen = sizeof(sin); + + if (bufferevent_socket_connect(bev, (struct sockaddr*)&sin, addrlen) == 0) { + connected = 1; + if (options.binary) { + prot = new ProtocolBinary(options, this, bev); + } else if (options.redis) { + prot = new ProtocolRESP(options, this, bev); + } else { + prot = new ProtocolAscii(options, this, bev); + } + break; + } + bufferevent_free(bev); + s = rand() % 10; + usleep(s); + + } + if (connected == 0) { + DIE("bufferevent_socket_connect()"); } } else { - if (bufferevent_socket_connect_hostname(bev, evdns, AF_UNSPEC, + bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, bev_read_cb, bev_write_cb, bev_event_cb, this); + bufferevent_enable(bev, EV_READ | EV_WRITE); + + if (options.binary) { + prot = new ProtocolBinary(options, this, bev); + } else if (options.redis) { + prot = new ProtocolRESP(options, this, bev); + } else { + prot = new ProtocolAscii(options, this, bev); + } + if (bufferevent_socket_connect_hostname(bev, evdns, AF_UNSPEC, hostname.c_str(), atoi(port.c_str()))) { DIE("bufferevent_socket_connect_hostname()"); @@ -515,7 +538,7 @@ void Connection::pop_op() { * Finish up (record stats) an operation that just returned from the * server. */ -void Connection::finish_op(Operation *op) { +void Connection::finish_op(Operation *op, int was_hit) { double now; #if USE_CACHED_TIME struct timeval now_tv; @@ -530,11 +553,20 @@ void Connection::finish_op(Operation *op) { op->end_time = now; #endif - switch (op->type) { - case Operation::GET: stats.log_get(*op); break; - case Operation::SET: stats.log_set(*op); break; - case Operation::DELETE: break; - default: DIE("Not implemented."); + if (options.successful_queries && was_hit) { + switch (op->type) { + case Operation::GET: stats.log_get(*op); break; + case Operation::SET: stats.log_set(*op); break; + case Operation::DELETE: break; + default: DIE("Not implemented."); + } + } else { + switch (op->type) { + case Operation::GET: stats.log_get(*op); break; + case Operation::SET: stats.log_set(*op); break; + case Operation::DELETE: break; + default: DIE("Not implemented."); + } } last_rx = now; @@ -819,7 +851,7 @@ void Connection::read_callback() { strcpy(key, keystr.c_str()); int valuelen = op->valuelen; - finish_op(op); // sets read_state = IDLE + finish_op(op,0); // sets read_state = IDLE //if not found and in getset mode, issue set if (options.read_file) { int index = lrand48() % (1024 * 1024); @@ -830,7 +862,7 @@ void Connection::read_callback() { issue_set(key, &random_char[index], valuelen); } } else { - finish_op(op); + finish_op(op,1); } @@ -854,12 +886,12 @@ void Connection::read_callback() { // r_time,r_appid,r_type,r_ksize,r_vsize,r_key,r_hit); //write(2,log,strlen(log)); - finish_op(op); + finish_op(op,1); break; case WAITING_FOR_DELETE: if (!prot->handle_response(input,done,found, obj_size)) return; - finish_op(op); + finish_op(op,1); break; case LOADING: diff --git a/Connection.h b/Connection.h index 07e45bc..b27dd37 100644 --- a/Connection.h +++ b/Connection.h @@ -120,7 +120,8 @@ class Connection { // state machine functions / event processing void pop_op(); - void finish_op(Operation *op); + //void finish_op(Operation *op); + void finish_op(Operation *op,int was_hit); void issue_something(double now = 0.0); void issue_getset(double now = 0.0); int issue_getsetorset(double now = 0.0); diff --git a/ConnectionOptions.h b/ConnectionOptions.h index 2792834..dbfee45 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -16,6 +16,7 @@ typedef struct { bool read_file; bool binary; bool unix_socket; + bool successful_queries; bool use_assoc; bool redis; bool getset; diff --git a/cmdline.ggo b/cmdline.ggo index 2e8a78b..0ef36d6 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -19,6 +19,7 @@ option "getset" - "Use getset mode, in getset mode we first issue \ a GET and if the response is MISS, then issue a SET for on that key following distribution value." option "getsetorset" - "Use getset mode and allow for direct writes (with optype == 2)." +option "successful" - "Only record latency and throughput stats for successful queries" option "prefix" - "Prefix all keys with a string (helps with multi-tennant eval)" string option "delete90" - "Delete 90 percent of keys after halfway through \ the workload, used to model Rumbel et. al. USENIX \ diff --git a/mutilate.cc b/mutilate.cc index 6449d6b..16af99b 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -784,7 +784,7 @@ int stick_this_thread_to_core(int core_id) { void* thread_main(void *arg) { struct thread_data *td = (struct thread_data *) arg; - stick_this_thread_to_core(td->id); + //stick_this_thread_to_core(td->id); ConnectionStats *cs = new ConnectionStats(); do_mutilate(*td->servers, *td->options, *cs, td->master @@ -1103,6 +1103,7 @@ void args_to_options(options_t* options) { options->twitter_trace = args.twitter_trace_arg; options->unix_socket = args.unix_socket_given; + options->successful_queries = args.successful_given; options->binary = args.binary_given; options->redis = args.redis_given; From 43dade9c5a2b5f4b93d19d0e8189ff440159ed08 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Tue, 9 Mar 2021 13:54:51 -0500 Subject: [PATCH 16/57] mut updates --- Connection.cc | 81 +++++++++++++++++++++++++++++++++++++++++++++++++-- Connection.h | 1 + README.md | 3 ++ mutilate.cc | 8 +++-- 4 files changed, 89 insertions(+), 4 deletions(-) diff --git a/Connection.cc b/Connection.cc index e8847a0..f8d9a91 100644 --- a/Connection.cc +++ b/Connection.cc @@ -259,6 +259,76 @@ void Connection::issue_getset(double now) { } +int Connection::issue_something_trace(double now) { + int ret = 0; + + string line; + string rT; + string rApp; + string rOp; + string rKey; + string rKeySize; + string rvaluelen; + + pthread_mutex_lock(&flock); + if (kvfile.good()) { + getline(kvfile,line); + pthread_mutex_unlock(&flock); + } + else { + pthread_mutex_unlock(&flock); + return 1; + } + stringstream ss(line); + int Op = 0; + + if (options.twitter_trace == 1) { + getline( ss, rT, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rKeySize, ',' ); + getline( ss, rvaluelen, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + if (rOp.compare("get") == 0) { + Op = 1; + } else if (rOp.compare("set") == 0) { + Op = 2; + } else { + Op = 1; + } + + } else { + getline( ss, rT, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + if (rOp.compare("read") == 0) + Op = 1; + if (rOp.compare("write") == 0) + Op = 2; + } + + + int vl = atoi(rvaluelen.c_str()); + if (vl < 8) vl = 8; + + //if (strcmp(key,"100004781") == 0) { + // fprintf(stderr,"ready!\n"); + //} + switch(Op) + { + case 1: + issue_get_with_len(rKey.c_str(), vl, now); + break; + case 2: + int index = lrand48() % (1024 * 1024); + issue_set(rKey.c_str(), &random_char[index], vl, now,true); + break; + } + return ret; +} + /** * Get/Set or Set Style * If a GET command: Issue a get first, if not found then set @@ -769,9 +839,12 @@ void Connection::drive_write_machine(double now) { else if (options.getsetorset) { int ret = issue_getsetorset(now); if (ret) return; //if at EOF + } else if (options.read_file) { + issue_something_trace(now); } - else + else { issue_something(now); + } last_tx = now; stats.log_op(op_queue.size()); @@ -862,7 +935,11 @@ void Connection::read_callback() { issue_set(key, &random_char[index], valuelen); } } else { - finish_op(op,1); + if (!found) { + finish_op(op,1); + } else { + finish_op(op,0); + } } diff --git a/Connection.h b/Connection.h index b27dd37..a6ae263 100644 --- a/Connection.h +++ b/Connection.h @@ -123,6 +123,7 @@ class Connection { //void finish_op(Operation *op); void finish_op(Operation *op,int was_hit); void issue_something(double now = 0.0); + int issue_something_trace(double now = 0.0); void issue_getset(double now = 0.0); int issue_getsetorset(double now = 0.0); void drive_write_machine(double now = 0.0); diff --git a/README.md b/README.md index 24bec9d..2b1904f 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ Command-line Options Basic options: -s, --server=STRING Memcached server hostname[:port]. Repeat to specify multiple servers. + --unix_socket Use UNIX socket instead of TCP. --binary Use binary memcached protocol instead of ASCII. --redis Use Redis RESP protocol instead of memchached. --getset Use getset mode, in getset mode we first issue @@ -139,6 +140,8 @@ Command-line Options key following distribution value. --getsetorset Use getset mode and allow for direct writes (with optype == 2). + --successful Only record latency and throughput stats for + successful queries --prefix=STRING Prefix all keys with a string (helps with multi-tennant eval) --delete90 Delete 90 percent of keys after halfway through diff --git a/mutilate.cc b/mutilate.cc index 16af99b..9e552f2 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -667,7 +667,7 @@ void go(const vector& servers, options_t& options, } #endif - if (options.read_file && (options.getset || options.getsetorset)) { + if (options.read_file) { kvfile.open(options.file_name); } @@ -784,7 +784,11 @@ int stick_this_thread_to_core(int core_id) { void* thread_main(void *arg) { struct thread_data *td = (struct thread_data *) arg; - //stick_this_thread_to_core(td->id); + int res = stick_this_thread_to_core(td->id); + if (res != 0) { + DIE("pthread_attr_setaffinity_np(%d) failed: %s", + td->id, strerror(res)); + } ConnectionStats *cs = new ConnectionStats(); do_mutilate(*td->servers, *td->options, *cs, td->master From 12b68389dfaf7fb92b95ebcb9bf93247e413994b Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 14 Apr 2021 10:58:58 -0400 Subject: [PATCH 17/57] updates --- Connection.cc | 77 ++++++++++++++++++++++++++++++++++++++++++++++----- mutilate.cc | 3 +- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/Connection.cc b/Connection.cc index f8d9a91..89f63e6 100644 --- a/Connection.cc +++ b/Connection.cc @@ -281,6 +281,7 @@ int Connection::issue_something_trace(double now) { } stringstream ss(line); int Op = 0; + int vl = 0; if (options.twitter_trace == 1) { getline( ss, rT, ',' ); @@ -294,8 +295,32 @@ int Connection::issue_something_trace(double now) { } else if (rOp.compare("set") == 0) { Op = 2; } else { - Op = 1; + Op = 0; } + + while (Op == 0) { + string line1; + pthread_mutex_lock(&flock); + if (kvfile.good()) { + getline(kvfile,line1); + pthread_mutex_unlock(&flock); + } + stringstream ss1(line1); + getline( ss1, rT, ',' ); + getline( ss1, rKey, ',' ); + getline( ss1, rKeySize, ',' ); + getline( ss1, rvaluelen, ',' ); + getline( ss1, rApp, ',' ); + getline( ss1, rOp, ',' ); + if (rOp.compare("get") == 0) { + Op = 1; + } else if (rOp.compare("set") == 0) { + Op = 2; + } else { + Op = 0; + } + } + vl = atoi(rvaluelen.c_str()); } else { getline( ss, rT, ',' ); @@ -307,12 +332,12 @@ int Connection::issue_something_trace(double now) { Op = 1; if (rOp.compare("write") == 0) Op = 2; + vl = atoi(rvaluelen.c_str()); } - int vl = atoi(rvaluelen.c_str()); if (vl < 8) vl = 8; - + //if (strcmp(key,"100004781") == 0) { // fprintf(stderr,"ready!\n"); //} @@ -380,6 +405,7 @@ int Connection::issue_getsetorset(double now) { } stringstream ss(line); int Op = 0; + int vl = 0; if (options.twitter_trace == 1) { getline( ss, rT, ',' ); @@ -395,13 +421,52 @@ int Connection::issue_getsetorset(double now) { } else { Op = 1; } + while (Op == 0) { + string line1; + pthread_mutex_lock(&flock); + if (kvfile.good()) { + getline(kvfile,line1); + pthread_mutex_unlock(&flock); + } + stringstream ss1(line1); + getline( ss1, rT, ',' ); + getline( ss1, rKey, ',' ); + getline( ss1, rKeySize, ',' ); + getline( ss1, rvaluelen, ',' ); + getline( ss1, rApp, ',' ); + getline( ss1, rOp, ',' ); + if (rOp.compare("get") == 0) { + Op = 1; + } else if (rOp.compare("set") == 0) { + Op = 2; + } else { + Op = 0; + } + } + vl = atoi(rvaluelen.c_str()); - } else { + } else if (options.twitter_trace == 2) { + getline( ss, rT, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + getline( ss, rOp, ',' ); + int op_n = atoi(rOp.c_str()); + + if (op_n == 1) + Op = 1; + else if (op_n == 0) { + Op = 2; + } + vl = atoi(rvaluelen.c_str()) - 76; + vl = vl - strlen(rKey.c_str()); + } + else { getline( ss, rT, ',' ); getline( ss, rApp, ',' ); getline( ss, rOp, ',' ); getline( ss, rKey, ',' ); getline( ss, rvaluelen, ',' ); + vl = atoi(rvaluelen.c_str()); if (rOp.compare("read") == 0) Op = 1; if (rOp.compare("write") == 0) @@ -409,8 +474,7 @@ int Connection::issue_getsetorset(double now) { } - int vl = atoi(rvaluelen.c_str()); - if (vl < 8) vl = 8; + if (vl < 100) vl = 100; char key[1024]; char skey[1024]; memset(key,0,1024); @@ -421,7 +485,6 @@ int Connection::issue_getsetorset(double now) { strncpy(key, options.prefix,strlen(options.prefix)); } strncat(key,skey,strlen(skey)); - //if (strcmp(key,"100004781") == 0) { // fprintf(stderr,"ready!\n"); //} diff --git a/mutilate.cc b/mutilate.cc index 9e552f2..23afcc3 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -784,7 +784,8 @@ int stick_this_thread_to_core(int core_id) { void* thread_main(void *arg) { struct thread_data *td = (struct thread_data *) arg; - int res = stick_this_thread_to_core(td->id); + int num_cores = sysconf(_SC_NPROCESSORS_ONLN); + int res = stick_this_thread_to_core(td->id % num_cores); if (res != 0) { DIE("pthread_attr_setaffinity_np(%d) failed: %s", td->id, strerror(res)); From 0014140f18dbe1e975e4abd42a68ac34c9069597 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 28 Apr 2021 21:07:15 -0400 Subject: [PATCH 18/57] debug info --- Connection.cc | 42 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/Connection.cc b/Connection.cc index 89f63e6..58526a3 100644 --- a/Connection.cc +++ b/Connection.cc @@ -290,6 +290,8 @@ int Connection::issue_something_trace(double now) { getline( ss, rvaluelen, ',' ); getline( ss, rApp, ',' ); getline( ss, rOp, ',' ); + vl = atoi(rvaluelen.c_str()); + if (vl < 1) vl = 1; if (rOp.compare("get") == 0) { Op = 1; } else if (rOp.compare("set") == 0) { @@ -312,6 +314,9 @@ int Connection::issue_something_trace(double now) { getline( ss1, rvaluelen, ',' ); getline( ss1, rApp, ',' ); getline( ss1, rOp, ',' ); + vl = atoi(rvaluelen.c_str()); + if (vl < 1) vl = 1; + if (rOp.compare("get") == 0) { Op = 1; } else if (rOp.compare("set") == 0) { @@ -320,7 +325,6 @@ int Connection::issue_something_trace(double now) { Op = 0; } } - vl = atoi(rvaluelen.c_str()); } else { getline( ss, rT, ',' ); @@ -336,8 +340,7 @@ int Connection::issue_something_trace(double now) { } - if (vl < 8) vl = 8; - + if (vl > 524000) vl = 524000; //if (strcmp(key,"100004781") == 0) { // fprintf(stderr,"ready!\n"); //} @@ -414,36 +417,15 @@ int Connection::issue_getsetorset(double now) { getline( ss, rvaluelen, ',' ); getline( ss, rApp, ',' ); getline( ss, rOp, ',' ); + vl = atoi(rvaluelen.c_str()); + if (vl < 1) vl = 1; if (rOp.compare("get") == 0) { Op = 1; } else if (rOp.compare("set") == 0) { Op = 2; } else { - Op = 1; - } - while (Op == 0) { - string line1; - pthread_mutex_lock(&flock); - if (kvfile.good()) { - getline(kvfile,line1); - pthread_mutex_unlock(&flock); - } - stringstream ss1(line1); - getline( ss1, rT, ',' ); - getline( ss1, rKey, ',' ); - getline( ss1, rKeySize, ',' ); - getline( ss1, rvaluelen, ',' ); - getline( ss1, rApp, ',' ); - getline( ss1, rOp, ',' ); - if (rOp.compare("get") == 0) { - Op = 1; - } else if (rOp.compare("set") == 0) { - Op = 2; - } else { - Op = 0; - } + Op = 0; } - vl = atoi(rvaluelen.c_str()); } else if (options.twitter_trace == 2) { getline( ss, rT, ',' ); @@ -474,7 +456,6 @@ int Connection::issue_getsetorset(double now) { } - if (vl < 100) vl = 100; char key[1024]; char skey[1024]; memset(key,0,1024); @@ -490,6 +471,10 @@ int Connection::issue_getsetorset(double now) { //} switch(Op) { + case 0: + fprintf(stderr,"invalid line: %s, vl: %d @T: %d\n", + key,vl,atoi(rT.c_str())); + break; case 1: issue_get_with_len(key, vl, now); break; @@ -497,6 +482,7 @@ int Connection::issue_getsetorset(double now) { int index = lrand48() % (1024 * 1024); issue_set(key, &random_char[index], vl, now,true); break; + } } From d450b136fc1e02cdd9c8648087b29bb25b6c628b Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Mon, 3 May 2021 15:54:14 -0400 Subject: [PATCH 19/57] update to max val size --- Connection.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/Connection.cc b/Connection.cc index 58526a3..d112c47 100644 --- a/Connection.cc +++ b/Connection.cc @@ -419,6 +419,7 @@ int Connection::issue_getsetorset(double now) { getline( ss, rOp, ',' ); vl = atoi(rvaluelen.c_str()); if (vl < 1) vl = 1; + if (vl > 524000) vl = 524000; if (rOp.compare("get") == 0) { Op = 1; } else if (rOp.compare("set") == 0) { From 9ca42781af74811eb9a093b0c5a3a79e907843be Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 5 May 2021 16:07:29 -0400 Subject: [PATCH 20/57] added request q --- Connection.cc | 27 +- Connection.h | 9 +- blockingconcurrentqueue.h | 582 ++++++ concurrentqueue.h | 3742 +++++++++++++++++++++++++++++++++++++ lightweightsemaphore.h | 411 ++++ mutilate.cc | 48 +- 6 files changed, 4805 insertions(+), 14 deletions(-) create mode 100644 blockingconcurrentqueue.h create mode 100644 concurrentqueue.h create mode 100644 lightweightsemaphore.h diff --git a/Connection.cc b/Connection.cc index d112c47..0df68b6 100644 --- a/Connection.cc +++ b/Connection.cc @@ -22,6 +22,10 @@ #include #include #include +#include "blockingconcurrentqueue.h" + + +using namespace moodycamel; extern ifstream kvfile; extern pthread_mutex_t flock; @@ -31,13 +35,17 @@ extern pthread_mutex_t flock; */ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, string _hostname, string _port, options_t _options, - bool sampling) : + BlockingConcurrentQueue* a_trace_queue, + bool sampling ) : start_time(0), stats(sampling), options(_options), hostname(_hostname), port(_port), base(_base), evdns(_evdns) { valuesize = createGenerator(options.valuesize); keysize = createGenerator(options.keysize); - + + trace_queue = a_trace_queue; + eof = 0; + keygen = new KeyGenerator(keysize, options.records); //if (options.read_file && (options.getset || options.getsetorset)) { // kvfile.open(options.file_name); @@ -357,6 +365,7 @@ int Connection::issue_something_trace(double now) { return ret; } + /** * Get/Set or Set Style * If a GET command: Issue a get first, if not found then set @@ -366,7 +375,7 @@ int Connection::issue_getsetorset(double now) { int ret = 0; - if (!options.read_file && !kvfile.is_open()) + if (!options.read_file) { string keystr; char key[256]; @@ -397,6 +406,14 @@ int Connection::issue_getsetorset(double now) { string rKeySize; string rvaluelen; + + trace_queue->wait_dequeue(line); + if (line.compare("EOF") == 0) { + eof = 1; + return 1; + } + + /* pthread_mutex_lock(&flock); if (kvfile.good()) { getline(kvfile,line); @@ -406,6 +423,7 @@ int Connection::issue_getsetorset(double now) { pthread_mutex_unlock(&flock); return 1; } + */ stringstream ss(line); int Op = 0; int vl = 0; @@ -720,9 +738,6 @@ bool Connection::check_exit_condition(double now) { if (now == 0.0) now = get_time(); if (options.read_file) { - pthread_mutex_lock(&flock); - int eof = kvfile.eof(); - pthread_mutex_unlock(&flock); if (eof) { return true; } diff --git a/Connection.h b/Connection.h index a6ae263..3737e3a 100644 --- a/Connection.h +++ b/Connection.h @@ -19,11 +19,11 @@ #include "Generator.h" #include "Operation.h" #include "util.h" - - +#include "blockingconcurrentqueue.h" #include "Protocol.h" using namespace std; +using namespace moodycamel; void bev_event_cb(struct bufferevent *bev, short events, void *ptr); void bev_read_cb(struct bufferevent *bev, void *ptr); @@ -36,6 +36,7 @@ class Connection { public: Connection(struct event_base* _base, struct evdns_base* _evdns, string _hostname, string _port, options_t options, + BlockingConcurrentQueue *a_trace_queue, bool sampling = true); ~Connection(); @@ -102,6 +103,8 @@ class Connection { char last_key[256]; int last_miss; + int eof; + //trace format variables double r_time; // time in seconds int r_appid; // prefix minus ':' char @@ -118,6 +121,8 @@ class Connection { Generator *iagen; std::queue op_queue; + BlockingConcurrentQueue *trace_queue; + // state machine functions / event processing void pop_op(); //void finish_op(Operation *op); diff --git a/blockingconcurrentqueue.h b/blockingconcurrentqueue.h new file mode 100644 index 0000000..66579b6 --- /dev/null +++ b/blockingconcurrentqueue.h @@ -0,0 +1,582 @@ +// Provides an efficient blocking version of moodycamel::ConcurrentQueue. +// ©2015-2020 Cameron Desrochers. Distributed under the terms of the simplified +// BSD license, available at the top of concurrentqueue.h. +// Also dual-licensed under the Boost Software License (see LICENSE.md) +// Uses Jeff Preshing's semaphore implementation (under the terms of its +// separate zlib license, see lightweightsemaphore.h). + +#pragma once + +#include "concurrentqueue.h" +#include "lightweightsemaphore.h" + +#include +#include +#include +#include +#include + +namespace moodycamel +{ +// This is a blocking version of the queue. It has an almost identical interface to +// the normal non-blocking version, with the addition of various wait_dequeue() methods +// and the removal of producer-specific dequeue methods. +template +class BlockingConcurrentQueue +{ +private: + typedef ::moodycamel::ConcurrentQueue ConcurrentQueue; + typedef ::moodycamel::LightweightSemaphore LightweightSemaphore; + +public: + typedef typename ConcurrentQueue::producer_token_t producer_token_t; + typedef typename ConcurrentQueue::consumer_token_t consumer_token_t; + + typedef typename ConcurrentQueue::index_t index_t; + typedef typename ConcurrentQueue::size_t size_t; + typedef typename std::make_signed::type ssize_t; + + static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE; + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD; + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE; + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE; + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE; + static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE; + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) + : inner(capacity), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) + { + assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); + if (!sema) { + MOODYCAMEL_THROW(std::bad_alloc()); + } + } + + BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) + { + assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); + if (!sema) { + MOODYCAMEL_THROW(std::bad_alloc()); + } + } + + // Disable copying and copy assignment + BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : inner(std::move(other.inner)), sema(std::move(other.sema)) + { } + + inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + inner.swap(other.inner); + sema.swap(other.sema); + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + if ((details::likely)(inner.enqueue(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + if ((details::likely)(inner.enqueue(std::move(item)))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + if ((details::likely)(inner.enqueue(token, item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + if ((details::likely)(inner.enqueue(token, std::move(item)))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + inline bool enqueue_bulk(It itemFirst, size_t count) + { + if ((details::likely)(inner.enqueue_bulk(std::forward(itemFirst), count))) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + if ((details::likely)(inner.enqueue_bulk(token, std::forward(itemFirst), count))) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + if (inner.try_enqueue(item)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + if (inner.try_enqueue(std::move(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + if (inner.try_enqueue(token, item)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + if (inner.try_enqueue(token, std::move(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool try_enqueue_bulk(It itemFirst, size_t count) + { + if (inner.try_enqueue_bulk(std::forward(itemFirst), count)) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + if (inner.try_enqueue_bulk(token, std::forward(itemFirst), count)) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue(U& item) + { + if (sema->tryWait()) { + while (!inner.try_dequeue(item)) { + continue; + } + return true; + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue(consumer_token_t& token, U& item) + { + if (sema->tryWait()) { + while (!inner.try_dequeue(token, item)) { + continue; + } + return true; + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + + + // Blocks the current thread until there's something to dequeue, then + // dequeues it. + // Never allocates. Thread-safe. + template + inline void wait_dequeue(U& item) + { + while (!sema->wait()) { + continue; + } + while (!inner.try_dequeue(item)) { + continue; + } + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout (specified in microseconds) expires. Returns false + // without setting `item` if the timeout expires, otherwise assigns + // to `item` and returns true. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs) + { + if (!sema->wait(timeout_usecs)) { + return false; + } + while (!inner.try_dequeue(item)) { + continue; + } + return true; + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout expires. Returns false without setting `item` if the + // timeout expires, otherwise assigns to `item` and returns true. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(U& item, std::chrono::duration const& timeout) + { + return wait_dequeue_timed(item, std::chrono::duration_cast(timeout).count()); + } + + // Blocks the current thread until there's something to dequeue, then + // dequeues it using an explicit consumer token. + // Never allocates. Thread-safe. + template + inline void wait_dequeue(consumer_token_t& token, U& item) + { + while (!sema->wait()) { + continue; + } + while (!inner.try_dequeue(token, item)) { + continue; + } + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout (specified in microseconds) expires. Returns false + // without setting `item` if the timeout expires, otherwise assigns + // to `item` and returns true. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs) + { + if (!sema->wait(timeout_usecs)) { + return false; + } + while (!inner.try_dequeue(token, item)) { + continue; + } + return true; + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout expires. Returns false without setting `item` if the + // timeout expires, otherwise assigns to `item` and returns true. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration const& timeout) + { + return wait_dequeue_timed(token, item, std::chrono::duration_cast(timeout).count()); + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which will + // always be at least one (this method blocks until the queue + // is non-empty) and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue_bulk. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration const& timeout) + { + return wait_dequeue_bulk_timed(itemFirst, max, std::chrono::duration_cast(timeout).count()); + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which will + // always be at least one (this method blocks until the queue + // is non-empty) and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue_bulk. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration const& timeout) + { + return wait_dequeue_bulk_timed(token, itemFirst, max, std::chrono::duration_cast(timeout).count()); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + inline size_t size_approx() const + { + return (size_t)sema->availableApprox(); + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static bool is_lock_free() + { + return ConcurrentQueue::is_lock_free(); + } + + +private: + template + static inline U* create(A1&& a1, A2&& a2) + { + void* p = (Traits::malloc)(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1), std::forward(a2)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) { + p->~U(); + } + (Traits::free)(p); + } + +private: + ConcurrentQueue inner; + std::unique_ptr sema; +}; + + +template +inline void swap(BlockingConcurrentQueue& a, BlockingConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} // end namespace moodycamel diff --git a/concurrentqueue.h b/concurrentqueue.h new file mode 100644 index 0000000..b38d218 --- /dev/null +++ b/concurrentqueue.h @@ -0,0 +1,3742 @@ +// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. +// An overview, including benchmark results, is provided here: +// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ +// The full design is also described in excruciating detail at: +// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue + +// Simplified BSD license: +// Copyright (c) 2013-2020, Cameron Desrochers. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Also dual-licensed under the Boost Software License (see LICENSE.md) + +#pragma once + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and +// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings +// upon assigning any computed values) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" + +#ifdef MCDBGQ_USE_RELACY +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +#endif +#endif + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +// VS2019 with /W4 warns about constant conditional expressions but unless /std=c++17 or higher +// does not support `if constexpr`, so we have no choice but to simply disable the warning +#pragma warning(push) +#pragma warning(disable: 4127) // conditional expression is constant +#endif + +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif + +#ifdef MCDBGQ_USE_RELACY +#include "relacy/relacy_std.hpp" +#include "relacy_shims.h" +// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. +// We'll override the default trait malloc ourselves without a macro. +#undef new +#undef delete +#undef malloc +#undef free +#else +#include // Requires C++11. Sorry VS2010. +#include +#endif +#include // for max_align_t +#include +#include +#include +#include +#include +#include +#include // for CHAR_BIT +#include +#include // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading + +// Platform-specific definitions of a numeric thread ID type and an invalid value +namespace moodycamel { namespace details { + template struct thread_id_converter { + typedef thread_id_t thread_id_numeric_size_t; + typedef thread_id_t thread_id_hash_t; + static thread_id_hash_t prehash(thread_id_t const& x) { return x; } + }; +} } +#if defined(MCDBGQ_USE_RELACY) +namespace moodycamel { namespace details { + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; + static inline thread_id_t thread_id() { return rl::thread_index(); } +} } +#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) +// No sense pulling in windows.h in a header, we'll manually declare the function +// we use and rely on backwards-compatibility for this not to break +extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +namespace moodycamel { namespace details { + static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. + static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } +} } +#elif defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) || (defined(__APPLE__) && TARGET_OS_IPHONE) +namespace moodycamel { namespace details { + static_assert(sizeof(std::thread::id) == 4 || sizeof(std::thread::id) == 8, "std::thread::id is expected to be either 4 or 8 bytes"); + + typedef std::thread::id thread_id_t; + static const thread_id_t invalid_thread_id; // Default ctor creates invalid ID + + // Note we don't define a invalid_thread_id2 since std::thread::id doesn't have one; it's + // only used if MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is defined anyway, which it won't + // be. + static inline thread_id_t thread_id() { return std::this_thread::get_id(); } + + template struct thread_id_size { }; + template<> struct thread_id_size<4> { typedef std::uint32_t numeric_t; }; + template<> struct thread_id_size<8> { typedef std::uint64_t numeric_t; }; + + template<> struct thread_id_converter { + typedef thread_id_size::numeric_t thread_id_numeric_size_t; +#ifndef __APPLE__ + typedef std::size_t thread_id_hash_t; +#else + typedef thread_id_numeric_size_t thread_id_hash_t; +#endif + + static thread_id_hash_t prehash(thread_id_t const& x) + { +#ifndef __APPLE__ + return std::hash()(x); +#else + return *reinterpret_cast(&x); +#endif + } + }; +} } +#else +// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 +// In order to get a numeric thread ID in a platform-independent way, we use a thread-local +// static variable's address as a thread identifier :-) +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +#define MOODYCAMEL_THREADLOCAL __thread +#elif defined(_MSC_VER) +#define MOODYCAMEL_THREADLOCAL __declspec(thread) +#else +// Assume C++11 compliant compiler +#define MOODYCAMEL_THREADLOCAL thread_local +#endif +namespace moodycamel { namespace details { + typedef std::uintptr_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr + static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. + inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast(&x); } +} } +#endif + +// Constexpr if +#ifndef MOODYCAMEL_CONSTEXPR_IF +#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || __cplusplus > 201402L +#define MOODYCAMEL_CONSTEXPR_IF if constexpr +#define MOODYCAMEL_MAYBE_UNUSED [[maybe_unused]] +#else +#define MOODYCAMEL_CONSTEXPR_IF if +#define MOODYCAMEL_MAYBE_UNUSED +#endif +#endif + +// Exceptions +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#endif +#endif +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED +#define MOODYCAMEL_TRY try +#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) +#define MOODYCAMEL_RETHROW throw +#define MOODYCAMEL_THROW(expr) throw (expr) +#else +#define MOODYCAMEL_TRY MOODYCAMEL_CONSTEXPR_IF (true) +#define MOODYCAMEL_CATCH(...) else MOODYCAMEL_CONSTEXPR_IF (false) +#define MOODYCAMEL_RETHROW +#define MOODYCAMEL_THROW(expr) +#endif + +#ifndef MOODYCAMEL_NOEXCEPT +#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) +#define MOODYCAMEL_NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 +// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( +// We have to assume *all* non-trivial constructors may throw on VS2012! +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#else +#define MOODYCAMEL_NOEXCEPT noexcept +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) +#endif +#endif + +#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#else +// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 +// g++ <=4.7 doesn't support thread_local either. +// Finally, iOS/ARM doesn't have support for it either, and g++/ARM allows it to compile but it's unconfirmed to actually work +#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && (!defined(__APPLE__) || !TARGET_OS_IPHONE) && !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__) +// Assume `thread_local` is fully supported in all other C++11 compilers/platforms +//#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED // always disabled for now since several users report having problems with it on +#endif +#endif +#endif + +// VS2012 doesn't support deleted functions. +// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. +#ifndef MOODYCAMEL_DELETE_FUNCTION +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define MOODYCAMEL_DELETE_FUNCTION +#else +#define MOODYCAMEL_DELETE_FUNCTION = delete +#endif +#endif + +namespace moodycamel { namespace details { +#ifndef MOODYCAMEL_ALIGNAS +// VS2013 doesn't support alignas or alignof, and align() requires a constant literal +#if defined(_MSC_VER) && _MSC_VER <= 1800 +#define MOODYCAMEL_ALIGNAS(alignment) __declspec(align(alignment)) +#define MOODYCAMEL_ALIGNOF(obj) __alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) typename details::Vs2013Aligned::value, T>::type + template struct Vs2013Aligned { }; // default, unsupported alignment + template struct Vs2013Aligned<1, T> { typedef __declspec(align(1)) T type; }; + template struct Vs2013Aligned<2, T> { typedef __declspec(align(2)) T type; }; + template struct Vs2013Aligned<4, T> { typedef __declspec(align(4)) T type; }; + template struct Vs2013Aligned<8, T> { typedef __declspec(align(8)) T type; }; + template struct Vs2013Aligned<16, T> { typedef __declspec(align(16)) T type; }; + template struct Vs2013Aligned<32, T> { typedef __declspec(align(32)) T type; }; + template struct Vs2013Aligned<64, T> { typedef __declspec(align(64)) T type; }; + template struct Vs2013Aligned<128, T> { typedef __declspec(align(128)) T type; }; + template struct Vs2013Aligned<256, T> { typedef __declspec(align(256)) T type; }; +#else + template struct identity { typedef T type; }; +#define MOODYCAMEL_ALIGNAS(alignment) alignas(alignment) +#define MOODYCAMEL_ALIGNOF(obj) alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) alignas(alignof(obj)) typename details::identity::type +#endif +#endif +} } + + +// TSAN can false report races in lock-free code. To enable TSAN to be used from projects that use this one, +// we can apply per-function compile-time suppression. +// See https://clang.llvm.org/docs/ThreadSanitizer.html#has-feature-thread-sanitizer +#define MOODYCAMEL_NO_TSAN +#if defined(__has_feature) + #if __has_feature(thread_sanitizer) + #undef MOODYCAMEL_NO_TSAN + #define MOODYCAMEL_NO_TSAN __attribute__((no_sanitize("thread"))) + #endif // TSAN +#endif // TSAN + +// Compiler-specific likely/unlikely hints +namespace moodycamel { namespace details { +#if defined(__GNUC__) + static inline bool (likely)(bool x) { return __builtin_expect((x), true); } + static inline bool (unlikely)(bool x) { return __builtin_expect((x), false); } +#else + static inline bool (likely)(bool x) { return x; } + static inline bool (unlikely)(bool x) { return x; } +#endif +} } + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG +#include "internal/concurrentqueue_internal_debug.h" +#endif + +namespace moodycamel { +namespace details { + template + struct const_numeric_max { + static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); + static const T value = std::numeric_limits::is_signed + ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) + : static_cast(-1); + }; + +#if defined(__GLIBCXX__) + typedef ::max_align_t std_max_align_t; // libstdc++ forgot to add it to std:: for a while +#else + typedef std::max_align_t std_max_align_t; // Others (e.g. MSVC) insist it can *only* be accessed via std:: +#endif + + // Some platforms have incorrectly set max_align_t to a type with <8 bytes alignment even while supporting + // 8-byte aligned scalar values (*cough* 32-bit iOS). Work around this with our own union. See issue #64. + typedef union { + std_max_align_t x; + long long y; + void* z; + } max_align_t; +} + +// Default traits for the ConcurrentQueue. To change some of the +// traits without re-implementing all of them, inherit from this +// struct and shadow the declarations you wish to be different; +// since the traits are used as a template type parameter, the +// shadowed declarations will be used where defined, and the defaults +// otherwise. +struct ConcurrentQueueDefaultTraits +{ + // General-purpose size type. std::size_t is strongly recommended. + typedef std::size_t size_t; + + // The type used for the enqueue and dequeue indices. Must be at least as + // large as size_t. Should be significantly larger than the number of elements + // you expect to hold at once, especially if you have a high turnover rate; + // for example, on 32-bit x86, if you expect to have over a hundred million + // elements or pump several million elements through your queue in a very + // short space of time, using a 32-bit type *may* trigger a race condition. + // A 64-bit int type is recommended in that case, and in practice will + // prevent a race condition no matter the usage of the queue. Note that + // whether the queue is lock-free with a 64-int type depends on the whether + // std::atomic is lock-free, which is platform-specific. + typedef std::size_t index_t; + + // Internally, all elements are enqueued and dequeued from multi-element + // blocks; this is the smallest controllable unit. If you expect few elements + // but many producers, a smaller block size should be favoured. For few producers + // and/or many elements, a larger block size is preferred. A sane default + // is provided. Must be a power of 2. + static const size_t BLOCK_SIZE = 32; + + // For explicit producers (i.e. when using a producer token), the block is + // checked for being empty by iterating through a list of flags, one per element. + // For large block sizes, this is too inefficient, and switching to an atomic + // counter-based approach is faster. The switch is made for block sizes strictly + // larger than this threshold. + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; + + // How many full blocks can be expected for a single explicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; + + // How many full blocks can be expected for a single implicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; + + // The initial size of the hash table mapping thread IDs to implicit producers. + // Note that the hash is resized every time it becomes half full. + // Must be a power of two, and either 0 or at least 1. If 0, implicit production + // (using the enqueue methods without an explicit producer token) is disabled. + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; + + // Controls the number of items that an explicit consumer (i.e. one with a token) + // must consume before it causes all consumers to rotate and move on to the next + // internal queue. + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; + + // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. + // Enqueue operations that would cause this limit to be surpassed will fail. Note + // that this limit is enforced at the block level (for performance reasons), i.e. + // it's rounded up to the nearest block size. + static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; + + // The number of times to spin before sleeping when waiting on a semaphore. + // Recommended values are on the order of 1000-10000 unless the number of + // consumer threads exceeds the number of idle cores (in which case try 0-100). + // Only affects instances of the BlockingConcurrentQueue. + static const int MAX_SEMA_SPINS = 10000; + + +#ifndef MCDBGQ_USE_RELACY + // Memory allocation can be customized if needed. + // malloc should return nullptr on failure, and handle alignment like std::malloc. +#if defined(malloc) || defined(free) + // Gah, this is 2015, stop defining macros that break standard code already! + // Work around malloc/free being special macros: + static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); } + static inline void WORKAROUND_free(void* ptr) { return free(ptr); } + static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); } + static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); } +#else + static inline void* malloc(size_t size) { return std::malloc(size); } + static inline void free(void* ptr) { return std::free(ptr); } +#endif +#else + // Debug versions when running under the Relacy race detector (ignore + // these in user code) + static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } + static inline void free(void* ptr) { return rl::rl_free(ptr, $); } +#endif +}; + + +// When producing or consuming many elements, the most efficient way is to: +// 1) Use one of the bulk-operation methods of the queue with a token +// 2) Failing that, use the bulk-operation methods without a token +// 3) Failing that, create a token and use that with the single-item methods +// 4) Failing that, use the single-parameter methods of the queue +// Having said that, don't create tokens willy-nilly -- ideally there should be +// a maximum of one token per thread (of each kind). +struct ProducerToken; +struct ConsumerToken; + +template class ConcurrentQueue; +template class BlockingConcurrentQueue; +class ConcurrentQueueTests; + + +namespace details +{ + struct ConcurrentQueueProducerTypelessBase + { + ConcurrentQueueProducerTypelessBase* next; + std::atomic inactive; + ProducerToken* token; + + ConcurrentQueueProducerTypelessBase() + : next(nullptr), inactive(false), token(nullptr) + { + } + }; + + template struct _hash_32_or_64 { + static inline std::uint32_t hash(std::uint32_t h) + { + // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + // Since the thread ID is already unique, all we really want to do is propagate that + // uniqueness evenly across all the bits, so that we can use a subset of the bits while + // reducing collisions significantly + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + return h ^ (h >> 16); + } + }; + template<> struct _hash_32_or_64<1> { + static inline std::uint64_t hash(std::uint64_t h) + { + h ^= h >> 33; + h *= 0xff51afd7ed558ccd; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53; + return h ^ (h >> 33); + } + }; + template struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { }; + + static inline size_t hash_thread_id(thread_id_t id) + { + static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); + return static_cast(hash_32_or_64::thread_id_hash_t)>::hash( + thread_id_converter::prehash(id))); + } + + template + static inline bool circular_less_than(T a, T b) + { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4554) +#endif + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); + return static_cast(a - b) > static_cast(static_cast(1) << static_cast(sizeof(T) * CHAR_BIT - 1)); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } + + template + static inline char* align_for(char* ptr) + { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + + template + static inline T ceil_to_pow_2(T x) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); + + // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (std::size_t i = 1; i < sizeof(T); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static inline void swap_relaxed(std::atomic& left, std::atomic& right) + { + T temp = std::move(left.load(std::memory_order_relaxed)); + left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); + right.store(std::move(temp), std::memory_order_relaxed); + } + + template + static inline T const& nomove(T const& x) + { + return x; + } + + template + struct nomove_if + { + template + static inline T const& eval(T const& x) + { + return x; + } + }; + + template<> + struct nomove_if + { + template + static inline auto eval(U&& x) + -> decltype(std::forward(x)) + { + return std::forward(x); + } + }; + + template + static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) + { + return *it; + } + +#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + template struct is_trivially_destructible : std::is_trivially_destructible { }; +#else + template struct is_trivially_destructible : std::has_trivial_destructor { }; +#endif + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY + typedef RelacyThreadExitListener ThreadExitListener; + typedef RelacyThreadExitNotifier ThreadExitNotifier; +#else + struct ThreadExitListener + { + typedef void (*callback_t)(void*); + callback_t callback; + void* userData; + + ThreadExitListener* next; // reserved for use by the ThreadExitNotifier + }; + + + class ThreadExitNotifier + { + public: + static void subscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + listener->next = tlsInst.tail; + tlsInst.tail = listener; + } + + static void unsubscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + ThreadExitListener** prev = &tlsInst.tail; + for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { + if (ptr == listener) { + *prev = ptr->next; + break; + } + prev = &ptr->next; + } + } + + private: + ThreadExitNotifier() : tail(nullptr) { } + ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + + ~ThreadExitNotifier() + { + // This thread is about to exit, let everyone know! + assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); + for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { + ptr->callback(ptr->userData); + } + } + + // Thread-local + static inline ThreadExitNotifier& instance() + { + static thread_local ThreadExitNotifier notifier; + return notifier; + } + + private: + ThreadExitListener* tail; + }; +#endif +#endif + + template struct static_is_lock_free_num { enum { value = 0 }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; + template struct static_is_lock_free : static_is_lock_free_num::type> { }; + template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; + template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; +} + + +struct ProducerToken +{ + template + explicit ProducerToken(ConcurrentQueue& queue); + + template + explicit ProducerToken(BlockingConcurrentQueue& queue); + + ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + : producer(other.producer) + { + other.producer = nullptr; + if (producer != nullptr) { + producer->token = this; + } + } + + inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(producer, other.producer); + if (producer != nullptr) { + producer->token = this; + } + if (other.producer != nullptr) { + other.producer->token = &other; + } + } + + // A token is always valid unless: + // 1) Memory allocation failed during construction + // 2) It was moved via the move constructor + // (Note: assignment does a swap, leaving both potentially valid) + // 3) The associated queue was destroyed + // Note that if valid() returns true, that only indicates + // that the token is valid for use with a specific queue, + // but not which one; that's up to the user to track. + inline bool valid() const { return producer != nullptr; } + + ~ProducerToken() + { + if (producer != nullptr) { + producer->token = nullptr; + producer->inactive.store(true, std::memory_order_release); + } + } + + // Disable copying and assignment + ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +protected: + details::ConcurrentQueueProducerTypelessBase* producer; +}; + + +struct ConsumerToken +{ + template + explicit ConsumerToken(ConcurrentQueue& q); + + template + explicit ConsumerToken(BlockingConcurrentQueue& q); + + ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) + { + } + + inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(initialOffset, other.initialOffset); + std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); + std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); + std::swap(currentProducer, other.currentProducer); + std::swap(desiredProducer, other.desiredProducer); + } + + // Disable copying and assignment + ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +private: // but shared with ConcurrentQueue + std::uint32_t initialOffset; + std::uint32_t lastKnownGlobalOffset; + std::uint32_t itemsConsumedFromCurrent; + details::ConcurrentQueueProducerTypelessBase* currentProducer; + details::ConcurrentQueueProducerTypelessBase* desiredProducer; +}; + +// Need to forward-declare this swap because it's in a namespace. +// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; + + +template +class ConcurrentQueue +{ +public: + typedef ::moodycamel::ProducerToken producer_token_t; + typedef ::moodycamel::ConsumerToken consumer_token_t; + + typedef typename Traits::index_t index_t; + typedef typename Traits::size_t size_t; + + static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) +#pragma warning(disable: 4309) // static_cast: Truncation of constant value +#endif + static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); + static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); + static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); + static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); + static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); + static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit ConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + // Track all the producers using a fully-resolved typed list for + // each kind; this makes it possible to debug them starting from + // the root queue object (otherwise wacky casts are needed that + // don't compile in the debugger's expression evaluator). + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Computes the correct amount of pre-allocated blocks for you based + // on the minimum number of elements you want available at any given + // time, and the maximum concurrent number of each type of producer. + ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers); + populate_initial_block_list(blocks); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + // This method is not thread safe. + ~ConcurrentQueue() + { + // Destroy producers + auto ptr = producerListTail.load(std::memory_order_relaxed); + while (ptr != nullptr) { + auto next = ptr->next_prod(); + if (ptr->token != nullptr) { + ptr->token->producer = nullptr; + } + destroy(ptr); + ptr = next; + } + + // Destroy implicit producer hash tables + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { + auto hash = implicitProducerHash.load(std::memory_order_relaxed); + while (hash != nullptr) { + auto prev = hash->prev; + if (prev != nullptr) { // The last hash is part of this object and was not allocated dynamically + for (size_t i = 0; i != hash->capacity; ++i) { + hash->entries[i].~ImplicitProducerKVP(); + } + hash->~ImplicitProducerHash(); + (Traits::free)(hash); + } + hash = prev; + } + } + + // Destroy global free list + auto block = freeList.head_unsafe(); + while (block != nullptr) { + auto next = block->freeListNext.load(std::memory_order_relaxed); + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } + + // Destroy initial free list + destroy_array(initialBlockPool, initialBlockPoolSize); + } + + // Disable copying and copy assignment + ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), + producerCount(other.producerCount.load(std::memory_order_relaxed)), + initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), + initialBlockPool(other.initialBlockPool), + initialBlockPoolSize(other.initialBlockPoolSize), + freeList(std::move(other.freeList)), + nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)), + globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed)) + { + // Move the other one into this, and leave the other one as an empty queue + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + swap_implicit_producer_hashes(other); + + other.producerListTail.store(nullptr, std::memory_order_relaxed); + other.producerCount.store(0, std::memory_order_relaxed); + other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); + other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + + other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); + other.initialBlockPoolSize = 0; + other.initialBlockPool = nullptr; + + reown_producers(); + } + + inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + ConcurrentQueue& swap_internal(ConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + details::swap_relaxed(producerListTail, other.producerListTail); + details::swap_relaxed(producerCount, other.producerCount); + details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); + std::swap(initialBlockPool, other.initialBlockPool); + std::swap(initialBlockPoolSize, other.initialBlockPoolSize); + freeList.swap(other.freeList); + details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); + details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); + + swap_implicit_producer_hashes(other); + + reown_producers(); + other.reown_producers(); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + details::swap_relaxed(explicitProducers, other.explicitProducers); + details::swap_relaxed(implicitProducers, other.implicitProducers); +#endif + + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + bool enqueue_bulk(It itemFirst, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, itemFirst, count); + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(It itemFirst, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, itemFirst, count); + } + + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(U& item) + { + // Instead of simply trying each producer in turn (which could cause needless contention on the first + // producer), we score them heuristically. + size_t nonEmptyCount = 0; + ProducerBase* best = nullptr; + size_t bestSize = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { + auto size = ptr->size_approx(); + if (size > 0) { + if (size > bestSize) { + bestSize = size; + best = ptr; + } + ++nonEmptyCount; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (nonEmptyCount > 0) { + if ((details::likely)(best->dequeue(item))) { + return true; + } + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr != best && ptr->dequeue(item)) { + return true; + } + } + } + return false; + } + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // This differs from the try_dequeue(item) method in that this one does + // not attempt to reduce contention by interleaving the order that producer + // streams are dequeued from. So, using this method can reduce overall throughput + // under contention, but will give more predictable results in single-threaded + // consumer scenarios. This is mostly only useful for internal unit tests. + // Never allocates. Thread-safe. + template + bool try_dequeue_non_interleaved(U& item) + { + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->dequeue(item)) { + return true; + } + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(consumer_token_t& token, U& item) + { + // The idea is roughly as follows: + // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less + // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place + // If there's no items where you're supposed to be, keep moving until you find a producer with some items + // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it + + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return false; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (static_cast(token.currentProducer)->dequeue(item)) { + if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return true; + } + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + if (ptr->dequeue(item)) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = 1; + return true; + } + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + count += ptr->dequeue_bulk(itemFirst, max - count); + if (count == max) { + break; + } + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return 0; + } + } + + size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); + if (count == max) { + if ((token.itemsConsumedFromCurrent += static_cast(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return max; + } + token.itemsConsumedFromCurrent += static_cast(count); + max -= count; + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + auto dequeued = ptr->dequeue_bulk(itemFirst, max); + count += dequeued; + if (dequeued != 0) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = static_cast(dequeued); + } + if (dequeued == max) { + break; + } + max -= dequeued; + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return count; + } + + + + // Attempts to dequeue from a specific producer's inner queue. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns false if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item) + { + return static_cast(producer.producer)->dequeue(item); + } + + // Attempts to dequeue several elements from a specific producer's inner queue. + // Returns the number of items actually dequeued. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns 0 if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max) + { + return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + size_t size_approx() const + { + size_t size = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + size += ptr->size_approx(); + } + return size; + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static bool is_lock_free() + { + return + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::thread_id_numeric_size_t>::value == 2; + } + + +private: + friend struct ProducerToken; + friend struct ConsumerToken; + struct ExplicitProducer; + friend struct ExplicitProducer; + struct ImplicitProducer; + friend struct ImplicitProducer; + friend class ConcurrentQueueTests; + + enum AllocationMode { CanAlloc, CannotAlloc }; + + + /////////////////////////////// + // Queue methods + /////////////////////////////// + + template + inline bool inner_enqueue(producer_token_t const& token, U&& element) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue(U&& element) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk(itemFirst, count); + } + + template + inline bool inner_enqueue_bulk(It itemFirst, size_t count) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk(itemFirst, count); + } + + inline bool update_current_producer_after_rotation(consumer_token_t& token) + { + // Ah, there's been a rotation, figure out where we should be! + auto tail = producerListTail.load(std::memory_order_acquire); + if (token.desiredProducer == nullptr && tail == nullptr) { + return false; + } + auto prodCount = producerCount.load(std::memory_order_relaxed); + auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); + if ((details::unlikely)(token.desiredProducer == nullptr)) { + // Aha, first time we're dequeueing anything. + // Figure out our local position + // Note: offset is from start, not end, but we're traversing from end -- subtract from count first + std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); + token.desiredProducer = tail; + for (std::uint32_t i = 0; i != offset; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + } + + std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; + if (delta >= prodCount) { + delta = delta % prodCount; + } + for (std::uint32_t i = 0; i != delta; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + + token.lastKnownGlobalOffset = globalOffset; + token.currentProducer = token.desiredProducer; + token.itemsConsumedFromCurrent = 0; + return true; + } + + + /////////////////////////// + // Free list + /////////////////////////// + + template + struct FreeListNode + { + FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } + + std::atomic freeListRefs; + std::atomic freeListNext; + }; + + // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but + // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly + // speedy under low contention. + template // N must inherit FreeListNode or have the same fields (and initialization of them) + struct FreeList + { + FreeList() : freeListHead(nullptr) { } + FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } + void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } + + FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + + inline void add(N* node) + { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to + // set it using a fetch_add + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { + // Oh look! We were the last ones referencing this node, and we know + // we want to add it to the free list, so let's do it! + add_knowing_refcount_is_zero(node); + } + } + + inline N* try_get() + { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + auto head = freeListHead.load(std::memory_order_acquire); + while (head != nullptr) { + auto prevHead = head; + auto refs = head->freeListRefs.load(std::memory_order_relaxed); + if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { + head = freeListHead.load(std::memory_order_acquire); + continue; + } + + // Good, reference count has been incremented (it wasn't at zero), which means we can read the + // next and not worry about it changing between now and the time we do the CAS + auto next = head->freeListNext.load(std::memory_order_relaxed); + if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { + // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no + // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). + assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); + + // Decrease refcount twice, once for our ref, and once for the list's ref + head->freeListRefs.fetch_sub(2, std::memory_order_release); + return head; + } + + // OK, the head must have changed on us, but we still need to decrease the refcount we increased. + // Note that we don't need to release any memory effects, but we do need to ensure that the reference + // count decrement happens-after the CAS on the head. + refs = prevHead->freeListRefs.fetch_sub(1, std::memory_order_acq_rel); + if (refs == SHOULD_BE_ON_FREELIST + 1) { + add_knowing_refcount_is_zero(prevHead); + } + } + + return nullptr; + } + + // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) + N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } + + private: + inline void add_knowing_refcount_is_zero(N* node) + { + // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run + // only one copy of this method per node at a time, i.e. the single thread case), then we know + // we can safely change the next pointer of the node; however, once the refcount is back above + // zero, then other threads could increase it (happens under heavy contention, when the refcount + // goes to zero in between a load and a refcount increment of a node in try_get, then back up to + // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS + // to add the node to the actual list fails, decrease the refcount and leave the add operation to + // the next thread who puts the refcount back at zero (which could be us, hence the loop). + auto head = freeListHead.load(std::memory_order_relaxed); + while (true) { + node->freeListNext.store(head, std::memory_order_relaxed); + node->freeListRefs.store(1, std::memory_order_release); + if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { + // Hmm, the add failed, but we can only try again when the refcount goes back to zero + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { + continue; + } + } + return; + } + } + + private: + // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) + std::atomic freeListHead; + + static const std::uint32_t REFS_MASK = 0x7FFFFFFF; + static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; + +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugMutex mutex; +#endif + }; + + + /////////////////////////// + // Block + /////////////////////////// + + enum InnerQueueContext { implicit_context = 0, explicit_context = 1 }; + + struct Block + { + Block() + : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), shouldBeOnFreeList(false), dynamicallyAllocated(true) + { +#ifdef MCDBGQ_TRACKMEM + owner = nullptr; +#endif + } + + template + inline bool is_empty() const + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Check flags + for (size_t i = 0; i < BLOCK_SIZE; ++i) { + if (!emptyFlags[i].load(std::memory_order_relaxed)) { + return false; + } + } + + // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + else { + // Check counter + if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); + return false; + } + } + + // Returns true if the block is now empty (does not apply in explicit context) + template + inline bool set_empty(MOODYCAMEL_MAYBE_UNUSED index_t i) + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flag + assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); + emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); + assert(prevVal < BLOCK_SIZE); + return prevVal == BLOCK_SIZE - 1; + } + } + + // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). + // Returns true if the block is now empty (does not apply in explicit context). + template + inline bool set_many_empty(MOODYCAMEL_MAYBE_UNUSED index_t i, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flags + std::atomic_thread_fence(std::memory_order_release); + i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; + for (size_t j = 0; j != count; ++j) { + assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); + emptyFlags[i + j].store(true, std::memory_order_relaxed); + } + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); + assert(prevVal + count <= BLOCK_SIZE); + return prevVal + count == BLOCK_SIZE; + } + } + + template + inline void set_all_empty() + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set all flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(true, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); + } + } + + template + inline void reset_empty() + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Reset flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(false, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(0, std::memory_order_relaxed); + } + } + + inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + + private: + static_assert(std::alignment_of::value <= sizeof(T), "The queue does not support types with an alignment greater than their size at this time"); + MOODYCAMEL_ALIGNED_TYPE_LIKE(char[sizeof(T) * BLOCK_SIZE], T) elements; + public: + Block* next; + std::atomic elementsCompletelyDequeued; + std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; + public: + std::atomic freeListRefs; + std::atomic freeListNext; + std::atomic shouldBeOnFreeList; + bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' + +#ifdef MCDBGQ_TRACKMEM + void* owner; +#endif + }; + static_assert(std::alignment_of::value >= std::alignment_of::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping"); + + +#ifdef MCDBGQ_TRACKMEM +public: + struct MemStats; +private: +#endif + + /////////////////////////// + // Producer base + /////////////////////////// + + struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase + { + ProducerBase(ConcurrentQueue* parent_, bool isExplicit_) : + tailIndex(0), + headIndex(0), + dequeueOptimisticCount(0), + dequeueOvercommit(0), + tailBlock(nullptr), + isExplicit(isExplicit_), + parent(parent_) + { + } + + virtual ~ProducerBase() { } + + template + inline bool dequeue(U& element) + { + if (isExplicit) { + return static_cast(this)->dequeue(element); + } + else { + return static_cast(this)->dequeue(element); + } + } + + template + inline size_t dequeue_bulk(It& itemFirst, size_t max) + { + if (isExplicit) { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + else { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + } + + inline ProducerBase* next_prod() const { return static_cast(next); } + + inline size_t size_approx() const + { + auto tail = tailIndex.load(std::memory_order_relaxed); + auto head = headIndex.load(std::memory_order_relaxed); + return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; + } + + inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } + protected: + std::atomic tailIndex; // Where to enqueue to next + std::atomic headIndex; // Where to dequeue from next + + std::atomic dequeueOptimisticCount; + std::atomic dequeueOvercommit; + + Block* tailBlock; + + public: + bool isExplicit; + ConcurrentQueue* parent; + + protected: +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + /////////////////////////// + // Explicit queue + /////////////////////////// + + struct ExplicitProducer : public ProducerBase + { + explicit ExplicitProducer(ConcurrentQueue* parent_) : + ProducerBase(parent_, true), + blockIndex(nullptr), + pr_blockIndexSlotsUsed(0), + pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), + pr_blockIndexFront(0), + pr_blockIndexEntries(nullptr), + pr_blockIndexRaw(nullptr) + { + size_t poolBasedIndexSize = details::ceil_to_pow_2(parent_->initialBlockPoolSize) >> 1; + if (poolBasedIndexSize > pr_blockIndexSize) { + pr_blockIndexSize = poolBasedIndexSize; + } + + new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE + } + + ~ExplicitProducer() + { + // Destruct any elements not yet dequeued. + // Since we're in the destructor, we can assume all elements + // are either completely dequeued or completely not (no halfways). + if (this->tailBlock != nullptr) { // Note this means there must be a block index too + // First find the block that's partially dequeued, if any + Block* halfDequeuedBlock = nullptr; + if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { + // The head's not on a block boundary, meaning a block somewhere is partially dequeued + // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) + size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); + while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { + i = (i + 1) & (pr_blockIndexSize - 1); + } + assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); + halfDequeuedBlock = pr_blockIndexEntries[i].block; + } + + // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) + auto block = this->tailBlock; + do { + block = block->next; + if (block->ConcurrentQueue::Block::template is_empty()) { + continue; + } + + size_t i = 0; // Offset into block + if (block == halfDequeuedBlock) { + i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + } + + // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index + auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { + (*block)[i++]->~T(); + } + } while (block != this->tailBlock); + } + + // Destroy all blocks that we own + if (this->tailBlock != nullptr) { + auto block = this->tailBlock; + do { + auto nextBlock = block->next; + if (block->dynamicallyAllocated) { + destroy(block); + } + else { + this->parent->add_block_to_free_list(block); + } + block = nextBlock; + } while (block != this->tailBlock); + } + + // Destroy the block indices + auto header = static_cast(pr_blockIndexRaw); + while (header != nullptr) { + auto prev = static_cast(header->prev); + header->~BlockIndexHeader(); + (Traits::free)(header); + header = prev; + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto startBlock = this->tailBlock; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + // We can re-use the block ahead of us, it's empty! + this->tailBlock = this->tailBlock->next; + this->tailBlock->ConcurrentQueue::Block::template reset_empty(); + + // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the + // last block from it first -- except instead of removing then adding, we can just overwrite). + // Note that there must be a valid block index here, since even if allocation failed in the ctor, + // it would have been re-attempted when adding the first block to the queue; since there is such + // a block, a block index must have been successfully allocated. + } + else { + // Whatever head value we see here is >= the last value we saw here (relatively), + // and <= its current value. Since we have the most recent tail, the head must be + // <= to it. + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) + || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + // We can't enqueue in another block because there's not enough leeway -- the + // tail could surpass the head by the time the block fills up! (Or we'll exceed + // the size limit, if the second part of the condition was true.) + return false; + } + // We're going to need a new block; check that the block index has room + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { + // Hmm, the circular block index is already full -- we'll need + // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if + // the initial allocation failed in the constructor. + + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } + else if (!new_block_index(pr_blockIndexSlotsUsed)) { + return false; + } + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + ++pr_blockIndexSlotsUsed; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + // The constructor may throw. We want the element not to appear in the queue in + // that case (without corrupting the queue): + MOODYCAMEL_TRY { + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + // Revert change to the current block, but leave the new block available + // for next time + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; + MOODYCAMEL_RETHROW; + } + } + else { + (void)startBlock; + (void)originalBlockIndexSlotsUsed; + } + + // Add block to block index + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + // Might be something to dequeue, let's give it a try + + // Note that this if is purely for performance purposes in the common case when the queue is + // empty and the values are eventually consistent -- we may enter here spuriously. + + // Note that whatever the values of overcommit and tail are, they are not going to change (unless we + // change them) and must be the same value at this point (inside the if) as when the if condition was + // evaluated. + + // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. + // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in + // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). + // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all + // read-modify-write operations are guaranteed to work on the latest value in the modification order), but + // unfortunately that can't be shown to be correct using only the C++11 standard. + // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case + std::atomic_thread_fence(std::memory_order_acquire); + + // Increment optimistic counter, then check if it went over the boundary + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + + // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever + // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now + // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon + // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. + // However, we can't assert this since both dequeueOptimisticCount and dequeueOvercommit may (independently) + // overflow; in such a case, though, the logic still holds since the difference between the two is maintained. + + // Note that we reload tail here in case it changed; it will be the same value as before or greater, since + // this load is sequenced after (happens after) the earlier load above. This is supported by read-read + // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + // Guaranteed to be at least one element to dequeue! + + // Get the index. Note that since there's guaranteed to be at least one element, this + // will never exceed tail. We need to do an acquire-release fence here since it's possible + // that whatever condition got us to this point was for an earlier enqueued element (that + // we already see the memory effects for), but that by the time we increment somebody else + // has incremented it, and we need to see the memory effects for *that* element, which is + // in such a case is necessarily visible on the thread that incremented it in the first + // place with the more current condition (they must have acquired a tail that is at least + // as recent). + auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + + // Determine which block the element is in + + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + // We need to be careful here about subtracting and dividing because of index wrap-around. + // When an index wraps, we need to preserve the sign of the offset when dividing it by the + // block size (in order to get a correct signed block count offset in all cases): + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(blockBaseIndex - headBase) / BLOCK_SIZE); + auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block; + + // Dequeue + auto& el = *((*block)[index]); + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { + // Make sure the element is still fully dequeued and destroyed even if the assignment + // throws + struct Guard { + Block* block; + index_t index; + + ~Guard() + { + (*block)[index]->~T(); + block->ConcurrentQueue::Block::template set_empty(index); + } + } guard = { block, index }; + + element = std::move(el); // NOLINT + } + else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + block->ConcurrentQueue::Block::template set_empty(index); + } + + return true; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write + } + } + + return false; + } + + template + bool MOODYCAMEL_NO_TSAN enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + auto originalBlockIndexFront = pr_blockIndexFront; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + + Block* firstAllocatedBlock = nullptr; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { + // Allocate as many blocks as possible from ahead + while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + this->tailBlock = this->tailBlock->next; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Now allocate as many blocks as necessary from the block pool + while (blockBaseDiff > 0) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + else if (full || !new_block_index(originalBlockIndexSlotsUsed)) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + + // pr_blockIndexFront is updated inside new_block_index, so we need to + // update our fallback value too (since we keep the new index even if we + // later fail) + originalBlockIndexFront = originalBlockIndexSlotsUsed; + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template set_all_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + ++pr_blockIndexSlotsUsed; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and + // publish the new block index front + auto block = firstAllocatedBlock; + while (true) { + block->ConcurrentQueue::Block::template reset_empty(); + if (block == this->tailBlock) { + break; + } + block = block->next; + } + + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + auto endBlock = this->tailBlock; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + // Must use copy constructor even if move constructor is available + // because we may have to revert if there's an exception. + // Sorry about the horrible templated next line, but it was the only way + // to disable moving *at compile time*, which is important because a type + // may only define a (noexcept) move constructor, and so calls to the + // cctor will not compile, even if they are in an if branch that will never + // be executed + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + // Oh dear, an exception's been thrown -- destroy the elements that + // were enqueued so far and revert the entire bulk operation (we'll keep + // any allocated blocks in our linked list for later, though). + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + if (firstAllocatedBlock != nullptr) + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Determine which block the first element is in + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / BLOCK_SIZE); + auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); + + // Iterate the blocks and dequeue + auto index = firstIndex; + do { + auto firstIndexInBlock = index; + index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + auto block = localBlockIndex->entries[indexIndex].block; + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + // It's too late to revert the dequeue, but we can make sure that all + // the dequeued objects are properly destroyed and the block index + // (and empty count) are properly updated before we propagate the exception + do { + block = localBlockIndex->entries[indexIndex].block; + while (index != endIndex) { + (*block)[index++]->~T(); + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + + firstIndexInBlock = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + struct BlockIndexEntry + { + index_t base; + Block* block; + }; + + struct BlockIndexHeader + { + size_t size; + std::atomic front; // Current slot (not next, like pr_blockIndexFront) + BlockIndexEntry* entries; + void* prev; + }; + + + bool new_block_index(size_t numberOfFilledSlotsToExpose) + { + auto prevBlockSizeMask = pr_blockIndexSize - 1; + + // Create the new block + pr_blockIndexSize <<= 1; + auto newRawPtr = static_cast((Traits::malloc)(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); + if (newRawPtr == nullptr) { + pr_blockIndexSize >>= 1; // Reset to allow graceful retry + return false; + } + + auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); + + // Copy in all the old indices, if any + size_t j = 0; + if (pr_blockIndexSlotsUsed != 0) { + auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; + do { + newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; + i = (i + 1) & prevBlockSizeMask; + } while (i != pr_blockIndexFront); + } + + // Update everything + auto header = new (newRawPtr) BlockIndexHeader; + header->size = pr_blockIndexSize; + header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); + header->entries = newBlockIndexEntries; + header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later + + pr_blockIndexFront = j; + pr_blockIndexEntries = newBlockIndexEntries; + pr_blockIndexRaw = newRawPtr; + blockIndex.store(header, std::memory_order_release); + + return true; + } + + private: + std::atomic blockIndex; + + // To be used by producer only -- consumer must use the ones in referenced by blockIndex + size_t pr_blockIndexSlotsUsed; + size_t pr_blockIndexSize; + size_t pr_blockIndexFront; // Next slot (not current) + BlockIndexEntry* pr_blockIndexEntries; + void* pr_blockIndexRaw; + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ExplicitProducer* nextExplicitProducer; + private: +#endif + +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Implicit queue + ////////////////////////////////// + + struct ImplicitProducer : public ProducerBase + { + ImplicitProducer(ConcurrentQueue* parent_) : + ProducerBase(parent_, false), + nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), + blockIndex(nullptr) + { + new_block_index(); + } + + ~ImplicitProducer() + { + // Note that since we're in the destructor we can assume that all enqueue/dequeue operations + // completed already; this means that all undequeued elements are placed contiguously across + // contiguous blocks, and that only the first and last remaining blocks can be only partially + // empty (all other remaining blocks must be completely full). + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + // Unregister ourselves for thread termination notification + if (!this->inactive.load(std::memory_order_relaxed)) { + details::ThreadExitNotifier::unsubscribe(&threadExitListener); + } +#endif + + // Destroy all remaining elements! + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto index = this->headIndex.load(std::memory_order_relaxed); + Block* block = nullptr; + assert(index == tail || details::circular_less_than(index, tail)); + bool forceFreeLastBlock = index != tail; // If we enter the loop, then the last (tail) block will not be freed + while (index != tail) { + if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { + if (block != nullptr) { + // Free the old block + this->parent->add_block_to_free_list(block); + } + + block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); + } + + ((*block)[index])->~T(); + ++index; + } + // Even if the queue is empty, there's still one block that's not on the free list + // (unless the head index reached the end of it, in which case the tail will be poised + // to create a new block). + if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0)) { + this->parent->add_block_to_free_list(this->tailBlock); + } + + // Destroy block index + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + if (localBlockIndex != nullptr) { + for (size_t i = 0; i != localBlockIndex->capacity; ++i) { + localBlockIndex->index[i]->~BlockIndexEntry(); + } + do { + auto prev = localBlockIndex->prev; + localBlockIndex->~BlockIndexHeader(); + (Traits::free)(localBlockIndex); + localBlockIndex = prev; + } while (localBlockIndex != nullptr); + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + return false; + } +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry; + if (!insert_block_index_entry(idxEntry, currentTailIndex)) { + return false; + } + + // Get ahold of a new block + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + // May throw, try to insert now before we publish the fact that we have this new block + MOODYCAMEL_TRY { + new ((*newBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(newBlock); + MOODYCAMEL_RETHROW; + } + } + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + this->tailBlock = newBlock; + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + // See ExplicitProducer::dequeue for rationale and explanation + index_t tail = this->tailIndex.load(std::memory_order_relaxed); + index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + std::atomic_thread_fence(std::memory_order_acquire); + + index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + // Determine which block the element is in + auto entry = get_block_index_entry_for_index(index); + + // Dequeue + auto block = entry->value.load(std::memory_order_relaxed); + auto& el = *((*block)[index]); + + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + // Note: Acquiring the mutex with every dequeue instead of only when a block + // is released is very sub-optimal, but it is, after all, purely debug code. + debug::DebugLock lock(producer->mutex); +#endif + struct Guard { + Block* block; + index_t index; + BlockIndexEntry* entry; + ConcurrentQueue* parent; + + ~Guard() + { + (*block)[index]->~T(); + if (block->ConcurrentQueue::Block::template set_empty(index)) { + entry->value.store(nullptr, std::memory_order_relaxed); + parent->add_block_to_free_list(block); + } + } + } guard = { block, index, entry, this->parent }; + + element = std::move(el); // NOLINT + } + else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + + if (block->ConcurrentQueue::Block::template set_empty(index)) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Add the block back into the global free pool (and remove from block index) + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + } + + return true; + } + else { + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); + } + } + + return false; + } + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4706) // assignment within conditional expression +#endif + template + bool enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + + // Note that the tailBlock we start off with may not be owned by us any more; + // this happens if it was filled up exactly to the top (setting tailIndex to + // the first index of the next block which is not yet allocated), then dequeued + // completely (putting it on the free list) before we enqueue again. + + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + Block* firstAllocatedBlock = nullptr; + auto endBlock = this->tailBlock; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + do { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry = nullptr; // initialization here unnecessary but compiler can't always tell + Block* newBlock; + bool indexInserted = false; + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + + if (full || !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == nullptr) { + // Index allocation or block allocation failed; revert any other allocations + // and index insertions done so far for this operation + if (indexInserted) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + } + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + newBlock->next = nullptr; + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + // Store the chain of blocks so that we can undo if later allocations fail, + // and so that we can find the blocks when we do the actual enqueueing + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) { + assert(this->tailBlock != nullptr); + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + endBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; + } while (blockBaseDiff > 0); + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + auto idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Iterate the blocks and dequeue + auto index = firstIndex; + BlockIndexHeader* localBlockIndex; + auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); + do { + auto blockStartIndex = index; + index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + + auto entry = localBlockIndex->index[indexIndex]; + auto block = entry->value.load(std::memory_order_relaxed); + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + do { + entry = localBlockIndex->index[indexIndex]; + block = entry->value.load(std::memory_order_relaxed); + while (index != endIndex) { + (*block)[index++]->~T(); + } + + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + entry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(block); + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + + blockStartIndex = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Note that the set_many_empty above did a release, meaning that anybody who acquires the block + // we're about to free can use it safely since our writes (and reads!) will have happened-before then. + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + // The block size must be > 1, so any number with the low bit set is an invalid block base index + static const index_t INVALID_BLOCK_BASE = 1; + + struct BlockIndexEntry + { + std::atomic key; + std::atomic value; + }; + + struct BlockIndexHeader + { + size_t capacity; + std::atomic tail; + BlockIndexEntry* entries; + BlockIndexEntry** index; + BlockIndexHeader* prev; + }; + + template + inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex) + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); // We're the only writer thread, relaxed is OK + if (localBlockIndex == nullptr) { + return false; // this can happen if new_block_index failed in the constructor + } + size_t newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || + idxEntry->value.load(std::memory_order_relaxed) == nullptr) { + + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + // No room in the old block index, try to allocate another one! + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } + else if (!new_block_index()) { + return false; + } + localBlockIndex = blockIndex.load(std::memory_order_relaxed); + newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + inline void rewind_block_index_tail() + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed); + } + + inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const + { + BlockIndexHeader* localBlockIndex; + auto idx = get_block_index_index_for_index(index, localBlockIndex); + return localBlockIndex->index[idx]; + } + + inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + index &= ~static_cast(BLOCK_SIZE - 1); + localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto tail = localBlockIndex->tail.load(std::memory_order_acquire); + auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); + assert(tailBase != INVALID_BLOCK_BASE); + // Note: Must use division instead of shift because the index may wrap around, causing a negative + // offset, whose negativity we want to preserve + auto offset = static_cast(static_cast::type>(index - tailBase) / BLOCK_SIZE); + size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); + assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); + return idx; + } + + bool new_block_index() + { + auto prev = blockIndex.load(std::memory_order_relaxed); + size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; + auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; + auto raw = static_cast((Traits::malloc)( + sizeof(BlockIndexHeader) + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity)); + if (raw == nullptr) { + return false; + } + + auto header = new (raw) BlockIndexHeader; + auto entries = reinterpret_cast(details::align_for(raw + sizeof(BlockIndexHeader))); + auto index = reinterpret_cast(details::align_for(reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); + if (prev != nullptr) { + auto prevTail = prev->tail.load(std::memory_order_relaxed); + auto prevPos = prevTail; + size_t i = 0; + do { + prevPos = (prevPos + 1) & (prev->capacity - 1); + index[i++] = prev->index[prevPos]; + } while (prevPos != prevTail); + assert(i == prevCapacity); + } + for (size_t i = 0; i != entryCount; ++i) { + new (entries + i) BlockIndexEntry; + entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); + index[prevCapacity + i] = entries + i; + } + header->prev = prev; + header->entries = entries; + header->index = index; + header->capacity = nextBlockIndexCapacity; + header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); + + blockIndex.store(header, std::memory_order_release); + + nextBlockIndexCapacity <<= 1; + + return true; + } + + private: + size_t nextBlockIndexCapacity; + std::atomic blockIndex; + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + public: + details::ThreadExitListener threadExitListener; + private: +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ImplicitProducer* nextImplicitProducer; + private: +#endif + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + mutable debug::DebugMutex mutex; +#endif +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Block pool manipulation + ////////////////////////////////// + + void populate_initial_block_list(size_t blockCount) + { + initialBlockPoolSize = blockCount; + if (initialBlockPoolSize == 0) { + initialBlockPool = nullptr; + return; + } + + initialBlockPool = create_array(blockCount); + if (initialBlockPool == nullptr) { + initialBlockPoolSize = 0; + } + for (size_t i = 0; i < initialBlockPoolSize; ++i) { + initialBlockPool[i].dynamicallyAllocated = false; + } + } + + inline Block* try_get_block_from_initial_pool() + { + if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { + return nullptr; + } + + auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); + + return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; + } + + inline void add_block_to_free_list(Block* block) + { +#ifdef MCDBGQ_TRACKMEM + block->owner = nullptr; +#endif + freeList.add(block); + } + + inline void add_blocks_to_free_list(Block* block) + { + while (block != nullptr) { + auto next = block->next; + add_block_to_free_list(block); + block = next; + } + } + + inline Block* try_get_block_from_free_list() + { + return freeList.try_get(); + } + + // Gets a free block from one of the memory pools, or allocates a new one (if applicable) + template + Block* requisition_block() + { + auto block = try_get_block_from_initial_pool(); + if (block != nullptr) { + return block; + } + + block = try_get_block_from_free_list(); + if (block != nullptr) { + return block; + } + + MOODYCAMEL_CONSTEXPR_IF (canAlloc == CanAlloc) { + return create(); + } + else { + return nullptr; + } + } + + +#ifdef MCDBGQ_TRACKMEM + public: + struct MemStats { + size_t allocatedBlocks; + size_t usedBlocks; + size_t freeBlocks; + size_t ownedBlocksExplicit; + size_t ownedBlocksImplicit; + size_t implicitProducers; + size_t explicitProducers; + size_t elementsEnqueued; + size_t blockClassBytes; + size_t queueClassBytes; + size_t implicitBlockIndexBytes; + size_t explicitBlockIndexBytes; + + friend class ConcurrentQueue; + + private: + static MemStats getFor(ConcurrentQueue* q) + { + MemStats stats = { 0 }; + + stats.elementsEnqueued = q->size_approx(); + + auto block = q->freeList.head_unsafe(); + while (block != nullptr) { + ++stats.allocatedBlocks; + ++stats.freeBlocks; + block = block->freeListNext.load(std::memory_order_relaxed); + } + + for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + bool implicit = dynamic_cast(ptr) != nullptr; + stats.implicitProducers += implicit ? 1 : 0; + stats.explicitProducers += implicit ? 0 : 1; + + if (implicit) { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ImplicitProducer); + auto head = prod->headIndex.load(std::memory_order_relaxed); + auto tail = prod->tailIndex.load(std::memory_order_relaxed); + auto hash = prod->blockIndex.load(std::memory_order_relaxed); + if (hash != nullptr) { + for (size_t i = 0; i != hash->capacity; ++i) { + if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { + ++stats.allocatedBlocks; + ++stats.ownedBlocksImplicit; + } + } + stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); + for (; hash != nullptr; hash = hash->prev) { + stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); + } + } + for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { + //auto block = prod->get_block_index_entry_for_index(head); + ++stats.usedBlocks; + } + } + else { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ExplicitProducer); + auto tailBlock = prod->tailBlock; + bool wasNonEmpty = false; + if (tailBlock != nullptr) { + auto block = tailBlock; + do { + ++stats.allocatedBlocks; + if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { + ++stats.usedBlocks; + wasNonEmpty = wasNonEmpty || block != tailBlock; + } + ++stats.ownedBlocksExplicit; + block = block->next; + } while (block != tailBlock); + } + auto index = prod->blockIndex.load(std::memory_order_relaxed); + while (index != nullptr) { + stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); + index = static_cast(index->prev); + } + } + } + + auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); + stats.allocatedBlocks += freeOnInitialPool; + stats.freeBlocks += freeOnInitialPool; + + stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; + stats.queueClassBytes += sizeof(ConcurrentQueue); + + return stats; + } + }; + + // For debugging only. Not thread-safe. + MemStats getMemStats() + { + return MemStats::getFor(this); + } + private: + friend struct MemStats; +#endif + + + ////////////////////////////////// + // Producer list manipulation + ////////////////////////////////// + + ProducerBase* recycle_or_create_producer(bool isExplicit) + { + bool recycled; + return recycle_or_create_producer(isExplicit, recycled); + } + + ProducerBase* recycle_or_create_producer(bool isExplicit, bool& recycled) + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + // Try to re-use one first + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { + bool expected = true; + if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { + // We caught one! It's been marked as activated, the caller can have it + recycled = true; + return ptr; + } + } + } + + recycled = false; + return add_producer(isExplicit ? static_cast(create(this)) : create(this)); + } + + ProducerBase* add_producer(ProducerBase* producer) + { + // Handle failed memory allocation + if (producer == nullptr) { + return nullptr; + } + + producerCount.fetch_add(1, std::memory_order_relaxed); + + // Add it to the lock-free list + auto prevTail = producerListTail.load(std::memory_order_relaxed); + do { + producer->next = prevTail; + } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + if (producer->isExplicit) { + auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextExplicitProducer = prevTailExplicit; + } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } + else { + auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextImplicitProducer = prevTailImplicit; + } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } +#endif + + return producer; + } + + void reown_producers() + { + // After another instance is moved-into/swapped-with this one, all the + // producers we stole still think their parents are the other queue. + // So fix them up! + for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { + ptr->parent = this; + } + } + + + ////////////////////////////////// + // Implicit producer hash + ////////////////////////////////// + + struct ImplicitProducerKVP + { + std::atomic key; + ImplicitProducer* value; // No need for atomicity since it's only read by the thread that sets it in the first place + + ImplicitProducerKVP() : value(nullptr) { } + + ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); + value = other.value; + } + + inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT + { + if (this != &other) { + details::swap_relaxed(key, other.key); + std::swap(value, other.value); + } + } + }; + + template + friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; + + struct ImplicitProducerHash + { + size_t capacity; + ImplicitProducerKVP* entries; + ImplicitProducerHash* prev; + }; + + inline void populate_initial_implicit_producer_hash() + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } + else { + implicitProducerHashCount.store(0, std::memory_order_relaxed); + auto hash = &initialImplicitProducerHash; + hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + hash->entries = &initialImplicitProducerHashEntries[0]; + for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { + initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + hash->prev = nullptr; + implicitProducerHash.store(hash, std::memory_order_relaxed); + } + } + + void swap_implicit_producer_hashes(ConcurrentQueue& other) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } + else { + // Swap (assumes our implicit producer hash is initialized) + initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); + initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; + other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; + + details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); + + details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); + if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { + implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &initialImplicitProducerHash; + } + if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { + other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &other.initialImplicitProducerHash; + } + } + } + + // Only fails (returns nullptr) if memory allocation fails + ImplicitProducer* get_or_add_implicit_producer() + { + // Note that since the data is essentially thread-local (key is thread ID), + // there's a reduced need for fences (memory ordering is already consistent + // for any individual thread), except for the current table itself. + + // Start by looking for the thread ID in the current and all previous hash tables. + // If it's not found, it must not be in there yet, since this same thread would + // have added it previously to one of the tables that we traversed. + + // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + + auto mainHash = implicitProducerHash.load(std::memory_order_acquire); + assert(mainHash != nullptr); // silence clang-tidy and MSVC warnings (hash cannot be null) + for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { + // Look for the id in this hash + auto index = hashedId; + while (true) { // Not an infinite loop because at least one slot is free in the hash table + index &= hash->capacity - 1; + + auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + // Found it! If we had to search several hashes deep, though, we should lazily add it + // to the current main hash table to avoid the extended search next time. + // Note there's guaranteed to be room in the current hash table since every subsequent + // table implicitly reserves space for all previous tables (there's only one + // implicitProducerHashCount). + auto value = hash->entries[index].value; + if (hash != mainHash) { + index = hashedId; + while (true) { + index &= mainHash->capacity - 1; + probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, std::memory_order_relaxed)) || + (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire, std::memory_order_acquire))) { +#else + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, std::memory_order_relaxed))) { +#endif + mainHash->entries[index].value = value; + break; + } + ++index; + } + } + + return value; + } + if (probedKey == details::invalid_thread_id) { + break; // Not in this hash table + } + ++index; + } + } + + // Insert! + auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); + while (true) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { + // We've acquired the resize lock, try to allocate a bigger hash table. + // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when + // we reload implicitProducerHash it must be the most recent version (it only gets changed within this + // locked block). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + if (newCount >= (mainHash->capacity >> 1)) { + auto newCapacity = mainHash->capacity << 1; + while (newCount >= (newCapacity >> 1)) { + newCapacity <<= 1; + } + auto raw = static_cast((Traits::malloc)(sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity)); + if (raw == nullptr) { + // Allocation failed + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + return nullptr; + } + + auto newHash = new (raw) ImplicitProducerHash; + newHash->capacity = static_cast(newCapacity); + newHash->entries = reinterpret_cast(details::align_for(raw + sizeof(ImplicitProducerHash))); + for (size_t i = 0; i != newCapacity; ++i) { + new (newHash->entries + i) ImplicitProducerKVP; + newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + newHash->prev = mainHash; + implicitProducerHash.store(newHash, std::memory_order_release); + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + mainHash = newHash; + } + else { + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + } + } + + // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table + // to finish being allocated by another thread (and if we just finished allocating above, the condition will + // always be true) + if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { + bool recycled; + auto producer = static_cast(recycle_or_create_producer(false, recycled)); + if (producer == nullptr) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + return nullptr; + } + if (recycled) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; + producer->threadExitListener.userData = producer; + details::ThreadExitNotifier::subscribe(&producer->threadExitListener); +#endif + + auto index = hashedId; + while (true) { + index &= mainHash->capacity - 1; + auto probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); + + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, std::memory_order_relaxed)) || + (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire, std::memory_order_acquire))) { +#else + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, std::memory_order_relaxed))) { +#endif + mainHash->entries[index].value = producer; + break; + } + ++index; + } + return producer; + } + + // Hmm, the old hash is quite full and somebody else is busy allocating a new one. + // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, + // we try to allocate ourselves). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + } + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + void implicit_producer_thread_exited(ImplicitProducer* producer) + { + // Remove from thread exit listeners + details::ThreadExitNotifier::unsubscribe(&producer->threadExitListener); + + // Remove from hash +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + auto hash = implicitProducerHash.load(std::memory_order_acquire); + assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + details::thread_id_t probedKey; + + // We need to traverse all the hashes just in case other threads aren't on the current one yet and are + // trying to add an entry thinking there's a free slot (because they reused a producer) + for (; hash != nullptr; hash = hash->prev) { + auto index = hashedId; + do { + index &= hash->capacity - 1; + probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + hash->entries[index].key.store(details::invalid_thread_id2, std::memory_order_release); + break; + } + ++index; + } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place + } + + // Mark the queue as being recyclable + producer->inactive.store(true, std::memory_order_release); + } + + static void implicit_producer_thread_exited_callback(void* userData) + { + auto producer = static_cast(userData); + auto queue = producer->parent; + queue->implicit_producer_thread_exited(producer); + } +#endif + + ////////////////////////////////// + // Utility functions + ////////////////////////////////// + + template + static inline void* aligned_malloc(size_t size) + { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) + return (Traits::malloc)(size); + else { + size_t alignment = std::alignment_of::value; + void* raw = (Traits::malloc)(size + alignment - 1 + sizeof(void*)); + if (!raw) + return nullptr; + char* ptr = details::align_for(reinterpret_cast(raw) + sizeof(void*)); + *(reinterpret_cast(ptr) - 1) = raw; + return ptr; + } + } + + template + static inline void aligned_free(void* ptr) + { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) + return (Traits::free)(ptr); + else + (Traits::free)(ptr ? *(reinterpret_cast(ptr) - 1) : nullptr); + } + + template + static inline U* create_array(size_t count) + { + assert(count > 0); + U* p = static_cast(aligned_malloc(sizeof(U) * count)); + if (p == nullptr) + return nullptr; + + for (size_t i = 0; i != count; ++i) + new (p + i) U(); + return p; + } + + template + static inline void destroy_array(U* p, size_t count) + { + if (p != nullptr) { + assert(count > 0); + for (size_t i = count; i != 0; ) + (p + --i)->~U(); + } + aligned_free(p); + } + + template + static inline U* create() + { + void* p = aligned_malloc(sizeof(U)); + return p != nullptr ? new (p) U : nullptr; + } + + template + static inline U* create(A1&& a1) + { + void* p = aligned_malloc(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + aligned_free(p); + } + +private: + std::atomic producerListTail; + std::atomic producerCount; + + std::atomic initialBlockPoolIndex; + Block* initialBlockPool; + size_t initialBlockPoolSize; + +#ifndef MCDBGQ_USEDEBUGFREELIST + FreeList freeList; +#else + debug::DebugFreeList freeList; +#endif + + std::atomic implicitProducerHash; + std::atomic implicitProducerHashCount; // Number of slots logically used + ImplicitProducerHash initialImplicitProducerHash; + std::array initialImplicitProducerHashEntries; + std::atomic_flag implicitProducerHashResizeInProgress; + + std::atomic nextExplicitConsumerId; + std::atomic globalExplicitConsumerOffset; + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugMutex implicitProdMutex; +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + std::atomic explicitProducers; + std::atomic implicitProducers; +#endif +}; + + +template +ProducerToken::ProducerToken(ConcurrentQueue& queue) + : producer(queue.recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ProducerToken::ProducerToken(BlockingConcurrentQueue& queue) + : producer(reinterpret_cast*>(&queue)->recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ConsumerToken::ConsumerToken(ConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = reinterpret_cast*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +#pragma warning(pop) +#endif + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +#pragma GCC diagnostic pop +#endif diff --git a/lightweightsemaphore.h b/lightweightsemaphore.h new file mode 100644 index 0000000..b0f24e1 --- /dev/null +++ b/lightweightsemaphore.h @@ -0,0 +1,411 @@ +// Provides an efficient implementation of a semaphore (LightweightSemaphore). +// This is an extension of Jeff Preshing's sempahore implementation (licensed +// under the terms of its separate zlib license) that has been adapted and +// extended by Cameron Desrochers. + +#pragma once + +#include // For std::size_t +#include +#include // For std::make_signed + +#if defined(_WIN32) +// Avoid including windows.h in a header; we only need a handful of +// items, so we'll redeclare them here (this is relatively safe since +// the API generally has to remain stable between Windows versions). +// I know this is an ugly hack but it still beats polluting the global +// namespace with thousands of generic names or adding a .cpp for nothing. +extern "C" { + struct _SECURITY_ATTRIBUTES; + __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName); + __declspec(dllimport) int __stdcall CloseHandle(void* hObject); + __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds); + __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount); +} +#elif defined(__MACH__) +#include +#elif defined(__unix__) +#include +#endif + +namespace moodycamel +{ +namespace details +{ + +// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's +// portable + lightweight semaphore implementations, originally from +// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h +// LICENSE: +// Copyright (c) 2015 Jeff Preshing +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgement in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +#if defined(_WIN32) +class Semaphore +{ +private: + void* m_hSema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + const long maxLong = 0x7fffffff; + m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); + assert(m_hSema); + } + + ~Semaphore() + { + CloseHandle(m_hSema); + } + + bool wait() + { + const unsigned long infinite = 0xffffffff; + return WaitForSingleObject(m_hSema, infinite) == 0; + } + + bool try_wait() + { + return WaitForSingleObject(m_hSema, 0) == 0; + } + + bool timed_wait(std::uint64_t usecs) + { + return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; + } + + void signal(int count = 1) + { + while (!ReleaseSemaphore(m_hSema, count, nullptr)); + } +}; +#elif defined(__MACH__) +//--------------------------------------------------------- +// Semaphore (Apple iOS and OSX) +// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html +//--------------------------------------------------------- +class Semaphore +{ +private: + semaphore_t m_sema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); + assert(rc == KERN_SUCCESS); + (void)rc; + } + + ~Semaphore() + { + semaphore_destroy(mach_task_self(), m_sema); + } + + bool wait() + { + return semaphore_wait(m_sema) == KERN_SUCCESS; + } + + bool try_wait() + { + return timed_wait(0); + } + + bool timed_wait(std::uint64_t timeout_usecs) + { + mach_timespec_t ts; + ts.tv_sec = static_cast(timeout_usecs / 1000000); + ts.tv_nsec = static_cast((timeout_usecs % 1000000) * 1000); + + // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html + kern_return_t rc = semaphore_timedwait(m_sema, ts); + return rc == KERN_SUCCESS; + } + + void signal() + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + + void signal(int count) + { + while (count-- > 0) + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + } +}; +#elif defined(__unix__) +//--------------------------------------------------------- +// Semaphore (POSIX, Linux) +//--------------------------------------------------------- +class Semaphore +{ +private: + sem_t m_sema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + int rc = sem_init(&m_sema, 0, static_cast(initialCount)); + assert(rc == 0); + (void)rc; + } + + ~Semaphore() + { + sem_destroy(&m_sema); + } + + bool wait() + { + // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error + int rc; + do { + rc = sem_wait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool try_wait() + { + int rc; + do { + rc = sem_trywait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool timed_wait(std::uint64_t usecs) + { + struct timespec ts; + const int usecs_in_1_sec = 1000000; + const int nsecs_in_1_sec = 1000000000; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += (time_t)(usecs / usecs_in_1_sec); + ts.tv_nsec += (long)(usecs % usecs_in_1_sec) * 1000; + // sem_timedwait bombs if you have more than 1e9 in tv_nsec + // so we have to clean things up before passing it in + if (ts.tv_nsec >= nsecs_in_1_sec) { + ts.tv_nsec -= nsecs_in_1_sec; + ++ts.tv_sec; + } + + int rc; + do { + rc = sem_timedwait(&m_sema, &ts); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + void signal() + { + while (sem_post(&m_sema) == -1); + } + + void signal(int count) + { + while (count-- > 0) + { + while (sem_post(&m_sema) == -1); + } + } +}; +#else +#error Unsupported platform! (No semaphore wrapper available) +#endif + +} // end namespace details + + +//--------------------------------------------------------- +// LightweightSemaphore +//--------------------------------------------------------- +class LightweightSemaphore +{ +public: + typedef std::make_signed::type ssize_t; + +private: + std::atomic m_count; + details::Semaphore m_sema; + int m_maxSpins; + + bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) + { + ssize_t oldCount; + int spin = m_maxSpins; + while (--spin >= 0) + { + oldCount = m_count.load(std::memory_order_relaxed); + if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) + return true; + std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop. + } + oldCount = m_count.fetch_sub(1, std::memory_order_acquire); + if (oldCount > 0) + return true; + if (timeout_usecs < 0) + { + if (m_sema.wait()) + return true; + } + if (timeout_usecs > 0 && m_sema.timed_wait((std::uint64_t)timeout_usecs)) + return true; + // At this point, we've timed out waiting for the semaphore, but the + // count is still decremented indicating we may still be waiting on + // it. So we have to re-adjust the count, but only if the semaphore + // wasn't signaled enough times for us too since then. If it was, we + // need to release the semaphore too. + while (true) + { + oldCount = m_count.load(std::memory_order_acquire); + if (oldCount >= 0 && m_sema.try_wait()) + return true; + if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) + return false; + } + } + + ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1) + { + assert(max > 0); + ssize_t oldCount; + int spin = m_maxSpins; + while (--spin >= 0) + { + oldCount = m_count.load(std::memory_order_relaxed); + if (oldCount > 0) + { + ssize_t newCount = oldCount > max ? oldCount - max : 0; + if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) + return oldCount - newCount; + } + std::atomic_signal_fence(std::memory_order_acquire); + } + oldCount = m_count.fetch_sub(1, std::memory_order_acquire); + if (oldCount <= 0) + { + if ((timeout_usecs == 0) || (timeout_usecs < 0 && !m_sema.wait()) || (timeout_usecs > 0 && !m_sema.timed_wait((std::uint64_t)timeout_usecs))) + { + while (true) + { + oldCount = m_count.load(std::memory_order_acquire); + if (oldCount >= 0 && m_sema.try_wait()) + break; + if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) + return 0; + } + } + } + if (max > 1) + return 1 + tryWaitMany(max - 1); + return 1; + } + +public: + LightweightSemaphore(ssize_t initialCount = 0, int maxSpins = 10000) : m_count(initialCount), m_maxSpins(maxSpins) + { + assert(initialCount >= 0); + assert(maxSpins >= 0); + } + + bool tryWait() + { + ssize_t oldCount = m_count.load(std::memory_order_relaxed); + while (oldCount > 0) + { + if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) + return true; + } + return false; + } + + bool wait() + { + return tryWait() || waitWithPartialSpinning(); + } + + bool wait(std::int64_t timeout_usecs) + { + return tryWait() || waitWithPartialSpinning(timeout_usecs); + } + + // Acquires between 0 and (greedily) max, inclusive + ssize_t tryWaitMany(ssize_t max) + { + assert(max >= 0); + ssize_t oldCount = m_count.load(std::memory_order_relaxed); + while (oldCount > 0) + { + ssize_t newCount = oldCount > max ? oldCount - max : 0; + if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) + return oldCount - newCount; + } + return 0; + } + + // Acquires at least one, and (greedily) at most max + ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs) + { + assert(max >= 0); + ssize_t result = tryWaitMany(max); + if (result == 0 && max > 0) + result = waitManyWithPartialSpinning(max, timeout_usecs); + return result; + } + + ssize_t waitMany(ssize_t max) + { + ssize_t result = waitMany(max, -1); + assert(result > 0); + return result; + } + + void signal(ssize_t count = 1) + { + assert(count >= 0); + ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release); + ssize_t toRelease = -oldCount < count ? -oldCount : count; + if (toRelease > 0) + { + m_sema.signal((int)toRelease); + } + } + + std::size_t availableApprox() const + { + ssize_t count = m_count.load(std::memory_order_relaxed); + return count > 0 ? static_cast(count) : 0; + } +}; + +} // end namespace moodycamel diff --git a/mutilate.cc b/mutilate.cc index 23afcc3..4dcda4c 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -38,10 +38,12 @@ #include "log.h" #include "mutilate.h" #include "util.h" +#include "blockingconcurrentqueue.h" #define MIN(a,b) ((a) < (b) ? (a) : (b)) using namespace std; +using namespace moodycamel; ifstream kvfile; pthread_mutex_t flock = PTHREAD_MUTEX_INITIALIZER; @@ -62,6 +64,12 @@ struct thread_data { zmq::socket_t *socket; #endif int id; + BlockingConcurrentQueue *trace_queue; +}; + +struct reader_data { + BlockingConcurrentQueue *trace_queue; + string trace_filename; }; // struct evdns_base *evdns; @@ -82,13 +90,14 @@ void go(const vector &servers, options_t &options, ); void do_mutilate(const vector &servers, options_t &options, - ConnectionStats &stats, bool master = true + ConnectionStats &stats,BlockingConcurrentQueue *trace_queue, bool master = true #ifdef HAVE_LIBZMQ , zmq::socket_t* socket = NULL #endif ); void args_to_options(options_t* options); void* thread_main(void *arg); +void* reader_thread(void *arg); #ifdef HAVE_LIBZMQ static std::string s_recv (zmq::socket_t &socket) { @@ -667,8 +676,14 @@ void go(const vector& servers, options_t& options, } #endif + BlockingConcurrentQueue *trace_queue = new BlockingConcurrentQueue; + struct reader_data *rdata = (struct reader_data*)malloc(sizeof(struct reader_data)); + rdata->trace_queue = trace_queue; if (options.read_file) { - kvfile.open(options.file_name); + pthread_t tid; + rdata->trace_filename = options.file_name; + pthread_create(&tid, NULL,reader_thread,rdata); + usleep(10); } @@ -684,9 +699,11 @@ void go(const vector& servers, options_t& options, int current_cpu = -1; #endif + for (int t = 0; t < options.threads; t++) { td[t].options = &options; td[t].id = t; + td[t].trace_queue = trace_queue; #ifdef HAVE_LIBZMQ td[t].socket = socket; #endif @@ -742,7 +759,7 @@ void go(const vector& servers, options_t& options, delete cs; } } else if (options.threads == 1) { - do_mutilate(servers, options, stats, true + do_mutilate(servers, options, stats, trace_queue, true #ifdef HAVE_LIBZMQ , socket #endif @@ -781,9 +798,26 @@ int stick_this_thread_to_core(int core_id) { return pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset); } +void* reader_thread(void *arg) { + struct reader_data *rdata = (struct reader_data *) arg; + BlockingConcurrentQueue *trace_queue = (BlockingConcurrentQueue*) rdata->trace_queue; + + ifstream trace_file; + trace_file.open(rdata->trace_filename); + while (trace_file.good()) { + string line; + getline(trace_file,line); + trace_queue->enqueue(line); + } + string eof = "EOF"; + for (int i = 0; i < 1000; i++) { + trace_queue->enqueue(eof); + } + return NULL; +} + void* thread_main(void *arg) { struct thread_data *td = (struct thread_data *) arg; - int num_cores = sysconf(_SC_NPROCESSORS_ONLN); int res = stick_this_thread_to_core(td->id % num_cores); if (res != 0) { @@ -792,7 +826,7 @@ void* thread_main(void *arg) { } ConnectionStats *cs = new ConnectionStats(); - do_mutilate(*td->servers, *td->options, *cs, td->master + do_mutilate(*td->servers, *td->options, *cs, td->trace_queue, td->master #ifdef HAVE_LIBZMQ , td->socket #endif @@ -802,7 +836,7 @@ void* thread_main(void *arg) { } void do_mutilate(const vector& servers, options_t& options, - ConnectionStats& stats, bool master + ConnectionStats& stats, BlockingConcurrentQueue *trace_queue, bool master #ifdef HAVE_LIBZMQ , zmq::socket_t* socket #endif @@ -839,6 +873,7 @@ void do_mutilate(const vector& servers, options_t& options, vector connections; vector server_lead; + for (auto s: servers) { // Split args.server_arg[s] into host:port using strtok(). char *s_copy = new char[s.length() + 1]; @@ -860,6 +895,7 @@ void do_mutilate(const vector& servers, options_t& options, for (int c = 0; c < conns; c++) { Connection* conn = new Connection(base, evdns, hostname, port, options, + trace_queue, args.agentmode_given ? false : true); connections.push_back(conn); From 6b0303e41c71a14b01f3cb73e7e6065bb4566e01 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 5 May 2021 16:48:53 -0400 Subject: [PATCH 21/57] updates --- Connection.cc | 183 +++++++++++++++++++++++++------------------------- Connection.h | 4 +- mutilate.cc | 12 ++-- 3 files changed, 100 insertions(+), 99 deletions(-) diff --git a/Connection.cc b/Connection.cc index 0df68b6..bb81b61 100644 --- a/Connection.cc +++ b/Connection.cc @@ -35,7 +35,7 @@ extern pthread_mutex_t flock; */ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, string _hostname, string _port, options_t _options, - BlockingConcurrentQueue* a_trace_queue, + ConcurrentQueue* a_trace_queue, bool sampling ) : start_time(0), stats(sampling), options(_options), hostname(_hostname), port(_port), base(_base), evdns(_evdns) @@ -407,101 +407,102 @@ int Connection::issue_getsetorset(double now) { string rvaluelen; - trace_queue->wait_dequeue(line); - if (line.compare("EOF") == 0) { - eof = 1; - return 1; - } - - /* - pthread_mutex_lock(&flock); - if (kvfile.good()) { - getline(kvfile,line); - pthread_mutex_unlock(&flock); - } - else { - pthread_mutex_unlock(&flock); - return 1; - } - */ - stringstream ss(line); - int Op = 0; - int vl = 0; - - if (options.twitter_trace == 1) { - getline( ss, rT, ',' ); - getline( ss, rKey, ',' ); - getline( ss, rKeySize, ',' ); - getline( ss, rvaluelen, ',' ); - getline( ss, rApp, ',' ); - getline( ss, rOp, ',' ); - vl = atoi(rvaluelen.c_str()); - if (vl < 1) vl = 1; - if (vl > 524000) vl = 524000; - if (rOp.compare("get") == 0) { - Op = 1; - } else if (rOp.compare("set") == 0) { - Op = 2; - } else { - Op = 0; + bool res = trace_queue->try_dequeue(line); + if (res) { + if (line.compare("EOF") == 0) { + eof = 1; + return 1; } - - } else if (options.twitter_trace == 2) { - getline( ss, rT, ',' ); - getline( ss, rKey, ',' ); - getline( ss, rvaluelen, ',' ); - getline( ss, rOp, ',' ); - int op_n = atoi(rOp.c_str()); - - if (op_n == 1) - Op = 1; - else if (op_n == 0) { - Op = 2; + /* + pthread_mutex_lock(&flock); + if (kvfile.good()) { + getline(kvfile,line); + pthread_mutex_unlock(&flock); + } + else { + pthread_mutex_unlock(&flock); + return 1; + } + */ + stringstream ss(line); + int Op = 0; + int vl = 0; + + if (options.twitter_trace == 1) { + getline( ss, rT, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rKeySize, ',' ); + getline( ss, rvaluelen, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + vl = atoi(rvaluelen.c_str()); + if (vl < 1) vl = 1; + if (vl > 524000) vl = 524000; + if (rOp.compare("get") == 0) { + Op = 1; + } else if (rOp.compare("set") == 0) { + Op = 2; + } else { + Op = 0; + } + + } else if (options.twitter_trace == 2) { + getline( ss, rT, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + getline( ss, rOp, ',' ); + int op_n = atoi(rOp.c_str()); + + if (op_n == 1) + Op = 1; + else if (op_n == 0) { + Op = 2; + } + vl = atoi(rvaluelen.c_str()) - 76; + vl = vl - strlen(rKey.c_str()); + } + else { + getline( ss, rT, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + vl = atoi(rvaluelen.c_str()); + if (rOp.compare("read") == 0) + Op = 1; + if (rOp.compare("write") == 0) + Op = 2; } - vl = atoi(rvaluelen.c_str()) - 76; - vl = vl - strlen(rKey.c_str()); - } - else { - getline( ss, rT, ',' ); - getline( ss, rApp, ',' ); - getline( ss, rOp, ',' ); - getline( ss, rKey, ',' ); - getline( ss, rvaluelen, ',' ); - vl = atoi(rvaluelen.c_str()); - if (rOp.compare("read") == 0) - Op = 1; - if (rOp.compare("write") == 0) - Op = 2; - } - char key[1024]; - char skey[1024]; - memset(key,0,1024); - memset(skey,0,1024); - strncpy(skey, rKey.c_str(),strlen(rKey.c_str())); + char key[1024]; + char skey[1024]; + memset(key,0,1024); + memset(skey,0,1024); + strncpy(skey, rKey.c_str(),strlen(rKey.c_str())); - if (args.prefix_given) { - strncpy(key, options.prefix,strlen(options.prefix)); - } - strncat(key,skey,strlen(skey)); - //if (strcmp(key,"100004781") == 0) { - // fprintf(stderr,"ready!\n"); - //} - switch(Op) - { - case 0: - fprintf(stderr,"invalid line: %s, vl: %d @T: %d\n", - key,vl,atoi(rT.c_str())); - break; - case 1: - issue_get_with_len(key, vl, now); - break; - case 2: - int index = lrand48() % (1024 * 1024); - issue_set(key, &random_char[index], vl, now,true); - break; - + if (args.prefix_given) { + strncpy(key, options.prefix,strlen(options.prefix)); + } + strncat(key,skey,strlen(skey)); + //if (strcmp(key,"100004781") == 0) { + // fprintf(stderr,"ready!\n"); + //} + switch(Op) + { + case 0: + fprintf(stderr,"invalid line: %s, vl: %d @T: %d\n", + key,vl,atoi(rT.c_str())); + break; + case 1: + issue_get_with_len(key, vl, now); + break; + case 2: + int index = lrand48() % (1024 * 1024); + issue_set(key, &random_char[index], vl, now,true); + break; + + } } } diff --git a/Connection.h b/Connection.h index 3737e3a..20ed4e9 100644 --- a/Connection.h +++ b/Connection.h @@ -36,7 +36,7 @@ class Connection { public: Connection(struct event_base* _base, struct evdns_base* _evdns, string _hostname, string _port, options_t options, - BlockingConcurrentQueue *a_trace_queue, + ConcurrentQueue *a_trace_queue, bool sampling = true); ~Connection(); @@ -121,7 +121,7 @@ class Connection { Generator *iagen; std::queue op_queue; - BlockingConcurrentQueue *trace_queue; + ConcurrentQueue *trace_queue; // state machine functions / event processing void pop_op(); diff --git a/mutilate.cc b/mutilate.cc index 4dcda4c..bca6a80 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -64,11 +64,11 @@ struct thread_data { zmq::socket_t *socket; #endif int id; - BlockingConcurrentQueue *trace_queue; + ConcurrentQueue *trace_queue; }; struct reader_data { - BlockingConcurrentQueue *trace_queue; + ConcurrentQueue *trace_queue; string trace_filename; }; @@ -90,7 +90,7 @@ void go(const vector &servers, options_t &options, ); void do_mutilate(const vector &servers, options_t &options, - ConnectionStats &stats,BlockingConcurrentQueue *trace_queue, bool master = true + ConnectionStats &stats,ConcurrentQueue *trace_queue, bool master = true #ifdef HAVE_LIBZMQ , zmq::socket_t* socket = NULL #endif @@ -676,7 +676,7 @@ void go(const vector& servers, options_t& options, } #endif - BlockingConcurrentQueue *trace_queue = new BlockingConcurrentQueue; + ConcurrentQueue *trace_queue = new ConcurrentQueue; struct reader_data *rdata = (struct reader_data*)malloc(sizeof(struct reader_data)); rdata->trace_queue = trace_queue; if (options.read_file) { @@ -800,7 +800,7 @@ int stick_this_thread_to_core(int core_id) { void* reader_thread(void *arg) { struct reader_data *rdata = (struct reader_data *) arg; - BlockingConcurrentQueue *trace_queue = (BlockingConcurrentQueue*) rdata->trace_queue; + ConcurrentQueue *trace_queue = (ConcurrentQueue*) rdata->trace_queue; ifstream trace_file; trace_file.open(rdata->trace_filename); @@ -836,7 +836,7 @@ void* thread_main(void *arg) { } void do_mutilate(const vector& servers, options_t& options, - ConnectionStats& stats, BlockingConcurrentQueue *trace_queue, bool master + ConnectionStats& stats, ConcurrentQueue *trace_queue, bool master #ifdef HAVE_LIBZMQ , zmq::socket_t* socket #endif From 84fb1519938974cfbde0f44d9d7e4ebfe28bfc0e Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Fri, 7 May 2021 09:11:55 -0400 Subject: [PATCH 22/57] set errors --- Connection.cc | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Connection.cc b/Connection.cc index bb81b61..d3bbc58 100644 --- a/Connection.cc +++ b/Connection.cc @@ -1021,13 +1021,20 @@ void Connection::read_callback() { case WAITING_FOR_SET: assert(op_queue.size() > 0); - if (!prot->handle_response(input, done, found, obj_size)) return; + full_read = prot->handle_response(input, done, found, obj_size); + if (!full_read) { + + char key[256]; + char log[1024]; + string keystr = op->key; + strcpy(key, keystr.c_str()); + int valuelen = op->valuelen; + sprintf(log,"ERROR SETTING: %s,%d\n",key,valuelen); + write(2,log,strlen(log)); + return; + } - //char log[256]; - //sprintf(log,"%f,%d,%d,%d,%d,%d,%d\n", - // r_time,r_appid,r_type,r_ksize,r_vsize,r_key,r_hit); - //write(2,log,strlen(log)); finish_op(op,1); break; From 69c88e702c086eccb1ef400a26600764b20a0984 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Fri, 7 May 2021 10:25:56 -0400 Subject: [PATCH 23/57] debug conn --- Connection.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Connection.cc b/Connection.cc index bb81b61..db998e4 100644 --- a/Connection.cc +++ b/Connection.cc @@ -19,6 +19,7 @@ #include "binary_protocol.h" #include "util.h" #include +#include #include #include #include @@ -446,6 +447,10 @@ int Connection::issue_getsetorset(double now) { Op = 0; } + //char buf[1024]; + //sprintf(buf,"%s,%d\n",rKey.c_str(),vl); + //write(1,buf,strlen(buf)); + } else if (options.twitter_trace == 2) { getline( ss, rT, ',' ); getline( ss, rKey, ',' ); From 6ad42c08899fc75196e0604332e15f3167bfda7c Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Mon, 10 May 2021 20:02:08 -0400 Subject: [PATCH 24/57] compression support --- Connection.cc | 14 +- SConstruct | 1 + common.h | 236 +++++ libzstd.a | Bin 0 -> 1149514 bytes mutilate.cc | 154 +++- zstd.h | 2450 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 2838 insertions(+), 17 deletions(-) create mode 100644 common.h create mode 100644 libzstd.a create mode 100644 zstd.h diff --git a/Connection.cc b/Connection.cc index d3bbc58..54df97c 100644 --- a/Connection.cc +++ b/Connection.cc @@ -1024,13 +1024,13 @@ void Connection::read_callback() { full_read = prot->handle_response(input, done, found, obj_size); if (!full_read) { - char key[256]; - char log[1024]; - string keystr = op->key; - strcpy(key, keystr.c_str()); - int valuelen = op->valuelen; - sprintf(log,"ERROR SETTING: %s,%d\n",key,valuelen); - write(2,log,strlen(log)); + //char key[256]; + //char log[1024]; + //string keystr = op->key; + //strcpy(key, keystr.c_str()); + //int valuelen = op->valuelen; + //sprintf(log,"ERROR SETTING: %s,%d\n",key,valuelen); + //write(2,log,strlen(log)); return; } diff --git a/SConstruct b/SConstruct index 57d0054..98024c8 100644 --- a/SConstruct +++ b/SConstruct @@ -48,6 +48,7 @@ src = Split("""mutilate.cc cmdline.cc log.cc distributions.cc util.cc if not env['HAVE_POSIX_BARRIER']: # USE_POSIX_BARRIER: src += ['barrier.cc'] +src += ['libzstd.a'] env.Program(target='mutilate', source=src) env.Program(target='gtest', source=['TestGenerator.cc', 'log.cc', 'util.cc', 'Generator.cc']) diff --git a/common.h b/common.h new file mode 100644 index 0000000..05e10df --- /dev/null +++ b/common.h @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2016-2021, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +/* + * This header file has common utility functions used in examples. + */ +#ifndef COMMON_H +#define COMMON_H + +#include // malloc, free, exit +#include // fprintf, perror, fopen, etc. +#include // strerror +#include // errno +#include // stat +#include + +/* + * Define the returned error code from utility functions. + */ +typedef enum { + ERROR_fsize = 1, + ERROR_fopen = 2, + ERROR_fclose = 3, + ERROR_fread = 4, + ERROR_fwrite = 5, + ERROR_loadFile = 6, + ERROR_saveFile = 7, + ERROR_malloc = 8, + ERROR_largeFile = 9, +} COMMON_ErrorCode; + +/*! CHECK + * Check that the condition holds. If it doesn't print a message and die. + */ +#define CHECK(cond, ...) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, \ + "%s:%d CHECK(%s) failed: ", \ + __FILE__, \ + __LINE__, \ + #cond); \ + fprintf(stderr, "" __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + exit(1); \ + } \ + } while (0) + +/*! CHECK_ZSTD + * Check the zstd error code and die if an error occurred after printing a + * message. + */ +#define CHECK_ZSTD(fn, ...) \ + do { \ + size_t const err = (fn); \ + CHECK(!ZSTD_isError(err), "%s", ZSTD_getErrorName(err)); \ + } while (0) + +/*! fsize_orDie() : + * Get the size of a given file path. + * + * @return The size of a given file path. + * +static size_t fsize_orDie(const char *filename) +{ + struct stat st; + if (stat(filename, &st) != 0) { + perror(filename); + exit(ERROR_fsize); + } + + off_t const fileSize = st.st_size; + size_t const size = (size_t)fileSize; + * 1. fileSize should be non-negative, + * 2. if off_t -> size_t type conversion results in discrepancy, + * the file size is too large for type size_t. + * + if ((fileSize < 0) || (fileSize != (off_t)size)) { + fprintf(stderr, "%s : filesize too large \n", filename); + exit(ERROR_largeFile); + } + return size; +} +*/ + +/*! fopen_orDie() : + * Open a file using given file path and open option. + * + * @return If successful this function will return a FILE pointer to an + * opened file otherwise it sends an error to stderr and exits. + */ +static FILE* fopen_orDie(const char *filename, const char *instruction) +{ + FILE* const inFile = fopen(filename, instruction); + if (inFile) return inFile; + /* error */ + perror(filename); + exit(ERROR_fopen); +} + +/*! fclose_orDie() : + * Close an opened file using given FILE pointer. + */ +static void fclose_orDie(FILE* file) +{ + if (!fclose(file)) { return; }; + /* error */ + perror("fclose"); + exit(ERROR_fclose); +} + +/*! fread_orDie() : + * + * Read sizeToRead bytes from a given file, storing them at the + * location given by buffer. + * + * @return The number of bytes read. + */ +static size_t fread_orDie(void* buffer, size_t sizeToRead, FILE* file) +{ + size_t const readSize = fread(buffer, 1, sizeToRead, file); + if (readSize == sizeToRead) return readSize; /* good */ + if (feof(file)) return readSize; /* good, reached end of file */ + /* error */ + perror("fread"); + exit(ERROR_fread); +} + +/*! fwrite_orDie() : + * + * Write sizeToWrite bytes to a file pointed to by file, obtaining + * them from a location given by buffer. + * + * Note: This function will send an error to stderr and exit if it + * cannot write data to the given file pointer. + * + * @return The number of bytes written. + */ +//static size_t fwrite_orDie(const void* buffer, size_t sizeToWrite, FILE* file) +//{ +// size_t const writtenSize = fwrite(buffer, 1, sizeToWrite, file); +// if (writtenSize == sizeToWrite) return sizeToWrite; /* good */ +// /* error */ +// perror("fwrite"); +// exit(ERROR_fwrite); +//} + +/*! malloc_orDie() : + * Allocate memory. + * + * @return If successful this function returns a pointer to allo- + * cated memory. If there is an error, this function will send that + * error to stderr and exit. + */ +static void* malloc_orDie(size_t size) +{ + void* const buff = malloc(size); + if (buff) return buff; + /* error */ + perror("malloc"); + exit(ERROR_malloc); +} + +/*! loadFile_orDie() : + * load file into buffer (memory). + * + * Note: This function will send an error to stderr and exit if it + * cannot read data from the given file path. + * + * @return If successful this function will load file into buffer and + * return file size, otherwise it will printout an error to stderr and exit. + * +static size_t loadFile_orDie(const char* fileName, void* buffer, size_t bufferSize) +{ + size_t const fileSize = fsize_orDie(fileName); + CHECK(fileSize <= bufferSize, "File too large!"); + + FILE* const inFile = fopen_orDie(fileName, "rb"); + size_t const readSize = fread(buffer, 1, fileSize, inFile); + if (readSize != (size_t)fileSize) { + fprintf(stderr, "fread: %s : %s \n", fileName, strerror(errno)); + exit(ERROR_fread); + } + fclose(inFile); + return fileSize; +} +*/ + +/*! mallocAndLoadFile_orDie() : + * allocate memory buffer and then load file into it. + * + * Note: This function will send an error to stderr and exit if memory allocation + * fails or it cannot read data from the given file path. + * + * @return If successful this function will return buffer and bufferSize(=fileSize), + * otherwise it will printout an error to stderr and exit. + * +static void* mallocAndLoadFile_orDie(const char* fileName, size_t* bufferSize) { + size_t const fileSize = fsize_orDie(fileName); + *bufferSize = fileSize; + void* const buffer = malloc_orDie(*bufferSize); + loadFile_orDie(fileName, buffer, *bufferSize); + return buffer; +} +*/ + +/*! saveFile_orDie() : + * + * Save buffSize bytes to a given file path, obtaining them from a location pointed + * to by buff. + * + * Note: This function will send an error to stderr and exit if it + * cannot write to a given file. + */ +//static void saveFile_orDie(const char* fileName, const void* buff, size_t buffSize) +//{ +// FILE* const oFile = fopen_orDie(fileName, "wb"); +// size_t const wSize = fwrite(buff, 1, buffSize, oFile); +// if (wSize != (size_t)buffSize) { +// fprintf(stderr, "fwrite: %s : %s \n", fileName, strerror(errno)); +// exit(ERROR_fwrite); +// } +// if (fclose(oFile)) { +// perror(fileName); +// exit(ERROR_fclose); +// } +//} + +#endif diff --git a/libzstd.a b/libzstd.a new file mode 100644 index 0000000000000000000000000000000000000000..3be4d4039e0b6456ea77c54e10694cd705623bf1 GIT binary patch literal 1149514 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

r|PXrf8 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�#XtF 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
  • 6s7;RgN%gLjQZ=~@=YHV*JU3fw~vhJFf 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 z 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ℑyuS zK`K+iu39DY4il1%`sI}H%X`Tf7mX8?H!N3Q>W#7r$rl9sFi)lL?D1c0a4zYYwb5GG z?aR6=&kx`vvfll9SntFdwNS$kte>UC&Pk2vTl6dvOSjcfOL;d zVVP%HJ0=7gmvUM-Xg>`CPFG{r@_b= zRSP`~8i2%D1OEme3R<_(*JwsYGR6>T`k3e7_t;TKlmKz;WxtH9NC%Rt1}UwPLKtn3 zPN}cX9~SmiqC&9#6<~yM#hF%uFFdNsi@z;QlfJ*)*qv5SOa8H&-I!%)i_?+)`Q=?# z5cA%?@N2PMTk^rv8dxFo-H0*5@JpGLMjS7|E!>0>Lhwg}LMUbUL!;HCZ)4VTEzOQ7 z1QgWV(3f`&&zAfHHx=Mux!sH4S&t05Yj~Qu(%|`}qIx1jX0Rj7?Ko)tHThU;FP_jC zo-N_m1$g$u_;v%&K5C5!niKp+h36F@NsOGMfQp%s&?tDS#-Xv`X)kvR&mNpVk=_y< zD=JdJ&)?s>=kWY+Q9Us{pCwJ)t|lagXIc1l0iKV)Gz^{u!z;tm0tv4DjJp6yY27g- z<@FjSy9UQlR|!Om>(bYPWk0_|KQ`QqUq_Gs({Ml|=~HPVScV;6Ff(dPzVX)~6tQUr zheKWBvPKf@1BXR?;EctQrPwX+ow!xPX)ux|%dfyFOK^}VN$Tj(5>YIyFn_zbWX1Pf(DD2;~7w2p5?N;5)+w#J7i?Jdsq4${g72 z?^Nc%Q%@#gV4gve@9&&+w)|bSs)#Ax%(H3z>Fl`l_%ksKMqA1tg&D}_QWd#OpUtY! zdf+OZn`2_bHCDko+uVieScwKN{>^Fp7TiJeQ~}(S~!nPf$Sqb zPl*H5sy;i1Mv-{900+&R`s+bHPj|n(n4{-zt!bguO;{Dn{`~!+nbOttK!3WPUv#}& zeGc%oWO7$q1Ya_Fpd2R9x1R8ntCbM7LSRM4IS zRW_isNW}-1SKD@X@(ZSOM@*pM^CX^E>|DnPnL-CZM0TeXTC|w*69|T0Px1)C1*2bj z3O$wuFMA<#1^?vX0)8`=QP96b5pPI~6<)_UJe69;~esGUCHFmn^2x>dLx}^<%?PuQ=w^qvS~Fj?apB9=aW-rlOqA$26DBH61*s*guz7T zN!GJJov8IL@)STfKLq3_cs&QREhzXR_9i07r}Y!OtWOrsAlpX6lV0!TRI?L3OOhM0 zU250sS; zUhTq#Nzzqdt~>veYH2FjC9V6p=@YDF>#yu_?aej|*2iS$>Ty`%QW)jbJCw5tW|><& z5#Xs)SmN6E+j&RQj&w<~V1lpWDkFlz!i~h`u2csQnb+~#G_7Bz_u6T9V&_)=%-RsD zDgP0oS(saan9~Bg76Z{#!>!hRulHQ;gm4jB&Gm0jp~6MS55@!Mp`ud-oFq_@$825Iq9Roa}meQv{_+doP}t>ngd zhruLOx4oH^B(sIm&c?|TAvk9^CwH!);Q^lw>+=!gSj{A_3~komQH2-3R#qw6AL+B`OrbpCr$nr z%i7;-Z|9OoGF!j%w<3G>!B3^|=epd9IQ~;o25YURp@WGgw%bhZT6;ZIVYf;Fr4Gn^ zt-qxIa479C%FrMZ{Fs%;i{QKZ-4oh(E0aig*gTTHLH{ySjQ(AC2=mv+3pH3_+MG}z z%AcCam3N5Jk$Wkb#ojeGy&H^EuR-)yi|D0ds<#?1gbsZT_+HbTs(Y355ZG869qA3h z8;A(rHl`6z3j}WkJfRJp@TG zG_&`Tlep8msCzKguybMRHaJy*ZW(>8TWfSn-l0mmmtnl3XL$;m^o_-bInHOvq#CZa<;HP9P&N?@Tgj}t z`e}D6kxoBdY4U3!A4$A*M$YJ^2B~ZxUE3?nucle@I-cW3b5)=f14)u0I9lNxU>af{ zg(I3_v0yRDc-0-F0tz`&aeQv^heRss4I<5nfy@l!aE{(YVt3>iYyTG!3*xtC7+mTe zZYE_#5v{0uSfoOT0b{C-h=ptxMl4KfA^PBOM$vrnPDd;b=0>r0w)QI^7xH<1sBF<; z_`5D)97+Emujj!DZV^E&UNwVfCYLTJyV%gdVJZI%m|uTXYA#2(RwZ}dEn%cSxE>lY zd=VSPk1`%15GgEqo2vbw8IS z>w2N28JC}7(J2zCD{MO0iB-2YdrZTJmwd>7+%4(prAu0sQYK!~MN9coTh}b*(j~k& zT*4BRa3{Qy@}HT4CzBv0|HIZO4+*DRte_bAU0K4KW<0Lk$e$H0;XXa>eK$*3i$cRC zNu^|odTa{70B?a{VoR7seI*50-jYpN+tGSv*iKgPI^IeBw!RPM;(z~=J_XVnFX=hl z>~=|)Q%LBv>m_Yz(^cx9yQIU69smE57MuS6(31W=OEO;4>$&-7F6kEPwVy{qPOTCga${GAp=>pyJOq4A$r-jthZf>!lF<015*aiFnYKT9SIG@hahnmP}O zADN{I4X@`po~aL2v$995pPfB#eR(SPucSn$Fh}OAg7Mwj)e2xVWNn z)3cV}G&j96xR^q7v$NaMXRphh_B#DiCdH=g#7mMpSMl+kNs@*pInQKMki^nJGQ||K zVQqVbw{m0W>YAYm-VI;n12QV!AYO?PsC9g~;x>SO`BysE9wmvJ50g7F&#YXhI#PA_ zBsb-A)8F7L!DiiB*?G1hq-Roz9qCW^AEId2YJX{k-&VeJz)w%~rv}>C(7bbXpnV9r zIk97vp3G++=VQ35_j<1e+yFuWIE`FFBP6`?1k^a4tHkYko5J!m_tSSCk|9vII@x(a z1+&d&%_Vy$v&o4&`}w9Z*?HRvz9$5TJe1UyDFYW!Ru|9aO&@O*&yy)5$ZhfTuzrrK zY0CDP$Z^9d71*0+w~%^uXa8uKS2NAo&(6otC-pkY_HUu0PZHjO0%)ipJkM~0R>)Hl zuX5kmlo-e@bynrfry0^A$-05e_|t2un`X?gGt4e}D{yQ~4jfB|nUjHIrAeWc%t-pj zPgGZHEv4h)IYQXlX&XB)npl=SUzr{2$rXtJgLB zq**lnX;njSGYGj7I7sx^p4i@9a(;yt(T-~-F*B$J4YaeQnQqLNL2-UMNAL~R-Gsbf z7SBBS*uy=@*~W=~{yr0uN#v7tYp|5gzfr@{nIOT(snP~yQ=zBX)(WUL8DASG>Kl8> zTJ7VV3siTxlG#%>f7)}V#Bbx3jgirr(3hQEGdFue1&5ks66?q|?RBigFx%`u&NNAE zo?CY>CY=-+ppP_NjW~R!D3iDciXU0_s7?8VA~*w%+c0NVF-`C~%Eam9olPt>+OlN)^_vFCd1=PluA1d4 z+OW;0qu5~kIMrvT2F_}xsPa%VVGIL|P z<*t@HaW?+uRc9|@;U&VaNv`rO$x3aS?vSX+QXT5zkw%$&K z`Aj8YnA-AsZl!T(`PA`Ceq$3a=V2Gwnk_QVIyWmQ%YtU_p=%ZDy zV-q*c{{6v`BDI~bW49Guk4&}}W9gg^P5$;|ovgN+DrrVj7k)aj+MXp?H5OZK{l~4B zX0uI63cyD|1I%2jzL>c-xpTlmfwWSB4_e>uum=(n9HxHDkb^}f zbL~{@E^OFO`p;pJ3_+Q)U~3@XubyJRQIE+54D__+?m~V;RGaJnwuy71$g*&yIzTtE z**aBJTCugK?DTUR))BP4IWwM7rgP;zSX|yJyKSGo(mH1M;5%%g``Iv{aE>xEKuDL8<>&&*t<;ve0nrwf~>)2%bV{S0n zVt#omyXfAr=)>C2tQ9G+U1Akgx1T1YY{dW@DfxO~&mf{EdzdS)b?9w{ukl=^{Ob1q zOWwP{M^#<><1?8E5xF_hQjM+F(N5c>Dke%XBetDMCU6d%QRJaiZ3#jUDvy{<2wGYr zGYL8OFm0-|_1=10?!E2(_1=10TNKfvCcFYZ11RFF23j#=d>~qRsO0zk?!C{<$z%q- z_t*RQfBxq~n6uYe`?dDkYwxx8-fNFnQJ;sP5U+!{;1}Og|1iXW3q2Z5LZQN>2hP!x z31!-Kz95b?;e&qU;7CJ95c23SY`EuRhkmU+ zpXS@`z_PK*i~$y_jxD#3GEm%TwR?YUmRu>PK|dICrMohR z>Pq^t@iWFBiW+*LA`+!Y{HU*a49Sm)0c8~B7j|(Cj1of6+D@^j%sh3)6xDSVH zgZ%~;p6l@1KpW3%jZ=r(c!Z3?%x<+1X6!{mfO&Bxjm2fMMdMoMU_ zIEADfYM{-QgoOa1HJ5EG&9@XMb>U<+PFx%?tT%Yh8m#;>Y9N?MiXBWjE3}o1k%#aR zFb^-P$v!WJ4B7iR{9^o2;)DetA(*R>CFsv_(|X(xB88_(k}J*@a-;!QbDS;bCcI2X zsb7>Oj7wRpr-k0*gNp_G@I{g_PJ;JMkNQ%Bs-XFCjkrv5}E0{3>8P z*4sd5^7`6%nTKj34*w15b288h7Q*^rBkg{`_eWj&$=*4$%A~&?t zqb$6KCvE0+dVht>)|;o(v$Kog+L`$Lr#d^wleH)SCO)#aXC^+wJ3Sk8NT=TpKzjKG z?q)~8^gBW>-?%%yT1P?ab}%bBT+)!&&` zj{c78!#-($?;h6Qe@oM?)8GGzfjs@)S?^W-{W$wORm=ZKf0tfor2sV#E>GXTcCOE0)6E zK$EY!ey0lHrZYKl!Ht1s*a`qMK#`~U(lBxvJy3xNRmm~u%PV`o-?)rzxaX4dVT7~}!8D8vog#2rPy4X<7z{$? zha%;haOF-Y9NgM?8hl)XJ^ebYw-d%_D?T?cKAentS#7^zJxMUodJ17IfVuq7G$;by z6z1fJH5HrQ>I$vpqpgzg;A5%RIIF~-w)p0vT3l?__FIIh8qR`=zAE*9uxGgK%~%aA zq(}i%sXn%hAL=8_q{32}T;WspQE=;85Et_JSNIXkD}#68XW%@>b&T3{Z3x>arB)|# zFpt9zn#Bw!uSa9y3?jn3LiIlU>Pa}D$FDGtS}()Efe75qfC+XwMXTrCUqXG1T^_Lx zVDUkkQgKP%P4B~w&ZCcqSKdVHJTw2VH}x~ku_uQ&+!5rhvSjEyQg z8=n~n@T$*)tRMFRa}9vqLKKTKt%@R`!oWSD`d0@BVNAjOJ-p_0lX3n`*wAZr)C}&_ zkKsntndac;@a;I+fz!K!ol1uwVnj+KhP`h9B{Tvy7ShMCYT;1wweaA!)IAh?icD0D zKH?3)PdnOvj@TX?r7o`t_atlY4dO|9I8 ztECH1@9rzaF0R7UaTRN!w_Cscm6;y zL(M~K~pa9 z6`nL=WYNi^P8t0-#ahX!r+sS7r%ykl^vutE_N=o%caGnG?z!ik_xaBU@O%N!7uEA6 z^^|2j0P#*MAKxLkhlUtCc+zG8eu5cy$!Nj%QJ+g>GI{+xxW%;~70v5!IOX3B-0|lFliwc(TeQI&hW=|Kom$-?U5vehSri!0hkgo~@M8!MpA{kHBtu{y9CKazO^u0Sa zSJgZoKcYv-%y>S-G$W$@zlYYFc*=KW8-%$VT7n}a zzim+IeH-DN>1EnxQt{M+DY;6|atYypp9^a8q?0LEwD{gaU;C(>Z5(E~nBG+K3ZCLm zk{;+<_A%+LRq6f9(DX7qm?__6w$qN0CobPXmEPR>DCCLT;i}@!lYCQ(Pn@2-0Ntr{`8PS z-iNGT=y?Y-rs0oIj7+)Usxh)Y`NT+-1w_#2;pjE=8I53pD}5oW0Xt8@`&1S}HUkz> zaHriIxqPL9`&AZ>%&i05D@G-};As{AdpwKrOSlsbDfq4UWqEp4I`=5JrQogX|9Bi( z{;zl^exHKhAijD$JMqi(*D5*Hso>8m{HYwo@i?;H3p}Q?R>6h5$M;V;aO_LsBV;*# z$0~T|X9d_JWHMgq-EPQY2tE{X2pT#?UDope4Y!$LOV zm6{IIuRBM=g*=q6XkS%$6?T4nVz%OhGQU1Cd&z{IH=mf%I3fA_IP#UVo}qAtd}1av z6y6K`lA$0G+Ls6ZZXP)OfaXeP4B)4M4qac6nFu+K*MNdod{M%MT*m8H6})btgbVqK z*I5eQpcqZaNx)+Y?msB;g}lS--3s1$wIGO;TYzs<@Lq9P$|L0u;IAq8!87oJN5~Pr z+}aeIH+yMAZ0uamwbN%rW;e~5Uq5$g!!72*SbgK1MNJaAa7j~rV=TO6-o%CcYix=c zOXk%t*XJ}XxI|*iU5Xi-d~I%+Hz!sZdj?oLk>CcJ9(8O|gc?r43^%$I0)D7RJo_B{#+v2#m1{n;Peg{fZm! zQs!~PLS!r|^Rf6}k$8>uO>^fgs-M_cfAf$ESH~({saHv45Nr00^|34Jm&c-WVsjUO zDl=t`>u;VOTiRHkMlZnX3mm%3rU-?4Rab}V-@8VE?F28#G2i_DAqWK zB+Jx5kahOOP4zb}u3zF*OSZ<)!Ua8J&6+CD?AcARIdgBCJ$J!Pv**uQxX7a$(`U?< zsWdHI+_0$LqtBcu%6~(3nog*HeQf%W;>$*y(O9plGk59Y2B0-nFItM^Z-{{kvYy!= zWF-XobAGxwcQjIU-om+(q$Ee4d`0q~2$LzIjUq@ica}0K2Zfyh^LHihIS+y(`SkP4 zEO@K>vRuh~_9}`(_!tq-+Y0Wa$%~_vf!;Mp`y@GdRIQ(UrQRrpWSSw@cmfq2*M` zcy7;vJNf=@MQ;4wEVzTeMved3`6jmxO~;|nQ#*#jTUCL+v~wt2fjwW#f;;)H*fkX2 zN&okG;48Q?gU6w#2;jdTXTiJFhrn|~@mm$VA`ko?jTDgb&CQ2nza-(=`9`zgT_U!} z^TYFVgA+go(FzM7W@fC_hqi} zlxaET*^vcz+V^5y&%?*b*J&VoDb%PWC$;qP#b*Me?}KMpE=E<63x{JGQl(|Nh! z1%ce~-CxWNKlG*C@Vm>i;HrI}{j!8-x7TY~@Ggnw`ThAr({bqk`~^edXQ_5+Q1cUq z4$k;hICd!h3dL7vy5J;=OPgd9_yYaYGi|u}f^pf;)DJ4_$E8BqB$h zgu&y`C#3jwQ5M{>KWxl`JNkH`l7CJ*jy}$dEZyll`uH!h;Eq22UKZTZ$0MpgIq5t4 z_?j%ZqmTbN3-0LSp0Q20ERM~&0l%?E`Mbb*Yph>1XKW05if1g8$2p#{H#9YQ#-^cT zo0lwFcvJnNTL4=s5#;aKxlOUk@g8Wfi>W+%#x7Y3)pODnGcOc6qDrEE!R+~sa~9W2 zdi=ls??(b`Qq^73$%WM0mmLGT3aeotjYn}s5{1$b`4ajU*}!!wVGb+IN2y0*{f3wn zJA|v|B1k;n&dS$;EB14O9*PO3`JatH9&vNk-^t&xw>cSR=U<5rx$3|69$A0K?#=qM zzhu|H7BHqMvGJmw)}Kg7Mb-zugP?wChh+TIvf%gros5{>UQXEgy-vli$co>k;*&n^ z$Em;bI}ab7cQ@YqD!v=W_iWr-6fCQno+B!Lgv&&BOvY$(65etWh%YYnbvh(M(Em!^(|19H= z%KDg{|3#ITToWc;{s;!9<)QoYrUx~tKG5b+(T_I#y+3e6`N9v|E>uo-fUh_o9K$+ zHka<^^SSTg-OU5XwR1f9`c7?PNAnw@#9;Gl_!`?!l=lZ-ls@AL?gc`udte8X+QOtB zMN-WV4IJnDd5rTLqWvq;-Wm8O0{(%3yCmR;1gs`tl?1dRUwR;v?gx2s=m&cIjVgOm ziD|c#Vmr`i(_ZQ~?Hl38@U|jsqZ>QF)9MFG(O^>dH(U6h;bFu3aPZ$7<&!n7618Za-SiIz9Ml0N_^<)w=x+?xxo5 z=|g=h9svS;e5dIT!r1M?q9)N(*?V6Bo6$Y>Y=3eB2>YN;yq0*??arxy=;wFR_Gfi268|s2|0o-% zQx+??tH(^JF45w1QM05jw%TD|7ChXcm342gHF=C_`E{!SL_jvvOJk+_-0GqU)rDGe zs*GYy#-20K427sOy*D9%!+&zyV|p8q*7i9J!usu7*etGw?8Mn#ice4KRcx68f136b zxJjFbU2XVM#xHykBLiQ!tpR(yaF8Z!-_FVgYZffKY2Y|IjpPTxgNE8s zrbXO5yp?=``lC|>O#7x;`bmR>BR!^fQ19;llx|%fh#&Eig-W&Wp9j=GqgFc!L1)R(b4qd)q8O zmS&D+8&e11Tv|J)m9z@mlg5z4zlw-B*T$WcxYI3+jc!+t(YyB*n%<*qY51u$aU@%} ztMs0*qIOsjyB~GHMHt((m9LQa!MC(kd-wv;@X)XSO+JS8p?-M3-u*hRAuF7QL#9Zy zK=hf^lTddu{dEZO;l?vf`qbSh^!!eT%~QCmhx{P%*_eZkYBwgtjc#pCeS_(C+BJ(% zy|(*NAl+`N;|-TZxRtrojVXi7hOzVdh~br%KXBE zEoJ!g{ZVcgeTT>Ai$t0{IbUShw-dpzZl|0= z2z<>D#T7zbGE^r++hvI2D{S9#LE9ijEOO(zxDp3EhzD27UcGIX>Y!SJj3Rarr^`MG z5JhKD)URVKfJ*`)y{koUc^);zc!0gCKC2r2M2JFm7u~zlwBbR;DAIdEs-Fnw9Uk4< zp6(|tBvkMz+)#lRF+$ugNH-eWR0{*9^_?on?NDmxQRP1vZIZfC%0CLZ64VD<#otWb z0R)h+zlbgMx@o9K+`9CjQzHz5M9y-66ocQ@(fgUo#KG`(b3lukXb)uojzn zOKKr*#WKZeo$jiRfk$&JV&Q~Rb84DPXS+R+kBc-nTvF3ZrLd)ql)Hp>ZMvwyHzOaR02 zEto5;I7yvgMcE@0>Ps4X74o1&OwU=iU|Ko1wDQd``e$L=uJ4_b9W& zP#)d3xidt!|G*7|9NfxCRva9L_sOd=1cis9KNLYSqtI2(pJ-&pWYd9IPPl|Afj!O< zbQ3K*xQ;D7xQ@*((a0EVgJGZbwCjZ}3o5%@)2dAS>Gsz}BB>XI%*)-scs-QUzYOam zOrhg%lsPf6Ss$D8F+H)XOWL|3@J^#8Mj!(HJ0`2EF!`ihOwJgkYv+VKx{r)f0tXMx z?}hC)_LQhyUrLdjENq1&9l^9pd03_p2U0PyPL6w)f>bc4mRN_^D29u&$`=jqdzcT| zUo+y{bJush<-(K-epDTa)lUq8uae{C`Y&or|?jmQ&F4-!K!UZ}ggPvL=+zx^8HJ1ZWYSl#ep)5V2ZXSsOLM7TPYLjX2@HM#p z&{6U1M>e3o_HX(?RMY-`F9H#}{XYDd_K)}C$NLf7Vwk?4QE1TT&BBQN8#b9~&&S+z zcjfy7m#BUPzc!Wdr`TAh+ou`c$4u`BhIePw`>qk(10^|(_SM$!^6o(*vGRzuDRS=X zZ~}4%nq-9O-LKnF5#mQXS{^j*hboZQ!2Ke1-I|3tdg*m09lR9%J0LnhB0yO{H~bhw;17zId6WPh0Dr6& z9VTMEFWQ4M?th|E*waPjfv3c|MvK-PCzwEMdwev$-oAy!ad8&rzsX6EDm<)$LuX-F z-b+0CSC7Mk|FTo*;SJ^o0zD!4D0!{c{s+okrAdN4hb?BHNp*(HZlp20zn4o~=Z7B6Cb zrzB!EjcK{sdL2cDj&c^b=T9SaoPD;JV<=sYp|n8^rFYp^jhS#&iMEoS%!OdQxoq%e z|KQDPvQFm&Tl_3g>f69j^P`?NXS_6e+MMwcuSF;lpKwoGdced%uoBVTghhc@1+bN= zDpv5s7RLn`4)nGc4XbUtpsf%rTH;?ure!wAulo;@{)6{ZpyP)Uy>b_XHVW;hid=|P zF$)Gi#^5T;!Y~aW`Hvg`)=mVtRgeKXusaLyculJ$A)#@!9bo~iHd+?7gpD~DJVul< z;d=rvp`dNMw6>oin5_LEexy)qxA7i+6jLVVjN@JRiQ8Y{ikgRD+h(EyG(mg;iEbce z^xb_h6(Ie+2t=)4_TmR4v6grj`aJLHV5w@`cEv8o6_pQ(7>_)D%qD8ih=lI?k_&K2&QbI~WVY51nf5 z&|#g4m4xl+1)^Zqw%rGG>m4KhFiYmGwQFMn9<}cmshqhBr=omNNy6HdwK4rJL;LME zJ-A(mFDtz6!KDu+#)N+_MTwW)jDf)!h|eQK?L^3VK0!)Ph#SMjyYd@n+F3ap%BxLk0<73Ac)c;Hlk5x0di2J zCO9{&o!Ul80cfw&I6Z9?7JMMoC`b)^v7eLg-vfkWd4;%3qfVp>mU#~_beIJy207=> zLd29#9>73tWZYWoppkr-X{mm6ys``ZCTg{{0+u=e#kJEO)5bhEK&SJR_r{Nm48#8N z{W)M`hcy8ZtoaZ!ASJjG5$LN`~K>arGH+31r zf*RM<9HllnzW`$xFI{fGk5TOJLN{$=k2P%3f1`Giy_qv`bi%0|9cFP5sEd73FvL|? z%Nh01SY!ni6Q^IFke;fUKtsG6(lZuVPA62Y$PrH7MLIoV&IABN;=b@zS3{k$rovP@ zBEx?iU9JL8h2w9a1JxGQTEC=M~k&X>#Ipd#``9~haZL#ZPkxKJ4n@1oqhX8 ze714TFDyDgI0MNbo?pbff**j7Xxgf``6XFf7O@UJ(g{MMTe930q8O=f0aeVeFm%G6 z@(_)-$tw!tN5^Q@kHwFk{V2jAt$hyjwH{`NOg#-17~>lRSsjvt>x@EDby-M0>x_UkRg^E-#QUW zO?TNtsA$|Lh&lAfVLu$!eP!qu6;gDR72^PGwF#19Y#1f#2M${H4YspxJ(oWPkiRtN zR}Q~YM^uf43)l}660D=GgxV}nA zqgLV!LvICtLhsBGRfO*fnj{`4O4d>gCCB=&zjr zc()tgmyF;RNLl8#>~q#3bdOWvGqdSaCaePGu>JIESHQc|{fx^wOo@@avd*;N&Qz{d zpp&yhRCcxPYUXVb=$FhLd6R)tCZ#-P%nNv%D96iMc&OU(9$IcHWrb)bH+6I$IUJNp z{XM>OLYuk{7o0tcBr#8UEp-Q=E_^i0gCsHAro9Uc;ra$jKS&x&a%&T}r+y9uqo=kA zKu;=yLR7xr)@9mrFcDk`ihRTWz45%xAf)5Gyl(wPw;mVe(}n#4gx@TAbHYu9D+b_m zg7+DONJyO=FQ=!2H$$I$TChwwIT4rU{X#4O>6UXb@!f$g&b5gu^o= zvy#s#`nju?;tZ0CGP4eeEHSHpf}GMM(&2^q<#Db)Fsx{4 z>M#U4+v24^Ic?#`q5*Ej6JNkzjHw9gUVP_aMSRBy46eaegw&QNjj6SQ|KjG-1!#~yet5`K%i)`AQ z`A+=~S{AOC6}PH|ttYqu_$)}HN9;#8qGMyoThq;<7QSN#wI6NrKh`lO5IX~T>MqU8 zu}4Mh2l_;QAE2kl!a9@`*cJ6)E!2-6aLyF59%~wf>k-8uI%3xp4NRHeX<{OQL`sZc4-Du?*-6I!2_qtH6AO$;6!s=jy$NyFSBq2QfY%4D z?L4FwvG(9zS<~t{7v|N`P=5D~pvUXBpJ5&^^Nuj%b1-H{lVgga_O(UP;NMq(Tv7Y7 zD71FdIxC>HVV%Ge4Ib7KK1|-Z3>`u_A2@>iqFlt9jRKjf81|hkhEd)Vv45Sw8|~x1 zW{+8p8?BDvLZf5C@psjJMWhd z@HUQU@|%8Gvz2_+)?bEsx))P&B>aM&j8u(khU=Hd^m8{ExQ9Hk4GB$wXSB*)yuhS= zognR<)FX@yPKpDy8GfkM)?fc=lw$L!sP@|z%<{bvZT(rNBl#{P8S#yRxx+XYR;1^( z#79UvWji@0min4WHY^gVEd9E%;1rxziGWZAT_LrR@=uJ^Y77{hB86K zzLPvcR?=1;q_DKtkN-7n>vFvZEQd6DszD5)Cse+VP5?`jy^h18ZvEbm z8X&YCg&-m9@0jdcJ=IkzVo3a2zb_Mb*eYN(LUuMWOFw=gp&A$99rw+sRjs9)J)8EJO-*N7 zJI{RwJ>O!_H@)3hgEH-#>jrjqT6dDuX!5j<3@qjR`UG|w4%@z(Ja(FJyhp8iWEpC( zu8nn%Es+U-5E6@v2(=gq$Fxk3SjTjG3fDdE;JSzD?US~e2K0ah!dA1!vDI);nR)or_sZB|*s{^tfl=NzP(- zvF_}`o7Of7Sez4K?@?+3-pOy0tC36&u4deirHPjCQ-B2Hdzl7TGrM(d;yMv3e@D00 zk<$(DRB5x;qqIRRZK(G9ASPt0JK)_y(O%`ERq9b$d#TOrvn z2gb`(C{}t<2!U=X$22ifg}kJ^;6?>#m419GWzOEi*dRt>#5iRpF3CDLccH}YmY{uuvxN44KxS-m*i?kn5%U5u2@z8Rh!$x^(AwV>T6imp z162~#dkJ9~^Ko3I9I<~&&J*V0KOmfX7@uX6x1R=Tr8*&?6jR5=1jZ}H7}Jwtj6)Aa z*=>j}=i}#YgY|vAw7yR-O7)}H$$7BoF^N~8Nv4X#V3}Yto}L<$KUgiUO8pAR;=(S& zS6u}q(1g|o#=;T+44j7VyJXumlfgmMOzcbJ7l+XV6Qo{7(M|hSKX%=kCfZ9BtCM;56Z4ZPj_;tBCdV zz}av(HXI{(v9^+@DlmeDgU4H5H0^a_HlTX7y{;GZHOmj_!95f@er**eD5h_n?9ht> zn2^Hk@=6DoQ5cFaKkfggXTjR*2_X}j*1981#QHTydDl8SXU=Wgpf4&$VE>a7KQ%=p z4~(MafZGWN*`}$ZU}3`si(%6MxGh%YY{FZLv>{AGkz~ldbs@q{C~Bz%I|NMoc24cl zFAO#H!fyzrgXKxi-Gp@XuIr`f_Qv<(Cm!cK!JFEGR?fk)*;6>A!-cEVIMLbMf^QpE zAiUhAQpOC^+f_{K1=D&<((voNsHCEQ&NJ+&cvG`d6Z_PZX-j~$^)OwL!e6I?Bqqkz zui5`GM6vly-%r^-@O7^xu7OCwPFAh$3cOH)`!SQfgzoecmn%q_uzu*+d6Fmx$Oyix z#r^yiJf^juffsNEn=myQ@7&#VIJFf#LkWZxMCj_C|+1_*XH6# zW6$K%OG0IQ)J#nKVw#xtjWoHqtDvB}cso2t^%h(segXJHPM~H?m+?0Op(T&4QYMUY7+k04oTM0yK&=qo+jh7QtHtsYj4Hzh}dkV+pEtAKL>D z7v1<=j_`H`n@-@A6Z+Sh1Y#CEs)$=td>6>R)@u^D5Wp}<90o89paI}Kg*ZteUJcmf z9FZFVt3}*e;MF4TRN#H{6ULneyl)N3x5GOH;Rw>AH!!9(6F3tAn+VuMRklbL%q*^| z*$kgy+u$_p33v^A65hX_0*gKk_h(2VKC*qp>XVX%-}n7MI9%KI#FI}w4RT$NzamA< zD@ey&^vn2$f=4x<&s$s-&6Aa0T<6XS=O&Qk0+s$Uywk5+JNr`HHy!@8zA*|t2~Jdt zi{SYxgwJaf?vun7JY1X4S6acZiYtubGTr4OP5Ox0tJ0XjH1MoIz1~o;^?w6Erf+2W zWu}gkio1LTKgih?YEJQu1Bd(27x1?ie|Z?X$% zDv++tf)U4Exa)!ATU!9AyNU}`m<5|GYS-nP2H3Y;siXV*3U1Ac$a={(S%m2~pVQ@! z@VnQlU_a(zo=lqT5$f^sId7UnF|O)y3G=%`6yYg6|In zSqV=AR8=6y4?q1+`0k=%zJOCH^@uSG?-$~aa87Xeh%pJj)FK2v#wiqRW}39u^{`o9e`Z$JM+Ld3P^A;1OvWR|AN-yMzRXpx&*gz%lm{LLoaOJNe8D5eAN&&^=-d!s}WE?^AGRT;Y*IrtepYiLnLvPvuGfg*@>0^1w$b*(pnt`TyKJ z@bP)zU(Ex*A`krfJn$yKS>L|1Wmz0qzFNUO>WsLM=Ro@c`PMY zI|`p_i(~M*C0%AI*J{xjvs>mYj7?nH2)|%8__nBCIl01@`sEF94p!Yfe|~-A)TK)o zNe^M;h2tuGc9L4SbjjqUH+X8MUo)ddA3Oe1I{i|v)aEuf#g;CH3p2-YmI&9(SpXmB%(!gM5|f4B(`u#vr2wGGODSGEmyG` z>*2AB;dHw413^>g;Cy-0Y`7Q`Z>V*n{J3z*vN?-{Lo|4RqnkL#Z5#Zi-B`Z_{^#IE zuKwocdX!UrYODu=7_MC|OP%%IAyn;>c`}b#v-I)fW=n5onIg@GqqVv9!@Z@^Z5hd( zr#kpFwB8~lJd91f@=8;D6qx|AY2mH)OXtrPG!h>rK$gM%QM%nz*lBNNNI@35Mv<>& z!NNuLWE*;6Te###nGhV&DTIlQOBZLT?UVxFC-QXv)W)SZiik^>IGgnD)KKvQ_h+9Z7@~3?T0in{Jkx| z;s!LLXL0>vaEQ!8HX1T@0CR7dyQtn=sw&My=Pz8ei0%hx%(($R?Nqxhfs+JdNkemN zI!dd9v7HZ2$yDV8b1j_9Jm)st;t^GhHQo}QH-u^CExfV5NtLLj5&jU(IZd(J#SO7r zgcCf*Uc7W!z2`QJBYYr6g*<>ILOlPF2j1m^^Sro&|0WZ_!ylf%ci>-yHJML#`V(Dn z&cPh~QEFaGe9n~|c()5qx#hs0alv`))`7p}f>YKy@PL{sF@3uIaNu)XaCiDEU2u0f zFQlCSPj)$P$ODhL;H8M?$?98s0?Si}M_M{8$ zZpVWzxEue=E+1PLyUG)E!QJiN>VjY5!vAd^_>MgAzoU@C!+MRwpF@Y&TyS^#XDH(_ z@!jxC^1xTS;BI-c&INbV=YR|D=93H6yqx*E$BPaZ+}*!^;exyK?RLT4`TngR3FBe< zm*UT%!_xBzm3Lt9n?S%%Ou-4O!k>fx(B~1rL%2KrMHC`<2oK`V!Qc5MLWk#j3x$Y+ zUxr@?e{Q(|4@-ZQ3-0DCx6d-_c1}7oD0J{-m(wqx63%bVGfTY?#y`jI@=O+7d<0Ca z6)IffJ9_pLsslRdIC}PI=sSEIxT7~O%7Q!k?p;}MN3V5!);Z}odadKz$btLWICvUd z^ALV>^uI(F+|mF3+s!qccXrplZPBnHf}R;2+J;ig^FL5#aK@JJ4rX-B^UoJsFfIgl_B|uQ(Y&%8kY0= z!0MPNCa(%_gg;f5h?Gg#J;NTWuL~;PP2Twwax0%%e#eKC(Z$%X-z6A zKV)5lZCltjb;jmSt2e%XB(}-`1Wet=!6js1jdae~R(2t0Y-YnY6F6@OVFL}0wLFIc z>B)&$KR-57w_32TVv>K^SFpkN^dor14lQgC8L6$0oIaSqc9#^ewTHVNWV0X(tJk`+ z2x%n{L^4)O3AViee=zVY0qMQNSB$_Op9;F6U|Lt4giu*J1P4?5{OJ(RtnMpKhv0N) zACEZ5cwU5xoRBC{Y>KrLwR@v`lLc@7f?p3cn0TvD0rlx;J(NVT;XK zmts4L1!pGn0)2FvvORqGdG5{XHD|uvlkg4~0^+?8?|${}!+WWEFTgvth6@}o-iz=qnh^<@qrg)lW4do!dHDHS z9Gx|+-N{11l9BT5sS4~hnco?;-ezmXry&##z7(5eZHij2pvkPAM)}jx;D;?|qvfL3 z2W+|S{l4;Ev|=jAxMJ^~X+4i)2<7{%$IyV~j}N@0`WM~IU4Sh+phwjDlVLrht>VRo zouqSHKi+ifptxldk|gzt2!rSYPlAItuq$k-{?1_>2p7Er+yX-Kw1$~%ETXqI$&neZjyaCJd8SJwgoTrCB~P9y#SxO%bV>K)hxcWLE|*qy_}H{fdYhQlwA z!~3Jjn?3>uMBxKxC3gs0+vEEv(F`7Ec-ycGv^zE@Ueppvl-j`FS;5Z-wp0%sejn`I zPzYAORI+nN%ZCx~QQ#Q6^L+p4W4<3h;dk%RNJqxX>E^MdpIC%Hp>5K2LD3-emiO%LMwLn`O3S| zps5RitoRtdCiLJQzti7p?_!Uhvum${{=JJcdi{eZY1h(+As3*T&Bn3B*kAcNc2gVQriY0D`TT3@?N?wxcONZ#s(oY#YPY)7+@NH*7tRt#}*Q!|ny| zkcaxnKwt*$d+X9xvb-4yze`B?)8O!OYb_;wE2b6LM~+?6yI+NL*LS~ZB+XwHY7dmp zw=UAwFD%g38$NG85xh?W@xKqt`?QeubI8yX^+s~~aZ(7o{TkW*H;^9NBc#&dm*cOZ z%Y<+eX9o`)?C!Ht`tE*olUCHEXuhRs>vhy5#2^DxZaYnRiJc*0w4j*#6BJUQ+*#$!$ z(eJR};@wx{j^KFOCl z3~NvX)5KnOay&Jyaj-AU(8Il~RO-3h0ArubCWkX#^*8d*OPF+k!D}a~6ti+FjTY zA5AtMwKk!u8`N+vs(_)~2tLnla<8_sf}Jcqo_7lg<^>OKsZIP@OYnrGZf%C!fGzt6 z4!>w5ufMA>qCIdvE^fXEbF7=t@2~SkyeZxL%Kr$O80oJArHtgXAjDcRM4~?ZkVISd|4EGXt*nn9_4yL&QzztD#JkUtV@%Qr9$Pl$6N<4A zs7W#QS(g~Qk`xwVOzLCN;Nw!>{RiY2W&?k%NyKTHNklAxxxJV`?8F4(+4Ka0wNBlI z?jZW9ZVg1OCov|1X|#ARz^FkC%5KzpTFoL}!30{#ucPE0HI4A59sss!Jx=Bj6XoxdZ65m%w5g&`F109FM6T(-rJo~+opG3cWr@T9n&7TOnczlI2iZTJxM)3H*!8|V~VnrT1b%HoU>$S{dcldLY*;d)btpP0Ku=T8LI;urdXz=#B? zD+NK>M4*%3ZTJ>vpp^l~81QA91k%O;eC@#3C|pt}ehb5x4vi-M!ALmIgP30}oPRKd zUf^i$Q;`jh4V`(U8u^?l^66sIPCoZC;N-(8vbKKHLo_;YD!<8EBmKGHxLhKFtW59o zdiOy^XF z9LOYP%#N!ZTKi8&i<(s)V$CX#@+-_*i2!Yt!akJ<@BoS)-#(atMN4>9Ucc#4r*1t+ zz5^3VYa>p?AiY&v1%DP^%!C^BIGiSP?i>mDVF!bP)L&pE&w|C^Wpr_$)dYO^8qqVKthD9B?0kSXMoINSTFIlOS~epy90vYG9N((>nTR4z}T4j6R|ovm?)uv$x8^H<2A~kFl-(YTN7vn%$#t2Af&CVL$gP$*TeBw8R;r7ri>9w zy1al*38o0*lZ)ipT{ya~(pDaUkvL+#6R{2>&QxX;e{G~*{uGGKR@Uuea%vx5h3{tJ z&e06U0%dR?wdyZu{_eMZdO1#CV#+8!>cQ*bXQjumM{sU|3c&@J_LHvjQ0{Yk{o6Z0 zNq9iO@-ryT3ef{mD}Igmrb0cwwa}o)ddg;@Vb7q3jj?u`H{4PEp%)g1gs1`{Z(wAF z7)>q;(H2%qtJ`94N9~7U?~}ys0}^mWRnoXIfja`I%aH7(4>{#23VWY|qs?^2LbWf# z;OzAt(c!796$y2mLe=Co7VT*xxk5+Lpwist_2P8)Mp3MA$K;P9)Ov;^g}omUtB>hK zf%Hx;q=@%0iC7fwxbhHCQVXn&O4&DG$~mVzIeV zsN*P9ZoFFWUx?So)S}0&qq(tMyD+s*c^e?RC82M!ma`=&B8Rg_7a8U$f1BQX>Um;TWqnLb! z&1pXh$yeB%(xqdXYShXU?;I45R;NwLF=>mFGfbsssKSLgMxk^EkpfHChu@Y3gMl~k z>n@!0O*2j{38WL@;Z5Z| zN))C>q0E)LQk!wIvvq|K_lIE7`%!BvjC^5eFA)qRC8^&dYcn_xEEwO2S4Ye;UP)T?qJO}pSH$`wEc9FU zKYn-@4D>V6KQ0yq`knX|>bO)`@Bf(ZbBna@=lUM%Xu&Xi96~ihU!2Edz`IHC&+ud>XAb3EG21zMu`#b+U2&ctbB8U!XLZ^Z=ml+@n>)F3-5=b<|L@ioT zGgXM)Mpq_fvqiOa@I#`5rao- z;qY%y>TYD==!5W?4RZb*knC{?0~dXxk&3pU;*2aRHh*}ck00g!80u&y^_V8>5^+&% zCMhO{Kv0bRum4-{C`F204T=fI!8zfm)dymsek2yL@WV)lUw~>4Vqw6Q#2QEwt4cPB zNFmhmT}dpF5r|bM*v_!tGp(&=aw>==I4){ETWkF>wH7JQ@661vqrtbeIDuIsu^8er z)PfV~p*$Cc@!ugxu8(PI4gH8{X=>?1_$v&{Jjf+x0U%c&iqwD6eo3xt$YAVOrggv& zyrsxRERc(l9CD%HdB`PF2zT5j$;CK=t>C~cYV|0VA~*dEU())Um?~-Q4nMK;BSQ)j zj$%Xk>6$ElYD!ZGU3S>VG=+wK40SZ6DFl9+5+#@xHgKpFolj(h>DgT%haQv)m6q6x zYevp`w_DwK}k-9n?#+D7BRcw8zAF!vpQXK?V` z?QOkU;yt`kF>b>zHSNkBsSDs!4TB`e6o3bm)lwyiA3Mnr#x37=mclawrSr`_7LgY2 zu}FpGScI`jg)x3|kWYs(i1uNKmtmzHWrm}d4`MjF=|gB~!hc5zhWB`nE(RnLFm(j= zB&&f0*Mol{6lc+nwIKB%Myg>$lq5*3i0<$@5$VLk6vwBMoa0mKI;8F-lG4PW2x_w+ z*qpE_wFnAh z3mE$o*b8dyDw+(!-EYI&$q=)FV=OoYlRmCN58OGwQ`iUSpEcV)zh07}9aZ|PNY`Z?hy<1k%RrLxhgt@NyhR3GA_HFOlw}#9EKgeo)-vE^vNwmm z{wpHTGH@QU%CZcw-B=gVp@b0vveRW5=;L?C2GPralhcwMIqe04uncS%ZW$oYa>`ul zu$9(+%W%tpYaS%ZMJ?R1415U1ADtnWWuORX+Nu{_mVr8#WdLr@hgk+@4Y3U9F3Z4F zjQtU<{by;*fMW@e`Z_5ERiOsXgM?)u#84Q89UZ_+wFl0+kO16dC^wT(gBM8U9*|B_ z5TAwRpcMfFb_&4n6|cfd&?jDnm4IS{*nZjtxYI`yb^)%>Fe_mf02_rL0iMSwe-ggl zfe)UeW>cZ9{0iFJIs%QJCG^q;yHWV7G|IOaHk?GE-Pa=K21ch9p$vCV!60D06F9^vdA4E22fev;i$kgp#4MBDhxpN=bEFFuS@sGisXzy zL&qsXU&kgE6bzeKOb%?P$^@pt`Q{o?9A!bsfz>fnAh94=zC{&t;9!mdq%kZ@w+q3w zUj|-!Kq2V-qA)p1t1JU+kqvbI0-?;K$a1N!!EHf+mEk@?myT!we7o;HkQ?oN(;gRq zErQy-+dPBWN!~axJH%K@X(z~?t}tMzVnV40!!FO8o~x*X);kXW<}EQ)e>S};_@FRD z$U;m>nhoTqN}>BdW@0j573pY#?tc$7{@Tj-Q$6_QyZ{nT=>18IZ4A5h{#GQeTkJY$ ziHP4UmR9-2yVCf@yHfbYyVCdhPIVtCI{Gvc&Q|q(C`m3=e@0+=Y6DA|(eo)t zoPPb+*7MgQ?O}TUjd}EZcGk2J!qM~l0A%Ud74qr%{N|opsc>4)SK+jtFT?rteEF4T z?##sMzI=K92qc@76^`?x&|jJ*I1D2AFg-pha~R9I>H_aY{OA*Jc} z$zY8qb|!l*WS^YliH63z7}Ypo%P9J~QuM#06#c2%$_AzAN2H=ZCNNCTFG~FaIhnyg z0BnpmA+C_&aw=+&yq5=P8sDR{zhFUtrq#C~znUSxWIxyrCH+HSQ0NGp9N$0snD3Z5 zi+m{7uw{e0{HV~WU4Gc&#}2M1R|FdRAB3+eDixEdbwS!uCx?4~q1A^#j|f-w8dw3L z&x;*|QlsBb>ulBrLJ=FHZm@Ze$O7@;jd(^Nl0%(8D?t70UqPdc{x#tOknPukY*K_{ zv=DP8sPh@RL0*$)F*nVi&ZlXX^Sg^?Iln_Z3Mr3|tQ5@!4!xp-U=UG^D^v8Eg zs!ajasF>?YwdbCh2(8L%Q3q9ap{v413v%(ouig770#guvI)&fK((ow~iF8BT^jADd({!og6(OTS$20ecW#fE*)qS&zS zZi*>ufWv50NH9?h5R9ej|IE%z_v8ewPU-xU9fFCnuMv?1y}(>#w9SIiuwaqJXxKl5 zL?Rti$Y?SeXJj3q4b}cu(kSr)a(2}I4dRQO4r__u@QYCU`xU%T#7K~Im^2HG0&2g| zuG^}m+Rq9>?H`AbRQtb-H>&-Q2(=&l3%x&_rT2@8w$l4q$9#G}zq!XI6;6*$DxB8) zRXDBpt8iNHXLy+2|7jG|rS})P^nO+@^%?lUBSAoe>%r3yn^|9SoYqmR@7zmE+5I{N<|AE*C!$-4Z% z>;GkHlG|)eXHOT;8qr22d8WZ)mIbDiZ+KOP|Bi z2xeX|Tf-E9WhSvnV5lh|c{w%@a9;pcd7-1y3cv_caeo(zb{KQM)j~2-U!sf%;}8ba zy@TLztmzwAbK$B3L{T3gv;Z)|y?Dnp0^G@0LF_Jsxfot%SjSE4IVjd&g1626_zfTq zF484Zr%2#&0KY(c!WWS~M87Z{%m{>t(JC;WO>M@nv;(O90bdkqI&=ZO9E;vFMVbw2 zksp=@ZRO9<7}il~2Ka(NmYq~De_WS3{aU0+0|0CW+h9?13;=<=1^~D2Kat7PCV|}p zdO7w-n3$uab${(A-F~bjtq7FC?dz9l1ke)I*yV${3v_U=J$D~a z%+EG?^C8dI*=1L%CjeiNI8PlV@_2K?~|Nh0LVwz!#ebyS?@;L#dNeY43p2iZS;tD$v6%ht>G=+jFDEg_(8!aqRhE4 z-Mqm&Akzg#=USdC0@gQG%J5b$Eg>i|>?+E>kSh-KrJKPg@3Z{i6J==l##Y5 zw0Hz|1}r4c7Ul-bM;v>@*%(s)ci0>5%(JzC_6D7${2#P89EMVyYhM9p9jVh*;r@5o z8zLvPH#`AigB`vr?G0bV7=v?jeY*8})OyB*vvTW|C_FyX_+W6YUJZ0@>tLwGOzsQ9 z;vi?t2nIw@u5lvxC1rpp%VmJz_5jQrG5le#0e8a7od(C?M4JjC3=nVQl}aIY)?J6? z5*i?IzRkVSpeXeS=&lEG?M-|e-yqh7{w@p1g1u|r9wCpc%T(xHyJ|`D`j|hA@Z_kSvg@FSvCS>#!*NThQv)DF$#!?t^H2j*gkJU>IJ8Q{HnpC1-jLp5OtJOHa5v=M#+<2 zlOPXh>oF1B-LEGjalr*)Obnf~Pnd|v7`W;so##Jw3r_eAQpE95Sa%F-ZwrqSfCuEf zVW@S-#JRKGuW_Q}p7!t_A2she0!reD>au541w~h1HdUp<*(B?lk0(Y4BvX6_+5nlN zbU0He3l-RFD*FkHM%d_pEg?rmZ^4F;)bEgi8GKWVKP<>I#8QMyDVX@2LoquA~6 z9Oo*Ss&JUiG@u*B`&Oo_i-{oS?9b_P!Ni5{rYorYdq;@8=SgPSc zxHLOhQB+c%gcIB#^WK`yd(ERtE(CFHIJg1(Quel7!TdQ@W~xY>kutVE%qSf@pPJc5 zkPBp8dbX;2pEQFnFWc$Z$SB~KU8xcxLx|Gm6cQa#kK~853QxcfERZ0QZoQk?^b!Fl z+=nmRGYlJ;7%Y$(wL0W2j88%~i1C4B;++2hB^}0R^8&t0b}_y0yPFwG!4O_Z;|)Vn z%WKRjn-@T>M#&5;mVwzRL;?%i?|Vct!=;KDunkHw12&nTfFE$Hj^GDTOVpNWsf9*l zpW%lvdC|oWGJ9MAqq0vtEcpTHfgfO!ll;JC1@MC)rOPCT=>sys1!=AYO?d!wNIr%a z_mun*0zyV*`_u`3kfoJPlutkuOMal!-MsRF>0tkceYZ0A-5qEJA83gS5J~Rw*?_PP z^WD7yUO;#bW8Yq+Gu+rW^DVK*hnc|GHwhtW?E5O-XzbhhrkGUCq|MK@zvq9Io$o@F zvG%4Xoc%qt^M&KxI;LI<|m}euO>D!02H;_pbhH<&y--KS2{0FY$q^ z1+4w+j0n3&^RFbF_FOUTWm2tc2a zqcOGOR%FVs821qFP+>7tAl#|K(tIuVtODJSGUMx7d_|Ta&J{m}wsd-&;I4>}*I+SS zw+aJ91DgXQa)@c7AXP-CYg;-)u?x{i=fEC>iO5~-RoeR2m+=#^1_XPTZ)Oxf<{pWL z@|Q*Luu{AGFp(Pvco}xTp-9Li;@ykVeE&E?`rKYN2QD$S*NoxyDd^xK^w9OzAI)Up zvdPJD?d3al?=HRjKtcS7*R&JkM7yJ6_o9dllM_vZIao7HVaRZo3X^OMZ&zWGjp1Gu zCfV%!#*thE?*$m9vp)R9#_4xie~j3P1oNteCj5Dd0Zd)4NJdr_Vx=fzm4-SRC_D5oB54KilVMz}wtCDHsFa!fMKy0Ob!ZRGO3)FuoC~_Q%zh_f)IQ zaKm@l`^xSESR^>vyRCa)QN;WF{7(4iK$lLA`AKlwd}~ys=YyLl&{gp*PH4xj+I~oftGzr^JpMW;i+9rTw(;TTo+>IU`4Zd}s7xfool5*A4 z{sG2J9f_6Fx>I7pn2kPJWP&G%W|pT+@9X`0a1tW56PPgmcz1<^yP8(1b!7;lFz@tD z7Pd!%yGX5$Sq0ldK5z4@sX7ccLQ)7mNgcz;DWtng?Dl{kK}ad(H^BOo`ZWH_C!JGs z@v)!y>G5mG`kjsjo(CF;bG+}Hve1C`>x^$d&2X+%>u){p4W+5 zr}x0NGT`ii&41qa9HugKS1-E=DZ{RzwLb`itW~`(A~<_(*(uWbobbHweHfv$cJ;E? zGAs60t$je&$hE3>(4AACJEs$#_npkSz{j8WEy=yAM;Ue`{4{g;Z z@Gs4EYE`d-l8{G#omD;mkPW_SRd0b>)#IsOMyS);x1wlrRZqw%*#224wW>#AaWxQo z!_(_}f>0Ptxvtj*I;nNNkfSkk0#Swax=XR5w+FxDxOn#!BqQ8y!MZ9fEdY)Oa0NF% z$1wh2LWQ zhG52eAF;Rw_%+1BvCZ!>*3}{w3+#Owu~?5N?5iq5lq+92#>Z8|@nD#n3osDCLV%66 znbn#to8pIez|@dl2OMfGq1mk!$vVq>!_bGX?y(Zi>fSK)VT*fx0$VOh4#OV4Ch4s0 z5%npj*twSW?hCYvl*Otfjt^&{4`1AK6?-`Pu(iEDk+>-KaO}*|9+t_(VkDRLup&9E z7}*PwpRCjo?;$ik7T=CSne9MHqTU0h{R_c9#_m5emnR(BoQq}8^ME}^t=7I~V(%6@ z94i3rM)VULt;RC#?A^vQ^Q`_!qNl_% zWo7}e3#DTz$w+_g4aZEg8pVSV9) zsujS@LF27*_3tZc^{>k#76Zqr)j!r6NnNT||EkzNa`crPB?dpN{>|d*--02le~+rw zKb$kZPp$q{oM82@8#&3Rcb5Ndg2CXGf%( zT6>IbYLio{K~a-xbp|G|M<)^mHLbJ+xil(Bgb9frtASj|?l3m3wsOw#wCS<6`md*Q zsv=&FO+ZNSf*@WHFOBz%s06SORN#A_b(z_-G6B@{|IYb+pZf=yy`KH9`{iBhUGI9A z-~(&L$w2PNordul8-NQlygMm*)#n{G0k#8OG!P_ZdUu9m<42oxum&Tx|E`ytU@#GY zhm*9gby{nbxXWmRnP4~vwdg^?Af`A5)A}5 zIGaJGHW1x9ns63f?$EJyuwkHuRHK!lpqQkP_c>M!6WQmzQ#OoqRKpOb#zo68HU+~j z`J~(*RQXfcwN|N?!ISVZc2?IIWK_3}MD${RP(;7<529V9?hl60Kt+pi6=UKcE~i}b z5N%6^a0lu_@)>txLtRe+!!>Qf+oPmor^}c1UnY_N}7<;H-$IF@%Lqj zNjC*+RgG*3B0=8g*%Eq@w!Xc0$d+)PY6)U5sAQ;(LEEkG%KbsEBSesB4-8Lq-CJC3 z0W!gqrXb@Nn}Z_$dJ%u*J_MHB9HbCIGzF5V81$ia5raJ6&%vQ|uJcpG&>#n*eYSTU zw*@&kh?pQvlG{Ksk!r%EwgpoMhs3tvR}Qr;$Z)t_h9#S^E%;}A;kHwO)v6l-`4W=P zHJ@$-TU?C*^LdU3s;fiCL-q78M1%4PlUZyT)CMpd)XGM{1ceYE8Ud5ox9fMZ5qw`Y zf?AP7G=iZv1pS=faX*mtFS3XFXHbLcU#yK>LxGH1YzB&`>qXR&`%*Ro6ZP*Zz#;2D zKxG+S|7shM_0O1CAGLxx$VA>M5;@dJptb>pN!Fp%1Q?Fo23&#q=5}8@ey!#+Ah1h! z)IW!Knxp=&v#_vXQpDfHQU9amHXtLxHehCA8*m@C*0>G$GyEp_HRf1fGxh_;r09Rm zd4C){A9_EKd5jMrhwM=h#bUo!X#I1ZSQ9@5bG0L!Jj%^L)&e{nVgktt5cUlR&m7}j z$<071FOToX1Q*?s+k;Y696#vZ4`h<#d))hhA~$bAs>A(2rX_xg-VY?PI8bnu-VfyP zAPy9~2@k231ResS_rqNB$foygmoO4S*>RnrB?E6lSPZFW34-JAc>ZEx!Hv24Z*nSJ z>XJ;h;i?w9>2ce&4`;`gG{Q^ZqMF&LjBxCCZ&B*(JQ;F!R$gp7IEu4PY!-%{cey24 z$t}TYqPi%4@*^~eiwD~3Z-tWfxXvK?x3y|x+a~9Qwx@6p!m4MNyzcQF^jHseE_ny> z930sL@$Sg}VA+~QxhkFUOORLyH8y?gAPAy26pn{QrjeTa)xJ34W!Gj*Q$mJ|7IbZUt+u~vM`jhq;rINq5W5#NrA937on>P&19-+Ylw zXX*`T1P3#%o9{$gV)dUwU9#)L3@n*q%WvruegdKqlx9-61~VDTj+JJ^|v)2!&G7Op{%=ZTQn1GB|8xFdr@t8EwY+#!#x;w zY+}}2YsUxp|5mxvjh~mQCGl?bqD0M+{d?g(od$hKNW;Dy`0Hy@y^c-9S&+RcI{e-PX2I7oq`$>G@K40}lz z6N;?oKyqE~sfcCuW3L7;+_d|3r|sQo!|r5Rk9A8|AiKwE{*U9(TblM+R_h&ni(T6f z!ROd<7?Q7v6prosFso_3cW%(CeF3t`uGh162do7da4%TPKX1a?4)!lM;rwUbT&rfC zJ&Xd{xmNY_{JI^V`sZ3#K91if@cZ0cYvxmP1J;2x_&T=_u8-(?mVcfg)CIzQbnT++ z9lTvJz<{2?+oGMAgj>(;OS)m`*Lv<5<>e|t*iFKIK`7j?N(#>iYt38)P%xMNnU;Wc z)h7J8qKBcqL{Gbgrzewc5&BDl+FG;=xqA*-Sg;x4>|!{0S+J4eFbh{aMSookM@BBf zm2MFZGq#aQU^qLqgW=H8!*JFyoTnMi>kQ{3kscAQbc=9a7t|^o2HK= z=9XLYH&{ovdC;^$@=Or#dE$K@N^AZ>I1A{6hbOJ0S5ujm{+^`2m+{dr{XGGHD=MWw zWPSd#_{1$M`s=ZdZm(7Tdg!myI@)eTWH_R6w7-3>H4nL+zXxCk$)B>0u3gDE@M3og z1b^n!wMk}T9x@^V>4pFPvc1;QTfjBYPx1m~8)8{#rz6otgOq&-V&i@GtUcDM`%h;} z$CPi@9^2WAEpM3Z!8vEX74U7=fb9kEihLiMi;?f=#;*0o8nOdrJFT1l3rq&`PkYuu z`t^b>y21+KVj!3R3VYYfw*tJmL&=CWAW;aV{)>c?8EeQFp^WTc)v&}xv_?75y~TAI-k2-j*2zZAe0-glOZ&9u|J|4HFd%& z#$kFAx^uEK-FQ|e7?{1n_IBE{Hdw2c3)YWqqFK*EKMA+1phrI;-_WD+Hfega+1~3Ia09E8y^YJ z!tkP5Xeid8c_-8hav=BlsmJ#2#km9gmf;4ATe{t>)1Uo})qEznx@4VW@kWc=W63|k zIfI2`cUucafqQ}%c4COz3@++mte@El9@=!kJJ+i1^xzwYzZwjtc^C=vtmY8feA9sp zcs)Olp7@bO_*!BeU0I;6);N$!3UH4Uib_!Ut>$4Oj4V==7!Wbekkyr=Yu9&}EaZ)}ZT`biIOZ zqM#cIx}P-yFi5$4MiM#Cu$s9P{epCj zAk8HyGirodD@m&b>1~4aD3W##CT*9bjrfVwHXZPhj74IExKfgJkSx@6z#>`iV6sh; ztdnHlAPq~+2&!Mw^x}ui6W4u|K^f<1Y-?d1k{ugkp=+U!ipeZBk7x!2S~OmN+i9_j zys=uSk8QSY{x{IWVlp~4I$|tRmnG)J2s7ArqqptY*hLw!+6+Ot=x~&ovD!>Q89E$g zRt)x!82gEbqZ}5i9VRIMj^*kh(>Xj=J6upcc{s`uvDy)W@^^=$%#PJ&3(ES#QI3q& zjuey?hoc-7s~sgMzj-*yoLFs+pxlS8s6*yGH&&Y~D4#hT$^t^%(gfxR8M*G%aZJpx`I`b=KMAQ7uX!-v-!KaqzN!w21Yx&*YWLjA#;L zgx3>3lh+*S^;qHcb?yisacjzzUgc8c&d=oaDCzZh5$n|nuX;8JHAoB`L$_q<`vmLe z5_nBBlwe92<8&_;95%d76iyo|rokKcfL;tNdoiO6&zfF1MvN{{AbJs+OxXbnLY%Er zV&lIC0pCwOP**LFjUDN1?CPVo0OU>8c}e@n_N?{Rs%9~MK?;YND7Gu4{dD<;Y3G_y zEbAPI3Cp1z+i5$Mn1NPeqS;x1Uz~BQ{1mEdb?;*2348xm;e{md!@9fUmm_T=kr#_Z ziizel%4(j0tY&Z5JK3S8PRuvgaNyJf;A%UTdyEXZx z1S5QlUDppywXx;nFm?%H&5kS~kEz`Q1D9QIa1Kb(&4Mif$gS+At~_YtHL7849fma_ zq~W+Hg)>2GCL}eG&{zjB2tsNTv<|F?VvumH^@4>oBDRRcRsHNG_=k;~O#!4DIlJ$LH}4Sq6q^_@485d_#tTp%%Ws{ds&t zc7Y)izT?<7`po%;%mPCneE;e5_=c?F1mS!B=kX001!Omf`_#|l8?uQ);d^KQ=g2o? z5=FxIeV@lSWD#S9@4C<98#0Iz;rp!5;~TPv@xu2$9J@n(H?4v20rSfZO`W9)0m4wN z9g|$qU96iQf$vNV$QbdtAd>T27&jPF@KBx=Uj{#1MS(Nm(y-V$Ww{>5X2C#v zC(iD8b!Ncx4z}3`?6Us4H{w~^Zv@GRg`XLPIU@Zc9UI{<4_rw5g96h~1BTsiJNAHG zHc;0S{|k02b!rge6fj(*Cb1uDi}Ylrrf0uQ)2Av~FURNWEzkCH7;ekQX^6yrA8t2> zl_cKR{XDde9?mMj*6|*k0?aSMiUAtf*sk|v8^;o|@hx%WPku+Zfz>3mo!7-hQ6J)z z1l?E+u7c`}wcf{>#JnuN|lCZO3 zDi0oGd@n)jp?YjGvKOtjv}Oy2iiycP%ahU3eA7}? zoj70lamtl$fhhJ!2cPngN#5Iy-Ahj--Mwy)W}_6Bq`%kAbtl~7$Ai{`$K(GMxae&I zey`7ht@esy6y3|bp1An+3TJ;s@xD;(%8%``xY!mI=GvF@eo@}DCeB@`^Bv-{HE1>n z%>!&qcc3b*){)rZ!nDYJ(HiADiGvr7bm#*f<4_YYIi7OSS{n$2k@Nd7QNqCs_w{Ez z^o*z|iLqKQlTn={?sPi|`vvmcR3fUC?x@y-VVhBL=ewc>x6EVn3sJou;`Kx7`nUlV zl?T&2tMzv>sxlFkyrzyK97M%fd-9{`;kI=Y47$qW} zNvKGa-bito+elpIru73vJb9qe@$BK&yu0WSXdCX-#D+gbYt}N}yNdbmX?|&{&E|e) z1E!JsJ~w~Lp$>VUTdB6%J{<=y<*|+ut93RKwRc+(r{Nhgk8e1eZK$y^IXHST1*>~u zsy$3s1^--#`lUC?yVxDi4z(uT{@sDD{Umu0nH0}u7i#mIz$VC zqkxW1mL?oH*Ch|L(vcVcJ79gr(o3-=?5yGB;wC;}i z-G>N4-IY}gOIX5Ad!DV%=ZQme)HA81R#3LJbU)gL(@i^X^3FG5=gm)wY+hO%9d0{W zg}CeOq)jLF_zU`R)!P)Sc?aU8fxJef38m479~ zM>b%dJ%fVz0n>Ur6tiZ6z>5Kx4L1PmEeNX@uQd7Y^bZOQ9v~LM^m?{v{;SvL@WQR*k#t|nb1Brni1Fzzvb=nYqhfdr9 zXETx)!clA%)AlK`=~XPI92C>#ET)4fqaaq9B6n!oqV{RR1I4K>&w~+Slv2Qp`)ptW zZbPC)Z-F6@NG+&f8w1z$LFf=2kFvsDZiyx2gL1Zog#L11mkL?`E3Bua!K6n z#tgB?xlcJLcjA%nK!U$(=~?vyKcBIU}RNV#$+Qm)*Il+(?W zgG(k;ZbK@@BO0kG=kOAL6Pe-*_2s}f(6(xkDZ#^9c*+`Zg(q^9(7a(ucS_Q*!Z4u9 zbLgO(;;~vz#wHruW@#8367rI_B4v8*6eNNslVOJQo0QUFz`NwmL{ z`rTz=I_-CDVOp72x)gyYY;vDZ6Vsy2PQpYAcP!k4R)A^zEZG=r)ffc({U21r#5^Y1 z63&HG?do*{Xb8IfvZ~`3qe6J7yM&5@MO3{kjh}(3l-L1g6}O|ft({cT{BZ4y>9`Pt$JYF>cVbFXH zn2a!o1|CbpN0n)7Y5O`xSwwASTiiA9IF2{q{LB6T?u%o)W2f)?D1wqGd(~>X11`3^ zf5k7`9ls({F(U!rNVYo|Dr37E(u986@2+;2_PcI(_{ba$GNI38#3!TKF*kb7A?_OJ z6}r*1aabfuiImDmlAuYoq92Yu>a0v z$Wpq2wuR(fztT>=I9?=%qs6DG1gksVL|f8^2whEy{8{E~d$(vrLg|04IwQlPVe9R? zZ=q11Z}}tQrmbMV)p|Qi15JqE5to?8r)xrsl7$0S?;5&P6G{EXNcG}YZH zM0JavA7mQ}%F=OZ8 zr>GV`+t*X~J@o>-x}${kbdHu(c9u%>JuDZfyLaLj1iVF!O_27~2W!cLfEL+VrBt9k z3G*ORVg4z|#X!Q;LSgtGoebi0UD7*sUg5^*$|5fXE?; zd=&47yy48W@`nCjXnBLM=M-}GL#(sp{Q+bpYNbdVn7eVE)~Z+AdYot4URm-kRi*>Z z253zOs6P$0>4D(L9;kc2j5Txp_mCJWc(W+z)ve);r3qvbj6)|hFk@{p|Do7#YQd{R zvBw(l6L224R(07;@8XchO92Pc9bR0Niz?}w7wT>$B_F&#r(Ur@!^|=lMx(4{iOCC@r#A3I3zYy6*sI$aV2ce2vk|i%HfX#O-&Hff?R9qDRetqab;^Xw+Y6e z@*wx19jGx)(gxJ-*o`m<(n|9&6|tLXqN(YiR|IFZa$hHQAr$D*av)f2FON323d2gy zv?ZAA!$K2wsi42?tf_koQO*k@75;Lkm7!3yk`Mp9i`7Q)vpzA?zjZisQxVJh1vIGw z(Y)AI!sb)JDUvpy%GT24(+gx(xq)sG5(}xk7A`Lj@)JEC z`x9QIC+I9~2cx68r!8*=yj#l#7QPvPwI_y#v{k{_QJ9!Y=Dn!G+(+==jlO?_GT z#4w@rC5=Co(D)NS76h?XhfgfkqEKvXYbZ8pOvrgW&M7U|7sBq-aYzBQ@}=FU?**cl zX3^-=0E|A3mNuVG29_M@CU`dP zjftECAxQ<~f0LtQZ-x=4$mGx z;>IjR2?xtwid>A2cR?&G1ToGM+H_)#z6EAHd`t!YWQEFJ#9re%PEu*i>3OTA7c-`? z^RZ|@!RRPfE#e3|;S6kbZmWBLJI=nMa)LP2G96=X)pyXvSRq2X0Gl77=p@XD&|&Z* zCf1E8bi70Ep(ec1Nr`zD@FL@?-0z)d?% zh458iExQfz!mF~=bRS$oTWZX&eviO;okvL^ntL2Nnm-)N8d(gx1O98G#@)Q zK7_5@{|v=)e{M&|QC7oQmmR$p#UkY?co#C9PKl#r?w8l8Mz2N5NNeYyDT;=WX?a~5 zWJbN9j*NpvrkA49(oxHv$Twg=4dNF_?O~AW*xNGn}%+6iM6;VlS!oiBz zY=6duvE1Q2nu~q_DZmmMusWOxCWoVySD9f#JV$DW>9;7N0%eCOpW2yu$`BKG#9@gE zDw;Fd+)yc~QB)=hJvZpQu>@EDdJbY)J95+g=#LN?;X`SB8`dNV8%uv;y$)?(X?yfx zj*4Q?iiZ%%5F=dtAE0x^vD7vAb)z+*usNi*{0H=x;>DmLYr^1xQ8mebxF6tk?+1KR zo4OwW`Fuh+PmV5Rj?v>%*d^_AVJd;K0}UjC_L@9!Bo7%S&K!;G8QE_yd9<(+J_PGL z%f&hwjEi;h_A_pF0CllWwaUnfxRJRTHV$@XoI-IsnA+_ zqTr+h<<7^FlUnTov|%t(SXoD_-c=s^A$oiZ+h!>CU#x8~b1~>wTdhxk6$WDGCwQsk zhtd06$jY2)%LF^c>4CByXBX`%E-ze;grWSa0?q>ri~1YQS0NO3o@%l^zu>3Cm_>B^ z+Y~?jAq|pU8bHT$+faNSgG6DVyM-a~2^JrOsxs5J_`9Llub7B&dqm@8xY6Tdxg*HS z{*vUT-q(U8?09D_5hLq7R8d=}E?Vb|89*V66Mlk;fD!IO`>X zv+g5j9hT&*mq^Y!EWug*8H=ZcV}*;+J4ys+ErChAJMGL!p=`irq*~7AI!B5wR z%HG$;5&I~CQ2aD{nwTkl=tLwl-Ld^{3g>HhLbMix;0+k&8otBX66!%-6)WWtc*o!% zI}zRmU+qAN<6ZF8yX3pztK>Goi%8FvNFZ8HzS@ab1Ybovh@Xn)G6{SYoSdzpMlsiF zwwP+gK1)b0xoSDNDtT@Ld^E7vV!h78k#gTUmA@8B{#qJ@?bCL|nraF$$Yx|MdlE@N z2-Br0#NBYEu-GuBf6?5d!9S|d*M?@Xj$*N!5RYcDTjjf8vAyzLuvm^dpb;##U%qP= z%M}J#EVq&(rzjT7Ai-i!bg|e&@z=`F!e1G-%f#Vl<*)QkJBQ@2e#Ku)$zMw{99ia~ zcF1CFtA*Q%;xsawSq&=@1z+gA=i;)wjkt)ccDPy@TCI11*)kMNqOj|R3pP0pV!sUg zRTbF4Dei)9Twl&r;Ep%C*s|3G4w%^gKLme{+n{r?=#fZd+A%Z!G&ryxpIsv=m~<72 zL5$NaRpio}@kel$g#N-H{`{R=zU0rIiZA(d{g?dtOa3g47k|m0zvRz@*1!K>;m>C; z|6k9aJ#{1FS<~gdfBcb|gZOjE5&CT*XSa|y*IrG#=tN+=)7P~H}yyo*p4K4!PRotZ|^tzNr+?i?Op}@nANv$_53Qxi!3+r5l-i8$PHC9nB zJvBGhKLZ!8eGfFfNv*W=q*mJPvO%O=FDew$!jL>SSWEv8tbPZaPvniH zCEh^S_Dt-Qi>fW&7K(n)hKntM3x`^3>Bg@yj%OfDU>xUqp?{`tNC_2x8ltg9cwmkG z75^bZh2l@R403X7>14zNaR#nw9nl5F!eWR9#f~GD;_?EpVjPNQ22a}HFWA=>#yxn= z+ZYwZHEhsW%VcxkxC8G(Iqh9u8^le$)midlA*@V)0M)7|@f*s7FXkYG){jzR@}bhC zuQXZ-AGqEMW(Kc?4a(~>K+G1FDRXT@RD?7Q8NZNHMt(PjiJHpX>L|#H$-d27-z%8M7@s`kH z7`-h9>6OS0ZnIi-C5-G=;KnESmR7nGKmPx$&=Z z@4C@*7q+X=dqpO8qMStVZsLG*7EL!ZV4?hVzf8LK8RR8BeKPcbcekzr8gSm1o*sMb z?n^+KYTiYP&D+7L(f5+Qz#9g6PiUmDQ0R0&Po?lJZ>Z!`4}rBd3bkfg>R!G8?v{04 zg6Y!Zg78+#ita#}iesJZWJN+R1BKxB%`3uqtsm^7f}V8{@IJ|$;8w$(Vef~ecGd;a zec4i7(T>#7!ZdccurTZuSI;tYs)vcoXJPd?r6!g2>mc@&#PzS4@LQVbW4gT{TJ(vc z&=yy_2|Dw+0di#~{@?~iTtreD@_t~SeS2Xe5@t2^LbwfupG@f930|~=GS7tl{dBzO zRY7rNyyym~W|}-Z83EK!g6;hgnnq@(40cp(u%n$9X?AoKJYOK$(Pn5JV`Sv+ zvQVl9_X4|c^CCMo9?I6!G3euD37R4ljRjGS)jIcBqzK*LkCZ_N2qRiA$`$v-0Fz0V ziqg0S8&_a!<|OwSKtfhj!Mh?Mr-uMnFe3aZCC4fPZ`ndhKO-mrB}a8=PsldIFOdH& zS6m3PIyyR8tN2ny$4WSG5+eQ{ev|tpTDKYuB92RRWX~wxC>T$O3f8jYg>efn?lwIE z6A|n*^^&C&dAr`rL^ZHN;mq@fil2v&6A?;HehR7bxie>n%RaW2Zo?!8qH4vDO1
    A}Lk}p{Fb1}55 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 z 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 Z2D&wQE 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 B`D_3H literal 0 HcmV?d00001 diff --git a/mutilate.cc b/mutilate.cc index bca6a80..9a66694 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -21,6 +21,10 @@ #include #include + +#include "common.h" //for zstd +#include "zstd.h" //shippped with mutilate + #include "config.h" #ifdef HAVE_LIBZMQ @@ -798,20 +802,150 @@ int stick_this_thread_to_core(int core_id) { return pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset); } +bool hasEnding (string const &fullString, string const &ending) { + if (fullString.length() >= ending.length()) { + return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending)); + } else { + return false; + } +} + +static char *get_stream(ZSTD_DCtx* dctx, FILE *fin, size_t const buffInSize, void* const buffIn, size_t const buffOutSize, void* const buffOut) { + /* This loop assumes that the input file is one or more concatenated zstd + * streams. This example won't work if there is trailing non-zstd data at + * the end, but streaming decompression in general handles this case. + * ZSTD_decompressStream() returns 0 exactly when the frame is completed, + * and doesn't consume input after the frame. + */ + size_t const toRead = buffInSize; + size_t read; + size_t lastRet = 0; + int isEmpty = 1; + if ( (read = fread_orDie(buffIn, toRead, fin)) ) { + isEmpty = 0; + ZSTD_inBuffer input = { buffIn, read, 0 }; + /* Given a valid frame, zstd won't consume the last byte of the frame + * until it has flushed all of the decompressed data of the frame. + * Therefore, instead of checking if the return code is 0, we can + * decompress just check if input.pos < input.size. + */ + char *trace = (char*)malloc(buffOutSize*2); + memset(trace,0,buffOutSize+1); + size_t tracelen = buffOutSize+1; + size_t total = 0; + while (input.pos < input.size) { + ZSTD_outBuffer output = { buffOut, buffOutSize, 0 }; + /* The return code is zero if the frame is complete, but there may + * be multiple frames concatenated together. Zstd will automatically + * reset the context when a frame is complete. Still, calling + * ZSTD_DCtx_reset() can be useful to reset the context to a clean + * state, for instance if the last decompression call returned an + * error. + */ + + size_t const ret = ZSTD_decompressStream(dctx, &output , &input); + + if (output.pos + total > tracelen) { + trace = (char*)realloc(trace,(output.pos+total+1)); + tracelen = (output.pos+total+1); + } + strncat(trace,(const char*)buffOut,output.pos); + total += output.pos; + + lastRet = ret; + } + int idx = total; + while (trace[idx] != '\n') { + idx--; + } + trace[idx] = 0; + trace[idx+1] = 0; + return trace; + + } + + if (isEmpty) { + fprintf(stderr, "input is empty\n"); + return NULL; + } + + if (lastRet != 0) { + /* The last return value from ZSTD_decompressStream did not end on a + * frame, but we reached the end of the file! We assume this is an + * error, and the input was truncated. + */ + fprintf(stderr, "EOF before end of stream: %zu\n", lastRet); + exit(1); + } + return NULL; + +} + void* reader_thread(void *arg) { struct reader_data *rdata = (struct reader_data *) arg; ConcurrentQueue *trace_queue = (ConcurrentQueue*) rdata->trace_queue; - ifstream trace_file; - trace_file.open(rdata->trace_filename); - while (trace_file.good()) { - string line; - getline(trace_file,line); - trace_queue->enqueue(line); - } - string eof = "EOF"; - for (int i = 0; i < 1000; i++) { - trace_queue->enqueue(eof); + if (hasEnding(rdata->trace_filename,".zst")) { + //init + const char *filename = rdata->trace_filename.c_str(); + FILE* const fin = fopen_orDie(filename, "rb"); + size_t const buffInSize = ZSTD_DStreamInSize()*1000; + void* const buffIn = malloc_orDie(buffInSize); + size_t const buffOutSize = ZSTD_DStreamOutSize()*1000; + void* const buffOut = malloc_orDie(buffOutSize); + + ZSTD_DCtx* const dctx = ZSTD_createDCtx(); + CHECK(dctx != NULL, "ZSTD_createDCtx() failed!"); + //char *leftover = malloc(buffOutSize); + //memset(leftover,0,buffOutSize); + //char *trace = (char*)decompress(filename); + uint64_t nwrites = 0; + uint64_t n = 0; + char *trace = get_stream(dctx, fin, buffInSize, buffIn, buffOutSize, buffOut); + while (trace != NULL) { + char *ftrace = trace; + char *line = NULL; + char *line_p = (char*)calloc(2048,sizeof(char)); + while ((line = strsep(&trace,"\n"))) { + if (strlen(line) < 2048) { + strncpy(line_p,line,strlen(line)); + } + string full_line(line); + trace_queue->enqueue(full_line); + n++; + if (n % 1000000 == 0) fprintf(stderr,"decompressed requests: %lu, writes: %lu\n",n,nwrites); + + } + free(line_p); + free(ftrace); + trace = get_stream(dctx, fin, buffInSize, buffIn, buffOutSize, buffOut); + } + string eof = "EOF"; + for (int i = 0; i < 1000; i++) { + trace_queue->enqueue(eof); + } + if (trace) { + free(trace); + } + ZSTD_freeDCtx(dctx); + fclose_orDie(fin); + free(buffIn); + free(buffOut); + + + } else { + + ifstream trace_file; + trace_file.open(rdata->trace_filename); + while (trace_file.good()) { + string line; + getline(trace_file,line); + trace_queue->enqueue(line); + } + string eof = "EOF"; + for (int i = 0; i < 1000; i++) { + trace_queue->enqueue(eof); + } } return NULL; } diff --git a/zstd.h b/zstd.h new file mode 100644 index 0000000..222339d --- /dev/null +++ b/zstd.h @@ -0,0 +1,2450 @@ +/* + * Copyright (c) 2016-2021, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ +#if defined (__cplusplus) +extern "C" { +#endif + +#ifndef ZSTD_H_235446 +#define ZSTD_H_235446 + +/* ====== Dependency ======*/ +#include /* INT_MAX */ +#include /* size_t */ + + +/* ===== ZSTDLIB_API : control library symbols visibility ===== */ +#ifndef ZSTDLIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define ZSTDLIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define ZSTDLIB_VISIBILITY +# endif +#endif +#if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1) +# define ZSTDLIB_API __declspec(dllexport) ZSTDLIB_VISIBILITY +#elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1) +# define ZSTDLIB_API __declspec(dllimport) ZSTDLIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define ZSTDLIB_API ZSTDLIB_VISIBILITY +#endif + + +/******************************************************************************* + Introduction + + zstd, short for Zstandard, is a fast lossless compression algorithm, targeting + real-time compression scenarios at zlib-level and better compression ratios. + The zstd compression library provides in-memory compression and decompression + functions. + + The library supports regular compression levels from 1 up to ZSTD_maxCLevel(), + which is currently 22. Levels >= 20, labeled `--ultra`, should be used with + caution, as they require more memory. The library also offers negative + compression levels, which extend the range of speed vs. ratio preferences. + The lower the level, the faster the speed (at the cost of compression). + + Compression can be done in: + - a single step (described as Simple API) + - a single step, reusing a context (described as Explicit context) + - unbounded multiple steps (described as Streaming compression) + + The compression ratio achievable on small data can be highly improved using + a dictionary. Dictionary compression can be performed in: + - a single step (described as Simple dictionary API) + - a single step, reusing a dictionary (described as Bulk-processing + dictionary API) + + Advanced experimental functions can be accessed using + `#define ZSTD_STATIC_LINKING_ONLY` before including zstd.h. + + Advanced experimental APIs should never be used with a dynamically-linked + library. They are not "stable"; their definitions or signatures may change in + the future. Only static linking is allowed. +*******************************************************************************/ + +/*------ Version ------*/ +#define ZSTD_VERSION_MAJOR 1 +#define ZSTD_VERSION_MINOR 4 +#define ZSTD_VERSION_RELEASE 9 +#define ZSTD_VERSION_NUMBER (ZSTD_VERSION_MAJOR *100*100 + ZSTD_VERSION_MINOR *100 + ZSTD_VERSION_RELEASE) + +/*! ZSTD_versionNumber() : + * Return runtime library version, the value is (MAJOR*100*100 + MINOR*100 + RELEASE). */ +ZSTDLIB_API unsigned ZSTD_versionNumber(void); + +#define ZSTD_LIB_VERSION ZSTD_VERSION_MAJOR.ZSTD_VERSION_MINOR.ZSTD_VERSION_RELEASE +#define ZSTD_QUOTE(str) #str +#define ZSTD_EXPAND_AND_QUOTE(str) ZSTD_QUOTE(str) +#define ZSTD_VERSION_STRING ZSTD_EXPAND_AND_QUOTE(ZSTD_LIB_VERSION) + +/*! ZSTD_versionString() : + * Return runtime library version, like "1.4.5". Requires v1.3.0+. */ +ZSTDLIB_API const char* ZSTD_versionString(void); + +/* ************************************* + * Default constant + ***************************************/ +#ifndef ZSTD_CLEVEL_DEFAULT +# define ZSTD_CLEVEL_DEFAULT 3 +#endif + +/* ************************************* + * Constants + ***************************************/ + +/* All magic numbers are supposed read/written to/from files/memory using little-endian convention */ +#define ZSTD_MAGICNUMBER 0xFD2FB528 /* valid since v0.8.0 */ +#define ZSTD_MAGIC_DICTIONARY 0xEC30A437 /* valid since v0.7.0 */ +#define ZSTD_MAGIC_SKIPPABLE_START 0x184D2A50 /* all 16 values, from 0x184D2A50 to 0x184D2A5F, signal the beginning of a skippable frame */ +#define ZSTD_MAGIC_SKIPPABLE_MASK 0xFFFFFFF0 + +#define ZSTD_BLOCKSIZELOG_MAX 17 +#define ZSTD_BLOCKSIZE_MAX (1<= `ZSTD_compressBound(srcSize)`. + * @return : compressed size written into `dst` (<= `dstCapacity), + * or an error code if it fails (which can be tested using ZSTD_isError()). */ +ZSTDLIB_API size_t ZSTD_compress( void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + int compressionLevel); + +/*! ZSTD_decompress() : + * `compressedSize` : must be the _exact_ size of some number of compressed and/or skippable frames. + * `dstCapacity` is an upper bound of originalSize to regenerate. + * If user cannot imply a maximum upper bound, it's better to use streaming mode to decompress data. + * @return : the number of bytes decompressed into `dst` (<= `dstCapacity`), + * or an errorCode if it fails (which can be tested using ZSTD_isError()). */ +ZSTDLIB_API size_t ZSTD_decompress( void* dst, size_t dstCapacity, + const void* src, size_t compressedSize); + +/*! ZSTD_getFrameContentSize() : requires v1.3.0+ + * `src` should point to the start of a ZSTD encoded frame. + * `srcSize` must be at least as large as the frame header. + * hint : any size >= `ZSTD_frameHeaderSize_max` is large enough. + * @return : - decompressed size of `src` frame content, if known + * - ZSTD_CONTENTSIZE_UNKNOWN if the size cannot be determined + * - ZSTD_CONTENTSIZE_ERROR if an error occurred (e.g. invalid magic number, srcSize too small) + * note 1 : a 0 return value means the frame is valid but "empty". + * note 2 : decompressed size is an optional field, it may not be present, typically in streaming mode. + * When `return==ZSTD_CONTENTSIZE_UNKNOWN`, data to decompress could be any size. + * In which case, it's necessary to use streaming mode to decompress data. + * Optionally, application can rely on some implicit limit, + * as ZSTD_decompress() only needs an upper bound of decompressed size. + * (For example, data could be necessarily cut into blocks <= 16 KB). + * note 3 : decompressed size is always present when compression is completed using single-pass functions, + * such as ZSTD_compress(), ZSTD_compressCCtx() ZSTD_compress_usingDict() or ZSTD_compress_usingCDict(). + * note 4 : decompressed size can be very large (64-bits value), + * potentially larger than what local system can handle as a single memory segment. + * In which case, it's necessary to use streaming mode to decompress data. + * note 5 : If source is untrusted, decompressed size could be wrong or intentionally modified. + * Always ensure return value fits within application's authorized limits. + * Each application can set its own limits. + * note 6 : This function replaces ZSTD_getDecompressedSize() */ +#define ZSTD_CONTENTSIZE_UNKNOWN (0ULL - 1) +#define ZSTD_CONTENTSIZE_ERROR (0ULL - 2) +ZSTDLIB_API unsigned long long ZSTD_getFrameContentSize(const void *src, size_t srcSize); + +/*! ZSTD_getDecompressedSize() : + * NOTE: This function is now obsolete, in favor of ZSTD_getFrameContentSize(). + * Both functions work the same way, but ZSTD_getDecompressedSize() blends + * "empty", "unknown" and "error" results to the same return value (0), + * while ZSTD_getFrameContentSize() gives them separate return values. + * @return : decompressed size of `src` frame content _if known and not empty_, 0 otherwise. */ +ZSTDLIB_API unsigned long long ZSTD_getDecompressedSize(const void* src, size_t srcSize); + +/*! ZSTD_findFrameCompressedSize() : + * `src` should point to the start of a ZSTD frame or skippable frame. + * `srcSize` must be >= first frame size + * @return : the compressed size of the first frame starting at `src`, + * suitable to pass as `srcSize` to `ZSTD_decompress` or similar, + * or an error code if input is invalid */ +ZSTDLIB_API size_t ZSTD_findFrameCompressedSize(const void* src, size_t srcSize); + + +/*====== Helper functions ======*/ +#define ZSTD_COMPRESSBOUND(srcSize) ((srcSize) + ((srcSize)>>8) + (((srcSize) < (128<<10)) ? (((128<<10) - (srcSize)) >> 11) /* margin, from 64 to 0 */ : 0)) /* this formula ensures that bound(A) + bound(B) <= bound(A+B) as long as A and B >= 128 KB */ +ZSTDLIB_API size_t ZSTD_compressBound(size_t srcSize); /*!< maximum compressed size in worst case single-pass scenario */ +ZSTDLIB_API unsigned ZSTD_isError(size_t code); /*!< tells if a `size_t` function result is an error code */ +ZSTDLIB_API const char* ZSTD_getErrorName(size_t code); /*!< provides readable string from an error code */ +ZSTDLIB_API int ZSTD_minCLevel(void); /*!< minimum negative compression level allowed */ +ZSTDLIB_API int ZSTD_maxCLevel(void); /*!< maximum compression level available */ + + +/*************************************** +* Explicit context +***************************************/ +/*= Compression context + * When compressing many times, + * it is recommended to allocate a context just once, + * and re-use it for each successive compression operation. + * This will make workload friendlier for system's memory. + * Note : re-using context is just a speed / resource optimization. + * It doesn't change the compression ratio, which remains identical. + * Note 2 : In multi-threaded environments, + * use one different context per thread for parallel execution. + */ +typedef struct ZSTD_CCtx_s ZSTD_CCtx; +ZSTDLIB_API ZSTD_CCtx* ZSTD_createCCtx(void); +ZSTDLIB_API size_t ZSTD_freeCCtx(ZSTD_CCtx* cctx); + +/*! ZSTD_compressCCtx() : + * Same as ZSTD_compress(), using an explicit ZSTD_CCtx. + * Important : in order to behave similarly to `ZSTD_compress()`, + * this function compresses at requested compression level, + * __ignoring any other parameter__ . + * If any advanced parameter was set using the advanced API, + * they will all be reset. Only `compressionLevel` remains. + */ +ZSTDLIB_API size_t ZSTD_compressCCtx(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + int compressionLevel); + +/*= Decompression context + * When decompressing many times, + * it is recommended to allocate a context only once, + * and re-use it for each successive compression operation. + * This will make workload friendlier for system's memory. + * Use one context per thread for parallel execution. */ +typedef struct ZSTD_DCtx_s ZSTD_DCtx; +ZSTDLIB_API ZSTD_DCtx* ZSTD_createDCtx(void); +ZSTDLIB_API size_t ZSTD_freeDCtx(ZSTD_DCtx* dctx); + +/*! ZSTD_decompressDCtx() : + * Same as ZSTD_decompress(), + * requires an allocated ZSTD_DCtx. + * Compatible with sticky parameters. + */ +ZSTDLIB_API size_t ZSTD_decompressDCtx(ZSTD_DCtx* dctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize); + + +/*************************************** +* Advanced compression API +***************************************/ + +/* API design : + * Parameters are pushed one by one into an existing context, + * using ZSTD_CCtx_set*() functions. + * Pushed parameters are sticky : they are valid for next compressed frame, and any subsequent frame. + * "sticky" parameters are applicable to `ZSTD_compress2()` and `ZSTD_compressStream*()` ! + * __They do not apply to "simple" one-shot variants such as ZSTD_compressCCtx()__ . + * + * It's possible to reset all parameters to "default" using ZSTD_CCtx_reset(). + * + * This API supercedes all other "advanced" API entry points in the experimental section. + * In the future, we expect to remove from experimental API entry points which are redundant with this API. + */ + + +/* Compression strategies, listed from fastest to strongest */ +typedef enum { ZSTD_fast=1, + ZSTD_dfast=2, + ZSTD_greedy=3, + ZSTD_lazy=4, + ZSTD_lazy2=5, + ZSTD_btlazy2=6, + ZSTD_btopt=7, + ZSTD_btultra=8, + ZSTD_btultra2=9 + /* note : new strategies _might_ be added in the future. + Only the order (from fast to strong) is guaranteed */ +} ZSTD_strategy; + + +typedef enum { + + /* compression parameters + * Note: When compressing with a ZSTD_CDict these parameters are superseded + * by the parameters used to construct the ZSTD_CDict. + * See ZSTD_CCtx_refCDict() for more info (superseded-by-cdict). */ + ZSTD_c_compressionLevel=100, /* Set compression parameters according to pre-defined cLevel table. + * Note that exact compression parameters are dynamically determined, + * depending on both compression level and srcSize (when known). + * Default level is ZSTD_CLEVEL_DEFAULT==3. + * Special: value 0 means default, which is controlled by ZSTD_CLEVEL_DEFAULT. + * Note 1 : it's possible to pass a negative compression level. + * Note 2 : setting a level does not automatically set all other compression parameters + * to default. Setting this will however eventually dynamically impact the compression + * parameters which have not been manually set. The manually set + * ones will 'stick'. */ + /* Advanced compression parameters : + * It's possible to pin down compression parameters to some specific values. + * In which case, these values are no longer dynamically selected by the compressor */ + ZSTD_c_windowLog=101, /* Maximum allowed back-reference distance, expressed as power of 2. + * This will set a memory budget for streaming decompression, + * with larger values requiring more memory + * and typically compressing more. + * Must be clamped between ZSTD_WINDOWLOG_MIN and ZSTD_WINDOWLOG_MAX. + * Special: value 0 means "use default windowLog". + * Note: Using a windowLog greater than ZSTD_WINDOWLOG_LIMIT_DEFAULT + * requires explicitly allowing such size at streaming decompression stage. */ + ZSTD_c_hashLog=102, /* Size of the initial probe table, as a power of 2. + * Resulting memory usage is (1 << (hashLog+2)). + * Must be clamped between ZSTD_HASHLOG_MIN and ZSTD_HASHLOG_MAX. + * Larger tables improve compression ratio of strategies <= dFast, + * and improve speed of strategies > dFast. + * Special: value 0 means "use default hashLog". */ + ZSTD_c_chainLog=103, /* Size of the multi-probe search table, as a power of 2. + * Resulting memory usage is (1 << (chainLog+2)). + * Must be clamped between ZSTD_CHAINLOG_MIN and ZSTD_CHAINLOG_MAX. + * Larger tables result in better and slower compression. + * This parameter is useless for "fast" strategy. + * It's still useful when using "dfast" strategy, + * in which case it defines a secondary probe table. + * Special: value 0 means "use default chainLog". */ + ZSTD_c_searchLog=104, /* Number of search attempts, as a power of 2. + * More attempts result in better and slower compression. + * This parameter is useless for "fast" and "dFast" strategies. + * Special: value 0 means "use default searchLog". */ + ZSTD_c_minMatch=105, /* Minimum size of searched matches. + * Note that Zstandard can still find matches of smaller size, + * it just tweaks its search algorithm to look for this size and larger. + * Larger values increase compression and decompression speed, but decrease ratio. + * Must be clamped between ZSTD_MINMATCH_MIN and ZSTD_MINMATCH_MAX. + * Note that currently, for all strategies < btopt, effective minimum is 4. + * , for all strategies > fast, effective maximum is 6. + * Special: value 0 means "use default minMatchLength". */ + ZSTD_c_targetLength=106, /* Impact of this field depends on strategy. + * For strategies btopt, btultra & btultra2: + * Length of Match considered "good enough" to stop search. + * Larger values make compression stronger, and slower. + * For strategy fast: + * Distance between match sampling. + * Larger values make compression faster, and weaker. + * Special: value 0 means "use default targetLength". */ + ZSTD_c_strategy=107, /* See ZSTD_strategy enum definition. + * The higher the value of selected strategy, the more complex it is, + * resulting in stronger and slower compression. + * Special: value 0 means "use default strategy". */ + + /* LDM mode parameters */ + ZSTD_c_enableLongDistanceMatching=160, /* Enable long distance matching. + * This parameter is designed to improve compression ratio + * for large inputs, by finding large matches at long distance. + * It increases memory usage and window size. + * Note: enabling this parameter increases default ZSTD_c_windowLog to 128 MB + * except when expressly set to a different value. + * Note: will be enabled by default if ZSTD_c_windowLog >= 128 MB and + * compression strategy >= ZSTD_btopt (== compression level 16+) */ + ZSTD_c_ldmHashLog=161, /* Size of the table for long distance matching, as a power of 2. + * Larger values increase memory usage and compression ratio, + * but decrease compression speed. + * Must be clamped between ZSTD_HASHLOG_MIN and ZSTD_HASHLOG_MAX + * default: windowlog - 7. + * Special: value 0 means "automatically determine hashlog". */ + ZSTD_c_ldmMinMatch=162, /* Minimum match size for long distance matcher. + * Larger/too small values usually decrease compression ratio. + * Must be clamped between ZSTD_LDM_MINMATCH_MIN and ZSTD_LDM_MINMATCH_MAX. + * Special: value 0 means "use default value" (default: 64). */ + ZSTD_c_ldmBucketSizeLog=163, /* Log size of each bucket in the LDM hash table for collision resolution. + * Larger values improve collision resolution but decrease compression speed. + * The maximum value is ZSTD_LDM_BUCKETSIZELOG_MAX. + * Special: value 0 means "use default value" (default: 3). */ + ZSTD_c_ldmHashRateLog=164, /* Frequency of inserting/looking up entries into the LDM hash table. + * Must be clamped between 0 and (ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN). + * Default is MAX(0, (windowLog - ldmHashLog)), optimizing hash table usage. + * Larger values improve compression speed. + * Deviating far from default value will likely result in a compression ratio decrease. + * Special: value 0 means "automatically determine hashRateLog". */ + + /* frame parameters */ + ZSTD_c_contentSizeFlag=200, /* Content size will be written into frame header _whenever known_ (default:1) + * Content size must be known at the beginning of compression. + * This is automatically the case when using ZSTD_compress2(), + * For streaming scenarios, content size must be provided with ZSTD_CCtx_setPledgedSrcSize() */ + ZSTD_c_checksumFlag=201, /* A 32-bits checksum of content is written at end of frame (default:0) */ + ZSTD_c_dictIDFlag=202, /* When applicable, dictionary's ID is written into frame header (default:1) */ + + /* multi-threading parameters */ + /* These parameters are only active if multi-threading is enabled (compiled with build macro ZSTD_MULTITHREAD). + * Otherwise, trying to set any other value than default (0) will be a no-op and return an error. + * In a situation where it's unknown if the linked library supports multi-threading or not, + * setting ZSTD_c_nbWorkers to any value >= 1 and consulting the return value provides a quick way to check this property. + */ + ZSTD_c_nbWorkers=400, /* Select how many threads will be spawned to compress in parallel. + * When nbWorkers >= 1, triggers asynchronous mode when invoking ZSTD_compressStream*() : + * ZSTD_compressStream*() consumes input and flush output if possible, but immediately gives back control to caller, + * while compression is performed in parallel, within worker thread(s). + * (note : a strong exception to this rule is when first invocation of ZSTD_compressStream2() sets ZSTD_e_end : + * in which case, ZSTD_compressStream2() delegates to ZSTD_compress2(), which is always a blocking call). + * More workers improve speed, but also increase memory usage. + * Default value is `0`, aka "single-threaded mode" : no worker is spawned, + * compression is performed inside Caller's thread, and all invocations are blocking */ + ZSTD_c_jobSize=401, /* Size of a compression job. This value is enforced only when nbWorkers >= 1. + * Each compression job is completed in parallel, so this value can indirectly impact the nb of active threads. + * 0 means default, which is dynamically determined based on compression parameters. + * Job size must be a minimum of overlap size, or 1 MB, whichever is largest. + * The minimum size is automatically and transparently enforced. */ + ZSTD_c_overlapLog=402, /* Control the overlap size, as a fraction of window size. + * The overlap size is an amount of data reloaded from previous job at the beginning of a new job. + * It helps preserve compression ratio, while each job is compressed in parallel. + * This value is enforced only when nbWorkers >= 1. + * Larger values increase compression ratio, but decrease speed. + * Possible values range from 0 to 9 : + * - 0 means "default" : value will be determined by the library, depending on strategy + * - 1 means "no overlap" + * - 9 means "full overlap", using a full window size. + * Each intermediate rank increases/decreases load size by a factor 2 : + * 9: full window; 8: w/2; 7: w/4; 6: w/8; 5:w/16; 4: w/32; 3:w/64; 2:w/128; 1:no overlap; 0:default + * default value varies between 6 and 9, depending on strategy */ + + /* note : additional experimental parameters are also available + * within the experimental section of the API. + * At the time of this writing, they include : + * ZSTD_c_rsyncable + * ZSTD_c_format + * ZSTD_c_forceMaxWindow + * ZSTD_c_forceAttachDict + * ZSTD_c_literalCompressionMode + * ZSTD_c_targetCBlockSize + * ZSTD_c_srcSizeHint + * ZSTD_c_enableDedicatedDictSearch + * ZSTD_c_stableInBuffer + * ZSTD_c_stableOutBuffer + * ZSTD_c_blockDelimiters + * ZSTD_c_validateSequences + * Because they are not stable, it's necessary to define ZSTD_STATIC_LINKING_ONLY to access them. + * note : never ever use experimentalParam? names directly; + * also, the enums values themselves are unstable and can still change. + */ + ZSTD_c_experimentalParam1=500, + ZSTD_c_experimentalParam2=10, + ZSTD_c_experimentalParam3=1000, + ZSTD_c_experimentalParam4=1001, + ZSTD_c_experimentalParam5=1002, + ZSTD_c_experimentalParam6=1003, + ZSTD_c_experimentalParam7=1004, + ZSTD_c_experimentalParam8=1005, + ZSTD_c_experimentalParam9=1006, + ZSTD_c_experimentalParam10=1007, + ZSTD_c_experimentalParam11=1008, + ZSTD_c_experimentalParam12=1009 +} ZSTD_cParameter; + +typedef struct { + size_t error; + int lowerBound; + int upperBound; +} ZSTD_bounds; + +/*! ZSTD_cParam_getBounds() : + * All parameters must belong to an interval with lower and upper bounds, + * otherwise they will either trigger an error or be automatically clamped. + * @return : a structure, ZSTD_bounds, which contains + * - an error status field, which must be tested using ZSTD_isError() + * - lower and upper bounds, both inclusive + */ +ZSTDLIB_API ZSTD_bounds ZSTD_cParam_getBounds(ZSTD_cParameter cParam); + +/*! ZSTD_CCtx_setParameter() : + * Set one compression parameter, selected by enum ZSTD_cParameter. + * All parameters have valid bounds. Bounds can be queried using ZSTD_cParam_getBounds(). + * Providing a value beyond bound will either clamp it, or trigger an error (depending on parameter). + * Setting a parameter is generally only possible during frame initialization (before starting compression). + * Exception : when using multi-threading mode (nbWorkers >= 1), + * the following parameters can be updated _during_ compression (within same frame): + * => compressionLevel, hashLog, chainLog, searchLog, minMatch, targetLength and strategy. + * new parameters will be active for next job only (after a flush()). + * @return : an error code (which can be tested using ZSTD_isError()). + */ +ZSTDLIB_API size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, int value); + +/*! ZSTD_CCtx_setPledgedSrcSize() : + * Total input data size to be compressed as a single frame. + * Value will be written in frame header, unless if explicitly forbidden using ZSTD_c_contentSizeFlag. + * This value will also be controlled at end of frame, and trigger an error if not respected. + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + * Note 1 : pledgedSrcSize==0 actually means zero, aka an empty frame. + * In order to mean "unknown content size", pass constant ZSTD_CONTENTSIZE_UNKNOWN. + * ZSTD_CONTENTSIZE_UNKNOWN is default value for any new frame. + * Note 2 : pledgedSrcSize is only valid once, for the next frame. + * It's discarded at the end of the frame, and replaced by ZSTD_CONTENTSIZE_UNKNOWN. + * Note 3 : Whenever all input data is provided and consumed in a single round, + * for example with ZSTD_compress2(), + * or invoking immediately ZSTD_compressStream2(,,,ZSTD_e_end), + * this value is automatically overridden by srcSize instead. + */ +ZSTDLIB_API size_t ZSTD_CCtx_setPledgedSrcSize(ZSTD_CCtx* cctx, unsigned long long pledgedSrcSize); + +typedef enum { + ZSTD_reset_session_only = 1, + ZSTD_reset_parameters = 2, + ZSTD_reset_session_and_parameters = 3 +} ZSTD_ResetDirective; + +/*! ZSTD_CCtx_reset() : + * There are 2 different things that can be reset, independently or jointly : + * - The session : will stop compressing current frame, and make CCtx ready to start a new one. + * Useful after an error, or to interrupt any ongoing compression. + * Any internal data not yet flushed is cancelled. + * Compression parameters and dictionary remain unchanged. + * They will be used to compress next frame. + * Resetting session never fails. + * - The parameters : changes all parameters back to "default". + * This removes any reference to any dictionary too. + * Parameters can only be changed between 2 sessions (i.e. no compression is currently ongoing) + * otherwise the reset fails, and function returns an error value (which can be tested using ZSTD_isError()) + * - Both : similar to resetting the session, followed by resetting parameters. + */ +ZSTDLIB_API size_t ZSTD_CCtx_reset(ZSTD_CCtx* cctx, ZSTD_ResetDirective reset); + +/*! ZSTD_compress2() : + * Behave the same as ZSTD_compressCCtx(), but compression parameters are set using the advanced API. + * ZSTD_compress2() always starts a new frame. + * Should cctx hold data from a previously unfinished frame, everything about it is forgotten. + * - Compression parameters are pushed into CCtx before starting compression, using ZSTD_CCtx_set*() + * - The function is always blocking, returns when compression is completed. + * Hint : compression runs faster if `dstCapacity` >= `ZSTD_compressBound(srcSize)`. + * @return : compressed size written into `dst` (<= `dstCapacity), + * or an error code if it fails (which can be tested using ZSTD_isError()). + */ +ZSTDLIB_API size_t ZSTD_compress2( ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize); + + +/*************************************** +* Advanced decompression API +***************************************/ + +/* The advanced API pushes parameters one by one into an existing DCtx context. + * Parameters are sticky, and remain valid for all following frames + * using the same DCtx context. + * It's possible to reset parameters to default values using ZSTD_DCtx_reset(). + * Note : This API is compatible with existing ZSTD_decompressDCtx() and ZSTD_decompressStream(). + * Therefore, no new decompression function is necessary. + */ + +typedef enum { + + ZSTD_d_windowLogMax=100, /* Select a size limit (in power of 2) beyond which + * the streaming API will refuse to allocate memory buffer + * in order to protect the host from unreasonable memory requirements. + * This parameter is only useful in streaming mode, since no internal buffer is allocated in single-pass mode. + * By default, a decompression context accepts window sizes <= (1 << ZSTD_WINDOWLOG_LIMIT_DEFAULT). + * Special: value 0 means "use default maximum windowLog". */ + + /* note : additional experimental parameters are also available + * within the experimental section of the API. + * At the time of this writing, they include : + * ZSTD_d_format + * ZSTD_d_stableOutBuffer + * ZSTD_d_forceIgnoreChecksum + * ZSTD_d_refMultipleDDicts + * Because they are not stable, it's necessary to define ZSTD_STATIC_LINKING_ONLY to access them. + * note : never ever use experimentalParam? names directly + */ + ZSTD_d_experimentalParam1=1000, + ZSTD_d_experimentalParam2=1001, + ZSTD_d_experimentalParam3=1002, + ZSTD_d_experimentalParam4=1003 + +} ZSTD_dParameter; + +/*! ZSTD_dParam_getBounds() : + * All parameters must belong to an interval with lower and upper bounds, + * otherwise they will either trigger an error or be automatically clamped. + * @return : a structure, ZSTD_bounds, which contains + * - an error status field, which must be tested using ZSTD_isError() + * - both lower and upper bounds, inclusive + */ +ZSTDLIB_API ZSTD_bounds ZSTD_dParam_getBounds(ZSTD_dParameter dParam); + +/*! ZSTD_DCtx_setParameter() : + * Set one compression parameter, selected by enum ZSTD_dParameter. + * All parameters have valid bounds. Bounds can be queried using ZSTD_dParam_getBounds(). + * Providing a value beyond bound will either clamp it, or trigger an error (depending on parameter). + * Setting a parameter is only possible during frame initialization (before starting decompression). + * @return : 0, or an error code (which can be tested using ZSTD_isError()). + */ +ZSTDLIB_API size_t ZSTD_DCtx_setParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int value); + +/*! ZSTD_DCtx_reset() : + * Return a DCtx to clean state. + * Session and parameters can be reset jointly or separately. + * Parameters can only be reset when no active frame is being decompressed. + * @return : 0, or an error code, which can be tested with ZSTD_isError() + */ +ZSTDLIB_API size_t ZSTD_DCtx_reset(ZSTD_DCtx* dctx, ZSTD_ResetDirective reset); + + +/**************************** +* Streaming +****************************/ + +typedef struct ZSTD_inBuffer_s { + const void* src; /**< start of input buffer */ + size_t size; /**< size of input buffer */ + size_t pos; /**< position where reading stopped. Will be updated. Necessarily 0 <= pos <= size */ +} ZSTD_inBuffer; + +typedef struct ZSTD_outBuffer_s { + void* dst; /**< start of output buffer */ + size_t size; /**< size of output buffer */ + size_t pos; /**< position where writing stopped. Will be updated. Necessarily 0 <= pos <= size */ +} ZSTD_outBuffer; + + + +/*-*********************************************************************** +* Streaming compression - HowTo +* +* A ZSTD_CStream object is required to track streaming operation. +* Use ZSTD_createCStream() and ZSTD_freeCStream() to create/release resources. +* ZSTD_CStream objects can be reused multiple times on consecutive compression operations. +* It is recommended to re-use ZSTD_CStream since it will play nicer with system's memory, by re-using already allocated memory. +* +* For parallel execution, use one separate ZSTD_CStream per thread. +* +* note : since v1.3.0, ZSTD_CStream and ZSTD_CCtx are the same thing. +* +* Parameters are sticky : when starting a new compression on the same context, +* it will re-use the same sticky parameters as previous compression session. +* When in doubt, it's recommended to fully initialize the context before usage. +* Use ZSTD_CCtx_reset() to reset the context and ZSTD_CCtx_setParameter(), +* ZSTD_CCtx_setPledgedSrcSize(), or ZSTD_CCtx_loadDictionary() and friends to +* set more specific parameters, the pledged source size, or load a dictionary. +* +* Use ZSTD_compressStream2() with ZSTD_e_continue as many times as necessary to +* consume input stream. The function will automatically update both `pos` +* fields within `input` and `output`. +* Note that the function may not consume the entire input, for example, because +* the output buffer is already full, in which case `input.pos < input.size`. +* The caller must check if input has been entirely consumed. +* If not, the caller must make some room to receive more compressed data, +* and then present again remaining input data. +* note: ZSTD_e_continue is guaranteed to make some forward progress when called, +* but doesn't guarantee maximal forward progress. This is especially relevant +* when compressing with multiple threads. The call won't block if it can +* consume some input, but if it can't it will wait for some, but not all, +* output to be flushed. +* @return : provides a minimum amount of data remaining to be flushed from internal buffers +* or an error code, which can be tested using ZSTD_isError(). +* +* At any moment, it's possible to flush whatever data might remain stuck within internal buffer, +* using ZSTD_compressStream2() with ZSTD_e_flush. `output->pos` will be updated. +* Note that, if `output->size` is too small, a single invocation with ZSTD_e_flush might not be enough (return code > 0). +* In which case, make some room to receive more compressed data, and call again ZSTD_compressStream2() with ZSTD_e_flush. +* You must continue calling ZSTD_compressStream2() with ZSTD_e_flush until it returns 0, at which point you can change the +* operation. +* note: ZSTD_e_flush will flush as much output as possible, meaning when compressing with multiple threads, it will +* block until the flush is complete or the output buffer is full. +* @return : 0 if internal buffers are entirely flushed, +* >0 if some data still present within internal buffer (the value is minimal estimation of remaining size), +* or an error code, which can be tested using ZSTD_isError(). +* +* Calling ZSTD_compressStream2() with ZSTD_e_end instructs to finish a frame. +* It will perform a flush and write frame epilogue. +* The epilogue is required for decoders to consider a frame completed. +* flush operation is the same, and follows same rules as calling ZSTD_compressStream2() with ZSTD_e_flush. +* You must continue calling ZSTD_compressStream2() with ZSTD_e_end until it returns 0, at which point you are free to +* start a new frame. +* note: ZSTD_e_end will flush as much output as possible, meaning when compressing with multiple threads, it will +* block until the flush is complete or the output buffer is full. +* @return : 0 if frame fully completed and fully flushed, +* >0 if some data still present within internal buffer (the value is minimal estimation of remaining size), +* or an error code, which can be tested using ZSTD_isError(). +* +* *******************************************************************/ + +typedef ZSTD_CCtx ZSTD_CStream; /**< CCtx and CStream are now effectively same object (>= v1.3.0) */ + /* Continue to distinguish them for compatibility with older versions <= v1.2.0 */ +/*===== ZSTD_CStream management functions =====*/ +ZSTDLIB_API ZSTD_CStream* ZSTD_createCStream(void); +ZSTDLIB_API size_t ZSTD_freeCStream(ZSTD_CStream* zcs); + +/*===== Streaming compression functions =====*/ +typedef enum { + ZSTD_e_continue=0, /* collect more data, encoder decides when to output compressed result, for optimal compression ratio */ + ZSTD_e_flush=1, /* flush any data provided so far, + * it creates (at least) one new block, that can be decoded immediately on reception; + * frame will continue: any future data can still reference previously compressed data, improving compression. + * note : multithreaded compression will block to flush as much output as possible. */ + ZSTD_e_end=2 /* flush any remaining data _and_ close current frame. + * note that frame is only closed after compressed data is fully flushed (return value == 0). + * After that point, any additional data starts a new frame. + * note : each frame is independent (does not reference any content from previous frame). + : note : multithreaded compression will block to flush as much output as possible. */ +} ZSTD_EndDirective; + +/*! ZSTD_compressStream2() : + * Behaves about the same as ZSTD_compressStream, with additional control on end directive. + * - Compression parameters are pushed into CCtx before starting compression, using ZSTD_CCtx_set*() + * - Compression parameters cannot be changed once compression is started (save a list of exceptions in multi-threading mode) + * - output->pos must be <= dstCapacity, input->pos must be <= srcSize + * - output->pos and input->pos will be updated. They are guaranteed to remain below their respective limit. + * - endOp must be a valid directive + * - When nbWorkers==0 (default), function is blocking : it completes its job before returning to caller. + * - When nbWorkers>=1, function is non-blocking : it copies a portion of input, distributes jobs to internal worker threads, flush to output whatever is available, + * and then immediately returns, just indicating that there is some data remaining to be flushed. + * The function nonetheless guarantees forward progress : it will return only after it reads or write at least 1+ byte. + * - Exception : if the first call requests a ZSTD_e_end directive and provides enough dstCapacity, the function delegates to ZSTD_compress2() which is always blocking. + * - @return provides a minimum amount of data remaining to be flushed from internal buffers + * or an error code, which can be tested using ZSTD_isError(). + * if @return != 0, flush is not fully completed, there is still some data left within internal buffers. + * This is useful for ZSTD_e_flush, since in this case more flushes are necessary to empty all buffers. + * For ZSTD_e_end, @return == 0 when internal buffers are fully flushed and frame is completed. + * - after a ZSTD_e_end directive, if internal buffer is not fully flushed (@return != 0), + * only ZSTD_e_end or ZSTD_e_flush operations are allowed. + * Before starting a new compression job, or changing compression parameters, + * it is required to fully flush internal buffers. + */ +ZSTDLIB_API size_t ZSTD_compressStream2( ZSTD_CCtx* cctx, + ZSTD_outBuffer* output, + ZSTD_inBuffer* input, + ZSTD_EndDirective endOp); + + +/* These buffer sizes are softly recommended. + * They are not required : ZSTD_compressStream*() happily accepts any buffer size, for both input and output. + * Respecting the recommended size just makes it a bit easier for ZSTD_compressStream*(), + * reducing the amount of memory shuffling and buffering, resulting in minor performance savings. + * + * However, note that these recommendations are from the perspective of a C caller program. + * If the streaming interface is invoked from some other language, + * especially managed ones such as Java or Go, through a foreign function interface such as jni or cgo, + * a major performance rule is to reduce crossing such interface to an absolute minimum. + * It's not rare that performance ends being spent more into the interface, rather than compression itself. + * In which cases, prefer using large buffers, as large as practical, + * for both input and output, to reduce the nb of roundtrips. + */ +ZSTDLIB_API size_t ZSTD_CStreamInSize(void); /**< recommended size for input buffer */ +ZSTDLIB_API size_t ZSTD_CStreamOutSize(void); /**< recommended size for output buffer. Guarantee to successfully flush at least one complete compressed block. */ + + +/* ***************************************************************************** + * This following is a legacy streaming API. + * It can be replaced by ZSTD_CCtx_reset() and ZSTD_compressStream2(). + * It is redundant, but remains fully supported. + * Advanced parameters and dictionary compression can only be used through the + * new API. + ******************************************************************************/ + +/*! + * Equivalent to: + * + * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); + * ZSTD_CCtx_refCDict(zcs, NULL); // clear the dictionary (if any) + * ZSTD_CCtx_setParameter(zcs, ZSTD_c_compressionLevel, compressionLevel); + */ +ZSTDLIB_API size_t ZSTD_initCStream(ZSTD_CStream* zcs, int compressionLevel); +/*! + * Alternative for ZSTD_compressStream2(zcs, output, input, ZSTD_e_continue). + * NOTE: The return value is different. ZSTD_compressStream() returns a hint for + * the next read size (if non-zero and not an error). ZSTD_compressStream2() + * returns the minimum nb of bytes left to flush (if non-zero and not an error). + */ +ZSTDLIB_API size_t ZSTD_compressStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output, ZSTD_inBuffer* input); +/*! Equivalent to ZSTD_compressStream2(zcs, output, &emptyInput, ZSTD_e_flush). */ +ZSTDLIB_API size_t ZSTD_flushStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output); +/*! Equivalent to ZSTD_compressStream2(zcs, output, &emptyInput, ZSTD_e_end). */ +ZSTDLIB_API size_t ZSTD_endStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output); + + +/*-*************************************************************************** +* Streaming decompression - HowTo +* +* A ZSTD_DStream object is required to track streaming operations. +* Use ZSTD_createDStream() and ZSTD_freeDStream() to create/release resources. +* ZSTD_DStream objects can be re-used multiple times. +* +* Use ZSTD_initDStream() to start a new decompression operation. +* @return : recommended first input size +* Alternatively, use advanced API to set specific properties. +* +* Use ZSTD_decompressStream() repetitively to consume your input. +* The function will update both `pos` fields. +* If `input.pos < input.size`, some input has not been consumed. +* It's up to the caller to present again remaining data. +* The function tries to flush all data decoded immediately, respecting output buffer size. +* If `output.pos < output.size`, decoder has flushed everything it could. +* But if `output.pos == output.size`, there might be some data left within internal buffers., +* In which case, call ZSTD_decompressStream() again to flush whatever remains in the buffer. +* Note : with no additional input provided, amount of data flushed is necessarily <= ZSTD_BLOCKSIZE_MAX. +* @return : 0 when a frame is completely decoded and fully flushed, +* or an error code, which can be tested using ZSTD_isError(), +* or any other value > 0, which means there is still some decoding or flushing to do to complete current frame : +* the return value is a suggested next input size (just a hint for better latency) +* that will never request more than the remaining frame size. +* *******************************************************************************/ + +typedef ZSTD_DCtx ZSTD_DStream; /**< DCtx and DStream are now effectively same object (>= v1.3.0) */ + /* For compatibility with versions <= v1.2.0, prefer differentiating them. */ +/*===== ZSTD_DStream management functions =====*/ +ZSTDLIB_API ZSTD_DStream* ZSTD_createDStream(void); +ZSTDLIB_API size_t ZSTD_freeDStream(ZSTD_DStream* zds); + +/*===== Streaming decompression functions =====*/ + +/* This function is redundant with the advanced API and equivalent to: + * + * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only); + * ZSTD_DCtx_refDDict(zds, NULL); + */ +ZSTDLIB_API size_t ZSTD_initDStream(ZSTD_DStream* zds); + +ZSTDLIB_API size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inBuffer* input); + +ZSTDLIB_API size_t ZSTD_DStreamInSize(void); /*!< recommended size for input buffer */ +ZSTDLIB_API size_t ZSTD_DStreamOutSize(void); /*!< recommended size for output buffer. Guarantee to successfully flush at least one complete block in all circumstances. */ + + +/************************** +* Simple dictionary API +***************************/ +/*! ZSTD_compress_usingDict() : + * Compression at an explicit compression level using a Dictionary. + * A dictionary can be any arbitrary data segment (also called a prefix), + * or a buffer with specified information (see dictBuilder/zdict.h). + * Note : This function loads the dictionary, resulting in significant startup delay. + * It's intended for a dictionary used only once. + * Note 2 : When `dict == NULL || dictSize < 8` no dictionary is used. */ +ZSTDLIB_API size_t ZSTD_compress_usingDict(ZSTD_CCtx* ctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const void* dict,size_t dictSize, + int compressionLevel); + +/*! ZSTD_decompress_usingDict() : + * Decompression using a known Dictionary. + * Dictionary must be identical to the one used during compression. + * Note : This function loads the dictionary, resulting in significant startup delay. + * It's intended for a dictionary used only once. + * Note : When `dict == NULL || dictSize < 8` no dictionary is used. */ +ZSTDLIB_API size_t ZSTD_decompress_usingDict(ZSTD_DCtx* dctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const void* dict,size_t dictSize); + + +/*********************************** + * Bulk processing dictionary API + **********************************/ +typedef struct ZSTD_CDict_s ZSTD_CDict; + +/*! ZSTD_createCDict() : + * When compressing multiple messages or blocks using the same dictionary, + * it's recommended to digest the dictionary only once, since it's a costly operation. + * ZSTD_createCDict() will create a state from digesting a dictionary. + * The resulting state can be used for future compression operations with very limited startup cost. + * ZSTD_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. + * @dictBuffer can be released after ZSTD_CDict creation, because its content is copied within CDict. + * Note 1 : Consider experimental function `ZSTD_createCDict_byReference()` if you prefer to not duplicate @dictBuffer content. + * Note 2 : A ZSTD_CDict can be created from an empty @dictBuffer, + * in which case the only thing that it transports is the @compressionLevel. + * This can be useful in a pipeline featuring ZSTD_compress_usingCDict() exclusively, + * expecting a ZSTD_CDict parameter with any data, including those without a known dictionary. */ +ZSTDLIB_API ZSTD_CDict* ZSTD_createCDict(const void* dictBuffer, size_t dictSize, + int compressionLevel); + +/*! ZSTD_freeCDict() : + * Function frees memory allocated by ZSTD_createCDict(). */ +ZSTDLIB_API size_t ZSTD_freeCDict(ZSTD_CDict* CDict); + +/*! ZSTD_compress_usingCDict() : + * Compression using a digested Dictionary. + * Recommended when same dictionary is used multiple times. + * Note : compression level is _decided at dictionary creation time_, + * and frame parameters are hardcoded (dictID=yes, contentSize=yes, checksum=no) */ +ZSTDLIB_API size_t ZSTD_compress_usingCDict(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const ZSTD_CDict* cdict); + + +typedef struct ZSTD_DDict_s ZSTD_DDict; + +/*! ZSTD_createDDict() : + * Create a digested dictionary, ready to start decompression operation without startup delay. + * dictBuffer can be released after DDict creation, as its content is copied inside DDict. */ +ZSTDLIB_API ZSTD_DDict* ZSTD_createDDict(const void* dictBuffer, size_t dictSize); + +/*! ZSTD_freeDDict() : + * Function frees memory allocated with ZSTD_createDDict() */ +ZSTDLIB_API size_t ZSTD_freeDDict(ZSTD_DDict* ddict); + +/*! ZSTD_decompress_usingDDict() : + * Decompression using a digested Dictionary. + * Recommended when same dictionary is used multiple times. */ +ZSTDLIB_API size_t ZSTD_decompress_usingDDict(ZSTD_DCtx* dctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const ZSTD_DDict* ddict); + + +/******************************** + * Dictionary helper functions + *******************************/ + +/*! ZSTD_getDictID_fromDict() : + * Provides the dictID stored within dictionary. + * if @return == 0, the dictionary is not conformant with Zstandard specification. + * It can still be loaded, but as a content-only dictionary. */ +ZSTDLIB_API unsigned ZSTD_getDictID_fromDict(const void* dict, size_t dictSize); + +/*! ZSTD_getDictID_fromDDict() : + * Provides the dictID of the dictionary loaded into `ddict`. + * If @return == 0, the dictionary is not conformant to Zstandard specification, or empty. + * Non-conformant dictionaries can still be loaded, but as content-only dictionaries. */ +ZSTDLIB_API unsigned ZSTD_getDictID_fromDDict(const ZSTD_DDict* ddict); + +/*! ZSTD_getDictID_fromFrame() : + * Provides the dictID required to decompressed the frame stored within `src`. + * If @return == 0, the dictID could not be decoded. + * This could for one of the following reasons : + * - The frame does not require a dictionary to be decoded (most common case). + * - The frame was built with dictID intentionally removed. Whatever dictionary is necessary is a hidden information. + * Note : this use case also happens when using a non-conformant dictionary. + * - `srcSize` is too small, and as a result, the frame header could not be decoded (only possible if `srcSize < ZSTD_FRAMEHEADERSIZE_MAX`). + * - This is not a Zstandard frame. + * When identifying the exact failure cause, it's possible to use ZSTD_getFrameHeader(), which will provide a more precise error code. */ +ZSTDLIB_API unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize); + + +/******************************************************************************* + * Advanced dictionary and prefix API + * + * This API allows dictionaries to be used with ZSTD_compress2(), + * ZSTD_compressStream2(), and ZSTD_decompress(). Dictionaries are sticky, and + * only reset with the context is reset with ZSTD_reset_parameters or + * ZSTD_reset_session_and_parameters. Prefixes are single-use. + ******************************************************************************/ + + +/*! ZSTD_CCtx_loadDictionary() : + * Create an internal CDict from `dict` buffer. + * Decompression will have to use same dictionary. + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + * Special: Loading a NULL (or 0-size) dictionary invalidates previous dictionary, + * meaning "return to no-dictionary mode". + * Note 1 : Dictionary is sticky, it will be used for all future compressed frames. + * To return to "no-dictionary" situation, load a NULL dictionary (or reset parameters). + * Note 2 : Loading a dictionary involves building tables. + * It's also a CPU consuming operation, with non-negligible impact on latency. + * Tables are dependent on compression parameters, and for this reason, + * compression parameters can no longer be changed after loading a dictionary. + * Note 3 :`dict` content will be copied internally. + * Use experimental ZSTD_CCtx_loadDictionary_byReference() to reference content instead. + * In such a case, dictionary buffer must outlive its users. + * Note 4 : Use ZSTD_CCtx_loadDictionary_advanced() + * to precisely select how dictionary content must be interpreted. */ +ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary(ZSTD_CCtx* cctx, const void* dict, size_t dictSize); + +/*! ZSTD_CCtx_refCDict() : + * Reference a prepared dictionary, to be used for all next compressed frames. + * Note that compression parameters are enforced from within CDict, + * and supersede any compression parameter previously set within CCtx. + * The parameters ignored are labelled as "superseded-by-cdict" in the ZSTD_cParameter enum docs. + * The ignored parameters will be used again if the CCtx is returned to no-dictionary mode. + * The dictionary will remain valid for future compressed frames using same CCtx. + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + * Special : Referencing a NULL CDict means "return to no-dictionary mode". + * Note 1 : Currently, only one dictionary can be managed. + * Referencing a new dictionary effectively "discards" any previous one. + * Note 2 : CDict is just referenced, its lifetime must outlive its usage within CCtx. */ +ZSTDLIB_API size_t ZSTD_CCtx_refCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); + +/*! ZSTD_CCtx_refPrefix() : + * Reference a prefix (single-usage dictionary) for next compressed frame. + * A prefix is **only used once**. Tables are discarded at end of frame (ZSTD_e_end). + * Decompression will need same prefix to properly regenerate data. + * Compressing with a prefix is similar in outcome as performing a diff and compressing it, + * but performs much faster, especially during decompression (compression speed is tunable with compression level). + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + * Special: Adding any prefix (including NULL) invalidates any previous prefix or dictionary + * Note 1 : Prefix buffer is referenced. It **must** outlive compression. + * Its content must remain unmodified during compression. + * Note 2 : If the intention is to diff some large src data blob with some prior version of itself, + * ensure that the window size is large enough to contain the entire source. + * See ZSTD_c_windowLog. + * Note 3 : Referencing a prefix involves building tables, which are dependent on compression parameters. + * It's a CPU consuming operation, with non-negligible impact on latency. + * If there is a need to use the same prefix multiple times, consider loadDictionary instead. + * Note 4 : By default, the prefix is interpreted as raw content (ZSTD_dct_rawContent). + * Use experimental ZSTD_CCtx_refPrefix_advanced() to alter dictionary interpretation. */ +ZSTDLIB_API size_t ZSTD_CCtx_refPrefix(ZSTD_CCtx* cctx, + const void* prefix, size_t prefixSize); + +/*! ZSTD_DCtx_loadDictionary() : + * Create an internal DDict from dict buffer, + * to be used to decompress next frames. + * The dictionary remains valid for all future frames, until explicitly invalidated. + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + * Special : Adding a NULL (or 0-size) dictionary invalidates any previous dictionary, + * meaning "return to no-dictionary mode". + * Note 1 : Loading a dictionary involves building tables, + * which has a non-negligible impact on CPU usage and latency. + * It's recommended to "load once, use many times", to amortize the cost + * Note 2 :`dict` content will be copied internally, so `dict` can be released after loading. + * Use ZSTD_DCtx_loadDictionary_byReference() to reference dictionary content instead. + * Note 3 : Use ZSTD_DCtx_loadDictionary_advanced() to take control of + * how dictionary content is loaded and interpreted. + */ +ZSTDLIB_API size_t ZSTD_DCtx_loadDictionary(ZSTD_DCtx* dctx, const void* dict, size_t dictSize); + +/*! ZSTD_DCtx_refDDict() : + * Reference a prepared dictionary, to be used to decompress next frames. + * The dictionary remains active for decompression of future frames using same DCtx. + * + * If called with ZSTD_d_refMultipleDDicts enabled, repeated calls of this function + * will store the DDict references in a table, and the DDict used for decompression + * will be determined at decompression time, as per the dict ID in the frame. + * The memory for the table is allocated on the first call to refDDict, and can be + * freed with ZSTD_freeDCtx(). + * + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + * Note 1 : Currently, only one dictionary can be managed. + * Referencing a new dictionary effectively "discards" any previous one. + * Special: referencing a NULL DDict means "return to no-dictionary mode". + * Note 2 : DDict is just referenced, its lifetime must outlive its usage from DCtx. + */ +ZSTDLIB_API size_t ZSTD_DCtx_refDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict); + +/*! ZSTD_DCtx_refPrefix() : + * Reference a prefix (single-usage dictionary) to decompress next frame. + * This is the reverse operation of ZSTD_CCtx_refPrefix(), + * and must use the same prefix as the one used during compression. + * Prefix is **only used once**. Reference is discarded at end of frame. + * End of frame is reached when ZSTD_decompressStream() returns 0. + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + * Note 1 : Adding any prefix (including NULL) invalidates any previously set prefix or dictionary + * Note 2 : Prefix buffer is referenced. It **must** outlive decompression. + * Prefix buffer must remain unmodified up to the end of frame, + * reached when ZSTD_decompressStream() returns 0. + * Note 3 : By default, the prefix is treated as raw content (ZSTD_dct_rawContent). + * Use ZSTD_CCtx_refPrefix_advanced() to alter dictMode (Experimental section) + * Note 4 : Referencing a raw content prefix has almost no cpu nor memory cost. + * A full dictionary is more costly, as it requires building tables. + */ +ZSTDLIB_API size_t ZSTD_DCtx_refPrefix(ZSTD_DCtx* dctx, + const void* prefix, size_t prefixSize); + +/* === Memory management === */ + +/*! ZSTD_sizeof_*() : + * These functions give the _current_ memory usage of selected object. + * Note that object memory usage can evolve (increase or decrease) over time. */ +ZSTDLIB_API size_t ZSTD_sizeof_CCtx(const ZSTD_CCtx* cctx); +ZSTDLIB_API size_t ZSTD_sizeof_DCtx(const ZSTD_DCtx* dctx); +ZSTDLIB_API size_t ZSTD_sizeof_CStream(const ZSTD_CStream* zcs); +ZSTDLIB_API size_t ZSTD_sizeof_DStream(const ZSTD_DStream* zds); +ZSTDLIB_API size_t ZSTD_sizeof_CDict(const ZSTD_CDict* cdict); +ZSTDLIB_API size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict); + +#endif /* ZSTD_H_235446 */ + + +/* ************************************************************************************** + * ADVANCED AND EXPERIMENTAL FUNCTIONS + **************************************************************************************** + * The definitions in the following section are considered experimental. + * They are provided for advanced scenarios. + * They should never be used with a dynamic library, as prototypes may change in the future. + * Use them only in association with static linking. + * ***************************************************************************************/ + +#if defined(ZSTD_STATIC_LINKING_ONLY) && !defined(ZSTD_H_ZSTD_STATIC_LINKING_ONLY) +#define ZSTD_H_ZSTD_STATIC_LINKING_ONLY + +/**************************************************************************************** + * experimental API (static linking only) + **************************************************************************************** + * The following symbols and constants + * are not planned to join "stable API" status in the near future. + * They can still change in future versions. + * Some of them are planned to remain in the static_only section indefinitely. + * Some of them might be removed in the future (especially when redundant with existing stable functions) + * ***************************************************************************************/ + +#define ZSTD_FRAMEHEADERSIZE_PREFIX(format) ((format) == ZSTD_f_zstd1 ? 5 : 1) /* minimum input size required to query frame header size */ +#define ZSTD_FRAMEHEADERSIZE_MIN(format) ((format) == ZSTD_f_zstd1 ? 6 : 2) +#define ZSTD_FRAMEHEADERSIZE_MAX 18 /* can be useful for static allocation */ +#define ZSTD_SKIPPABLEHEADERSIZE 8 + +/* compression parameter bounds */ +#define ZSTD_WINDOWLOG_MAX_32 30 +#define ZSTD_WINDOWLOG_MAX_64 31 +#define ZSTD_WINDOWLOG_MAX ((int)(sizeof(size_t) == 4 ? ZSTD_WINDOWLOG_MAX_32 : ZSTD_WINDOWLOG_MAX_64)) +#define ZSTD_WINDOWLOG_MIN 10 +#define ZSTD_HASHLOG_MAX ((ZSTD_WINDOWLOG_MAX < 30) ? ZSTD_WINDOWLOG_MAX : 30) +#define ZSTD_HASHLOG_MIN 6 +#define ZSTD_CHAINLOG_MAX_32 29 +#define ZSTD_CHAINLOG_MAX_64 30 +#define ZSTD_CHAINLOG_MAX ((int)(sizeof(size_t) == 4 ? ZSTD_CHAINLOG_MAX_32 : ZSTD_CHAINLOG_MAX_64)) +#define ZSTD_CHAINLOG_MIN ZSTD_HASHLOG_MIN +#define ZSTD_SEARCHLOG_MAX (ZSTD_WINDOWLOG_MAX-1) +#define ZSTD_SEARCHLOG_MIN 1 +#define ZSTD_MINMATCH_MAX 7 /* only for ZSTD_fast, other strategies are limited to 6 */ +#define ZSTD_MINMATCH_MIN 3 /* only for ZSTD_btopt+, faster strategies are limited to 4 */ +#define ZSTD_TARGETLENGTH_MAX ZSTD_BLOCKSIZE_MAX +#define ZSTD_TARGETLENGTH_MIN 0 /* note : comparing this constant to an unsigned results in a tautological test */ +#define ZSTD_STRATEGY_MIN ZSTD_fast +#define ZSTD_STRATEGY_MAX ZSTD_btultra2 + + +#define ZSTD_OVERLAPLOG_MIN 0 +#define ZSTD_OVERLAPLOG_MAX 9 + +#define ZSTD_WINDOWLOG_LIMIT_DEFAULT 27 /* by default, the streaming decoder will refuse any frame + * requiring larger than (1< 0: + * If litLength != 0: + * rep == 1 --> offset == repeat_offset_1 + * rep == 2 --> offset == repeat_offset_2 + * rep == 3 --> offset == repeat_offset_3 + * If litLength == 0: + * rep == 1 --> offset == repeat_offset_2 + * rep == 2 --> offset == repeat_offset_3 + * rep == 3 --> offset == repeat_offset_1 - 1 + * + * Note: This field is optional. ZSTD_generateSequences() will calculate the value of + * 'rep', but repeat offsets do not necessarily need to be calculated from an external + * sequence provider's perspective. For example, ZSTD_compressSequences() does not + * use this 'rep' field at all (as of now). + */ +} ZSTD_Sequence; + +typedef struct { + unsigned windowLog; /**< largest match distance : larger == more compression, more memory needed during decompression */ + unsigned chainLog; /**< fully searched segment : larger == more compression, slower, more memory (useless for fast) */ + unsigned hashLog; /**< dispatch table : larger == faster, more memory */ + unsigned searchLog; /**< nb of searches : larger == more compression, slower */ + unsigned minMatch; /**< match length searched : larger == faster decompression, sometimes less compression */ + unsigned targetLength; /**< acceptable match size for optimal parser (only) : larger == more compression, slower */ + ZSTD_strategy strategy; /**< see ZSTD_strategy definition above */ +} ZSTD_compressionParameters; + +typedef struct { + int contentSizeFlag; /**< 1: content size will be in frame header (when known) */ + int checksumFlag; /**< 1: generate a 32-bits checksum using XXH64 algorithm at end of frame, for error detection */ + int noDictIDFlag; /**< 1: no dictID will be saved into frame header (dictID is only useful for dictionary compression) */ +} ZSTD_frameParameters; + +typedef struct { + ZSTD_compressionParameters cParams; + ZSTD_frameParameters fParams; +} ZSTD_parameters; + +typedef enum { + ZSTD_dct_auto = 0, /* dictionary is "full" when starting with ZSTD_MAGIC_DICTIONARY, otherwise it is "rawContent" */ + ZSTD_dct_rawContent = 1, /* ensures dictionary is always loaded as rawContent, even if it starts with ZSTD_MAGIC_DICTIONARY */ + ZSTD_dct_fullDict = 2 /* refuses to load a dictionary if it does not respect Zstandard's specification, starting with ZSTD_MAGIC_DICTIONARY */ +} ZSTD_dictContentType_e; + +typedef enum { + ZSTD_dlm_byCopy = 0, /**< Copy dictionary content internally */ + ZSTD_dlm_byRef = 1 /**< Reference dictionary content -- the dictionary buffer must outlive its users. */ +} ZSTD_dictLoadMethod_e; + +typedef enum { + ZSTD_f_zstd1 = 0, /* zstd frame format, specified in zstd_compression_format.md (default) */ + ZSTD_f_zstd1_magicless = 1 /* Variant of zstd frame format, without initial 4-bytes magic number. + * Useful to save 4 bytes per generated frame. + * Decoder cannot recognise automatically this format, requiring this instruction. */ +} ZSTD_format_e; + +typedef enum { + /* Note: this enum controls ZSTD_d_forceIgnoreChecksum */ + ZSTD_d_validateChecksum = 0, + ZSTD_d_ignoreChecksum = 1 +} ZSTD_forceIgnoreChecksum_e; + +typedef enum { + /* Note: this enum controls ZSTD_d_refMultipleDDicts */ + ZSTD_rmd_refSingleDDict = 0, + ZSTD_rmd_refMultipleDDicts = 1 +} ZSTD_refMultipleDDicts_e; + +typedef enum { + /* Note: this enum and the behavior it controls are effectively internal + * implementation details of the compressor. They are expected to continue + * to evolve and should be considered only in the context of extremely + * advanced performance tuning. + * + * Zstd currently supports the use of a CDict in three ways: + * + * - The contents of the CDict can be copied into the working context. This + * means that the compression can search both the dictionary and input + * while operating on a single set of internal tables. This makes + * the compression faster per-byte of input. However, the initial copy of + * the CDict's tables incurs a fixed cost at the beginning of the + * compression. For small compressions (< 8 KB), that copy can dominate + * the cost of the compression. + * + * - The CDict's tables can be used in-place. In this model, compression is + * slower per input byte, because the compressor has to search two sets of + * tables. However, this model incurs no start-up cost (as long as the + * working context's tables can be reused). For small inputs, this can be + * faster than copying the CDict's tables. + * + * - The CDict's tables are not used at all, and instead we use the working + * context alone to reload the dictionary and use params based on the source + * size. See ZSTD_compress_insertDictionary() and ZSTD_compress_usingDict(). + * This method is effective when the dictionary sizes are very small relative + * to the input size, and the input size is fairly large to begin with. + * + * Zstd has a simple internal heuristic that selects which strategy to use + * at the beginning of a compression. However, if experimentation shows that + * Zstd is making poor choices, it is possible to override that choice with + * this enum. + */ + ZSTD_dictDefaultAttach = 0, /* Use the default heuristic. */ + ZSTD_dictForceAttach = 1, /* Never copy the dictionary. */ + ZSTD_dictForceCopy = 2, /* Always copy the dictionary. */ + ZSTD_dictForceLoad = 3 /* Always reload the dictionary */ +} ZSTD_dictAttachPref_e; + +typedef enum { + ZSTD_lcm_auto = 0, /**< Automatically determine the compression mode based on the compression level. + * Negative compression levels will be uncompressed, and positive compression + * levels will be compressed. */ + ZSTD_lcm_huffman = 1, /**< Always attempt Huffman compression. Uncompressed literals will still be + * emitted if Huffman compression is not profitable. */ + ZSTD_lcm_uncompressed = 2 /**< Always emit uncompressed literals. */ +} ZSTD_literalCompressionMode_e; + + +/*************************************** +* Frame size functions +***************************************/ + +/*! ZSTD_findDecompressedSize() : + * `src` should point to the start of a series of ZSTD encoded and/or skippable frames + * `srcSize` must be the _exact_ size of this series + * (i.e. there should be a frame boundary at `src + srcSize`) + * @return : - decompressed size of all data in all successive frames + * - if the decompressed size cannot be determined: ZSTD_CONTENTSIZE_UNKNOWN + * - if an error occurred: ZSTD_CONTENTSIZE_ERROR + * + * note 1 : decompressed size is an optional field, that may not be present, especially in streaming mode. + * When `return==ZSTD_CONTENTSIZE_UNKNOWN`, data to decompress could be any size. + * In which case, it's necessary to use streaming mode to decompress data. + * note 2 : decompressed size is always present when compression is done with ZSTD_compress() + * note 3 : decompressed size can be very large (64-bits value), + * potentially larger than what local system can handle as a single memory segment. + * In which case, it's necessary to use streaming mode to decompress data. + * note 4 : If source is untrusted, decompressed size could be wrong or intentionally modified. + * Always ensure result fits within application's authorized limits. + * Each application can set its own limits. + * note 5 : ZSTD_findDecompressedSize handles multiple frames, and so it must traverse the input to + * read each contained frame header. This is fast as most of the data is skipped, + * however it does mean that all frame data must be present and valid. */ +ZSTDLIB_API unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize); + +/*! ZSTD_decompressBound() : + * `src` should point to the start of a series of ZSTD encoded and/or skippable frames + * `srcSize` must be the _exact_ size of this series + * (i.e. there should be a frame boundary at `src + srcSize`) + * @return : - upper-bound for the decompressed size of all data in all successive frames + * - if an error occurred: ZSTD_CONTENTSIZE_ERROR + * + * note 1 : an error can occur if `src` contains an invalid or incorrectly formatted frame. + * note 2 : the upper-bound is exact when the decompressed size field is available in every ZSTD encoded frame of `src`. + * in this case, `ZSTD_findDecompressedSize` and `ZSTD_decompressBound` return the same value. + * note 3 : when the decompressed size field isn't available, the upper-bound for that frame is calculated by: + * upper-bound = # blocks * min(128 KB, Window_Size) + */ +ZSTDLIB_API unsigned long long ZSTD_decompressBound(const void* src, size_t srcSize); + +/*! ZSTD_frameHeaderSize() : + * srcSize must be >= ZSTD_FRAMEHEADERSIZE_PREFIX. + * @return : size of the Frame Header, + * or an error code (if srcSize is too small) */ +ZSTDLIB_API size_t ZSTD_frameHeaderSize(const void* src, size_t srcSize); + +typedef enum { + ZSTD_sf_noBlockDelimiters = 0, /* Representation of ZSTD_Sequence has no block delimiters, sequences only */ + ZSTD_sf_explicitBlockDelimiters = 1 /* Representation of ZSTD_Sequence contains explicit block delimiters */ +} ZSTD_sequenceFormat_e; + +/*! ZSTD_generateSequences() : + * Generate sequences using ZSTD_compress2, given a source buffer. + * + * Each block will end with a dummy sequence + * with offset == 0, matchLength == 0, and litLength == length of last literals. + * litLength may be == 0, and if so, then the sequence of (of: 0 ml: 0 ll: 0) + * simply acts as a block delimiter. + * + * zc can be used to insert custom compression params. + * This function invokes ZSTD_compress2 + * + * The output of this function can be fed into ZSTD_compressSequences() with CCtx + * setting of ZSTD_c_blockDelimiters as ZSTD_sf_explicitBlockDelimiters + * @return : number of sequences generated + */ + +ZSTDLIB_API size_t ZSTD_generateSequences(ZSTD_CCtx* zc, ZSTD_Sequence* outSeqs, + size_t outSeqsSize, const void* src, size_t srcSize); + +/*! ZSTD_mergeBlockDelimiters() : + * Given an array of ZSTD_Sequence, remove all sequences that represent block delimiters/last literals + * by merging them into into the literals of the next sequence. + * + * As such, the final generated result has no explicit representation of block boundaries, + * and the final last literals segment is not represented in the sequences. + * + * The output of this function can be fed into ZSTD_compressSequences() with CCtx + * setting of ZSTD_c_blockDelimiters as ZSTD_sf_noBlockDelimiters + * @return : number of sequences left after merging + */ +ZSTDLIB_API size_t ZSTD_mergeBlockDelimiters(ZSTD_Sequence* sequences, size_t seqsSize); + +/*! ZSTD_compressSequences() : + * Compress an array of ZSTD_Sequence, generated from the original source buffer, into dst. + * If a dictionary is included, then the cctx should reference the dict. (see: ZSTD_CCtx_refCDict(), ZSTD_CCtx_loadDictionary(), etc.) + * The entire source is compressed into a single frame. + * + * The compression behavior changes based on cctx params. In particular: + * If ZSTD_c_blockDelimiters == ZSTD_sf_noBlockDelimiters, the array of ZSTD_Sequence is expected to contain + * no block delimiters (defined in ZSTD_Sequence). Block boundaries are roughly determined based on + * the block size derived from the cctx, and sequences may be split. This is the default setting. + * + * If ZSTD_c_blockDelimiters == ZSTD_sf_explicitBlockDelimiters, the array of ZSTD_Sequence is expected to contain + * block delimiters (defined in ZSTD_Sequence). Behavior is undefined if no block delimiters are provided. + * + * If ZSTD_c_validateSequences == 0, this function will blindly accept the sequences provided. Invalid sequences cause undefined + * behavior. If ZSTD_c_validateSequences == 1, then if sequence is invalid (see doc/zstd_compression_format.md for + * specifics regarding offset/matchlength requirements) then the function will bail out and return an error. + * + * In addition to the two adjustable experimental params, there are other important cctx params. + * - ZSTD_c_minMatch MUST be set as less than or equal to the smallest match generated by the match finder. It has a minimum value of ZSTD_MINMATCH_MIN. + * - ZSTD_c_compressionLevel accordingly adjusts the strength of the entropy coder, as it would in typical compression. + * - ZSTD_c_windowLog affects offset validation: this function will return an error at higher debug levels if a provided offset + * is larger than what the spec allows for a given window log and dictionary (if present). See: doc/zstd_compression_format.md + * + * Note: Repcodes are, as of now, always re-calculated within this function, so ZSTD_Sequence::rep is unused. + * Note 2: Once we integrate ability to ingest repcodes, the explicit block delims mode must respect those repcodes exactly, + * and cannot emit an RLE block that disagrees with the repcode history + * @return : final compressed size or a ZSTD error. + */ +ZSTDLIB_API size_t ZSTD_compressSequences(ZSTD_CCtx* const cctx, void* dst, size_t dstSize, + const ZSTD_Sequence* inSeqs, size_t inSeqsSize, + const void* src, size_t srcSize); + + +/*! ZSTD_writeSkippableFrame() : + * Generates a zstd skippable frame containing data given by src, and writes it to dst buffer. + * + * Skippable frames begin with a a 4-byte magic number. There are 16 possible choices of magic number, + * ranging from ZSTD_MAGIC_SKIPPABLE_START to ZSTD_MAGIC_SKIPPABLE_START+15. + * As such, the parameter magicVariant controls the exact skippable frame magic number variant used, so + * the magic number used will be ZSTD_MAGIC_SKIPPABLE_START + magicVariant. + * + * Returns an error if destination buffer is not large enough, if the source size is not representable + * with a 4-byte unsigned int, or if the parameter magicVariant is greater than 15 (and therefore invalid). + * + * @return : number of bytes written or a ZSTD error. + */ +ZSTDLIB_API size_t ZSTD_writeSkippableFrame(void* dst, size_t dstCapacity, + const void* src, size_t srcSize, unsigned magicVariant); + + +/*************************************** +* Memory management +***************************************/ + +/*! ZSTD_estimate*() : + * These functions make it possible to estimate memory usage + * of a future {D,C}Ctx, before its creation. + * + * ZSTD_estimateCCtxSize() will provide a memory budget large enough + * for any compression level up to selected one. + * Note : Unlike ZSTD_estimateCStreamSize*(), this estimate + * does not include space for a window buffer. + * Therefore, the estimation is only guaranteed for single-shot compressions, not streaming. + * The estimate will assume the input may be arbitrarily large, + * which is the worst case. + * + * When srcSize can be bound by a known and rather "small" value, + * this fact can be used to provide a tighter estimation + * because the CCtx compression context will need less memory. + * This tighter estimation can be provided by more advanced functions + * ZSTD_estimateCCtxSize_usingCParams(), which can be used in tandem with ZSTD_getCParams(), + * and ZSTD_estimateCCtxSize_usingCCtxParams(), which can be used in tandem with ZSTD_CCtxParams_setParameter(). + * Both can be used to estimate memory using custom compression parameters and arbitrary srcSize limits. + * + * Note 2 : only single-threaded compression is supported. + * ZSTD_estimateCCtxSize_usingCCtxParams() will return an error code if ZSTD_c_nbWorkers is >= 1. + */ +ZSTDLIB_API size_t ZSTD_estimateCCtxSize(int compressionLevel); +ZSTDLIB_API size_t ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams); +ZSTDLIB_API size_t ZSTD_estimateCCtxSize_usingCCtxParams(const ZSTD_CCtx_params* params); +ZSTDLIB_API size_t ZSTD_estimateDCtxSize(void); + +/*! ZSTD_estimateCStreamSize() : + * ZSTD_estimateCStreamSize() will provide a budget large enough for any compression level up to selected one. + * It will also consider src size to be arbitrarily "large", which is worst case. + * If srcSize is known to always be small, ZSTD_estimateCStreamSize_usingCParams() can provide a tighter estimation. + * ZSTD_estimateCStreamSize_usingCParams() can be used in tandem with ZSTD_getCParams() to create cParams from compressionLevel. + * ZSTD_estimateCStreamSize_usingCCtxParams() can be used in tandem with ZSTD_CCtxParams_setParameter(). Only single-threaded compression is supported. This function will return an error code if ZSTD_c_nbWorkers is >= 1. + * Note : CStream size estimation is only correct for single-threaded compression. + * ZSTD_DStream memory budget depends on window Size. + * This information can be passed manually, using ZSTD_estimateDStreamSize, + * or deducted from a valid frame Header, using ZSTD_estimateDStreamSize_fromFrame(); + * Note : if streaming is init with function ZSTD_init?Stream_usingDict(), + * an internal ?Dict will be created, which additional size is not estimated here. + * In this case, get total size by adding ZSTD_estimate?DictSize */ +ZSTDLIB_API size_t ZSTD_estimateCStreamSize(int compressionLevel); +ZSTDLIB_API size_t ZSTD_estimateCStreamSize_usingCParams(ZSTD_compressionParameters cParams); +ZSTDLIB_API size_t ZSTD_estimateCStreamSize_usingCCtxParams(const ZSTD_CCtx_params* params); +ZSTDLIB_API size_t ZSTD_estimateDStreamSize(size_t windowSize); +ZSTDLIB_API size_t ZSTD_estimateDStreamSize_fromFrame(const void* src, size_t srcSize); + +/*! ZSTD_estimate?DictSize() : + * ZSTD_estimateCDictSize() will bet that src size is relatively "small", and content is copied, like ZSTD_createCDict(). + * ZSTD_estimateCDictSize_advanced() makes it possible to control compression parameters precisely, like ZSTD_createCDict_advanced(). + * Note : dictionaries created by reference (`ZSTD_dlm_byRef`) are logically smaller. + */ +ZSTDLIB_API size_t ZSTD_estimateCDictSize(size_t dictSize, int compressionLevel); +ZSTDLIB_API size_t ZSTD_estimateCDictSize_advanced(size_t dictSize, ZSTD_compressionParameters cParams, ZSTD_dictLoadMethod_e dictLoadMethod); +ZSTDLIB_API size_t ZSTD_estimateDDictSize(size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod); + +/*! ZSTD_initStatic*() : + * Initialize an object using a pre-allocated fixed-size buffer. + * workspace: The memory area to emplace the object into. + * Provided pointer *must be 8-bytes aligned*. + * Buffer must outlive object. + * workspaceSize: Use ZSTD_estimate*Size() to determine + * how large workspace must be to support target scenario. + * @return : pointer to object (same address as workspace, just different type), + * or NULL if error (size too small, incorrect alignment, etc.) + * Note : zstd will never resize nor malloc() when using a static buffer. + * If the object requires more memory than available, + * zstd will just error out (typically ZSTD_error_memory_allocation). + * Note 2 : there is no corresponding "free" function. + * Since workspace is allocated externally, it must be freed externally too. + * Note 3 : cParams : use ZSTD_getCParams() to convert a compression level + * into its associated cParams. + * Limitation 1 : currently not compatible with internal dictionary creation, triggered by + * ZSTD_CCtx_loadDictionary(), ZSTD_initCStream_usingDict() or ZSTD_initDStream_usingDict(). + * Limitation 2 : static cctx currently not compatible with multi-threading. + * Limitation 3 : static dctx is incompatible with legacy support. + */ +ZSTDLIB_API ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize); +ZSTDLIB_API ZSTD_CStream* ZSTD_initStaticCStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticCCtx() */ + +ZSTDLIB_API ZSTD_DCtx* ZSTD_initStaticDCtx(void* workspace, size_t workspaceSize); +ZSTDLIB_API ZSTD_DStream* ZSTD_initStaticDStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticDCtx() */ + +ZSTDLIB_API const ZSTD_CDict* ZSTD_initStaticCDict( + void* workspace, size_t workspaceSize, + const void* dict, size_t dictSize, + ZSTD_dictLoadMethod_e dictLoadMethod, + ZSTD_dictContentType_e dictContentType, + ZSTD_compressionParameters cParams); + +ZSTDLIB_API const ZSTD_DDict* ZSTD_initStaticDDict( + void* workspace, size_t workspaceSize, + const void* dict, size_t dictSize, + ZSTD_dictLoadMethod_e dictLoadMethod, + ZSTD_dictContentType_e dictContentType); + + +/*! Custom memory allocation : + * These prototypes make it possible to pass your own allocation/free functions. + * ZSTD_customMem is provided at creation time, using ZSTD_create*_advanced() variants listed below. + * All allocation/free operations will be completed using these custom variants instead of regular ones. + */ +typedef void* (*ZSTD_allocFunction) (void* opaque, size_t size); +typedef void (*ZSTD_freeFunction) (void* opaque, void* address); +typedef struct { ZSTD_allocFunction customAlloc; ZSTD_freeFunction customFree; void* opaque; } ZSTD_customMem; +static +#ifdef __GNUC__ +__attribute__((__unused__)) +#endif +ZSTD_customMem const ZSTD_defaultCMem = { NULL, NULL, NULL }; /**< this constant defers to stdlib's functions */ + +ZSTDLIB_API ZSTD_CCtx* ZSTD_createCCtx_advanced(ZSTD_customMem customMem); +ZSTDLIB_API ZSTD_CStream* ZSTD_createCStream_advanced(ZSTD_customMem customMem); +ZSTDLIB_API ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem); +ZSTDLIB_API ZSTD_DStream* ZSTD_createDStream_advanced(ZSTD_customMem customMem); + +ZSTDLIB_API ZSTD_CDict* ZSTD_createCDict_advanced(const void* dict, size_t dictSize, + ZSTD_dictLoadMethod_e dictLoadMethod, + ZSTD_dictContentType_e dictContentType, + ZSTD_compressionParameters cParams, + ZSTD_customMem customMem); + +/* ! Thread pool : + * These prototypes make it possible to share a thread pool among multiple compression contexts. + * This can limit resources for applications with multiple threads where each one uses + * a threaded compression mode (via ZSTD_c_nbWorkers parameter). + * ZSTD_createThreadPool creates a new thread pool with a given number of threads. + * Note that the lifetime of such pool must exist while being used. + * ZSTD_CCtx_refThreadPool assigns a thread pool to a context (use NULL argument value + * to use an internal thread pool). + * ZSTD_freeThreadPool frees a thread pool. + */ +typedef struct POOL_ctx_s ZSTD_threadPool; +ZSTDLIB_API ZSTD_threadPool* ZSTD_createThreadPool(size_t numThreads); +ZSTDLIB_API void ZSTD_freeThreadPool (ZSTD_threadPool* pool); +ZSTDLIB_API size_t ZSTD_CCtx_refThreadPool(ZSTD_CCtx* cctx, ZSTD_threadPool* pool); + + +/* + * This API is temporary and is expected to change or disappear in the future! + */ +ZSTDLIB_API ZSTD_CDict* ZSTD_createCDict_advanced2( + const void* dict, size_t dictSize, + ZSTD_dictLoadMethod_e dictLoadMethod, + ZSTD_dictContentType_e dictContentType, + const ZSTD_CCtx_params* cctxParams, + ZSTD_customMem customMem); + +ZSTDLIB_API ZSTD_DDict* ZSTD_createDDict_advanced( + const void* dict, size_t dictSize, + ZSTD_dictLoadMethod_e dictLoadMethod, + ZSTD_dictContentType_e dictContentType, + ZSTD_customMem customMem); + + +/*************************************** +* Advanced compression functions +***************************************/ + +/*! ZSTD_createCDict_byReference() : + * Create a digested dictionary for compression + * Dictionary content is just referenced, not duplicated. + * As a consequence, `dictBuffer` **must** outlive CDict, + * and its content must remain unmodified throughout the lifetime of CDict. + * note: equivalent to ZSTD_createCDict_advanced(), with dictLoadMethod==ZSTD_dlm_byRef */ +ZSTDLIB_API ZSTD_CDict* ZSTD_createCDict_byReference(const void* dictBuffer, size_t dictSize, int compressionLevel); + +/*! ZSTD_getDictID_fromCDict() : + * Provides the dictID of the dictionary loaded into `cdict`. + * If @return == 0, the dictionary is not conformant to Zstandard specification, or empty. + * Non-conformant dictionaries can still be loaded, but as content-only dictionaries. */ +ZSTDLIB_API unsigned ZSTD_getDictID_fromCDict(const ZSTD_CDict* cdict); + +/*! ZSTD_getCParams() : + * @return ZSTD_compressionParameters structure for a selected compression level and estimated srcSize. + * `estimatedSrcSize` value is optional, select 0 if not known */ +ZSTDLIB_API ZSTD_compressionParameters ZSTD_getCParams(int compressionLevel, unsigned long long estimatedSrcSize, size_t dictSize); + +/*! ZSTD_getParams() : + * same as ZSTD_getCParams(), but @return a full `ZSTD_parameters` object instead of sub-component `ZSTD_compressionParameters`. + * All fields of `ZSTD_frameParameters` are set to default : contentSize=1, checksum=0, noDictID=0 */ +ZSTDLIB_API ZSTD_parameters ZSTD_getParams(int compressionLevel, unsigned long long estimatedSrcSize, size_t dictSize); + +/*! ZSTD_checkCParams() : + * Ensure param values remain within authorized range. + * @return 0 on success, or an error code (can be checked with ZSTD_isError()) */ +ZSTDLIB_API size_t ZSTD_checkCParams(ZSTD_compressionParameters params); + +/*! ZSTD_adjustCParams() : + * optimize params for a given `srcSize` and `dictSize`. + * `srcSize` can be unknown, in which case use ZSTD_CONTENTSIZE_UNKNOWN. + * `dictSize` must be `0` when there is no dictionary. + * cPar can be invalid : all parameters will be clamped within valid range in the @return struct. + * This function never fails (wide contract) */ +ZSTDLIB_API ZSTD_compressionParameters ZSTD_adjustCParams(ZSTD_compressionParameters cPar, unsigned long long srcSize, size_t dictSize); + +/*! ZSTD_compress_advanced() : + * Note : this function is now DEPRECATED. + * It can be replaced by ZSTD_compress2(), in combination with ZSTD_CCtx_setParameter() and other parameter setters. + * This prototype will be marked as deprecated and generate compilation warning on reaching v1.5.x */ +ZSTDLIB_API size_t ZSTD_compress_advanced(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const void* dict,size_t dictSize, + ZSTD_parameters params); + +/*! ZSTD_compress_usingCDict_advanced() : + * Note : this function is now REDUNDANT. + * It can be replaced by ZSTD_compress2(), in combination with ZSTD_CCtx_loadDictionary() and other parameter setters. + * This prototype will be marked as deprecated and generate compilation warning in some future version */ +ZSTDLIB_API size_t ZSTD_compress_usingCDict_advanced(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const ZSTD_CDict* cdict, + ZSTD_frameParameters fParams); + + +/*! ZSTD_CCtx_loadDictionary_byReference() : + * Same as ZSTD_CCtx_loadDictionary(), but dictionary content is referenced, instead of being copied into CCtx. + * It saves some memory, but also requires that `dict` outlives its usage within `cctx` */ +ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary_byReference(ZSTD_CCtx* cctx, const void* dict, size_t dictSize); + +/*! ZSTD_CCtx_loadDictionary_advanced() : + * Same as ZSTD_CCtx_loadDictionary(), but gives finer control over + * how to load the dictionary (by copy ? by reference ?) + * and how to interpret it (automatic ? force raw mode ? full mode only ?) */ +ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary_advanced(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); + +/*! ZSTD_CCtx_refPrefix_advanced() : + * Same as ZSTD_CCtx_refPrefix(), but gives finer control over + * how to interpret prefix content (automatic ? force raw mode (default) ? full mode only ?) */ +ZSTDLIB_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType); + +/* === experimental parameters === */ +/* these parameters can be used with ZSTD_setParameter() + * they are not guaranteed to remain supported in the future */ + + /* Enables rsyncable mode, + * which makes compressed files more rsync friendly + * by adding periodic synchronization points to the compressed data. + * The target average block size is ZSTD_c_jobSize / 2. + * It's possible to modify the job size to increase or decrease + * the granularity of the synchronization point. + * Once the jobSize is smaller than the window size, + * it will result in compression ratio degradation. + * NOTE 1: rsyncable mode only works when multithreading is enabled. + * NOTE 2: rsyncable performs poorly in combination with long range mode, + * since it will decrease the effectiveness of synchronization points, + * though mileage may vary. + * NOTE 3: Rsyncable mode limits maximum compression speed to ~400 MB/s. + * If the selected compression level is already running significantly slower, + * the overall speed won't be significantly impacted. + */ + #define ZSTD_c_rsyncable ZSTD_c_experimentalParam1 + +/* Select a compression format. + * The value must be of type ZSTD_format_e. + * See ZSTD_format_e enum definition for details */ +#define ZSTD_c_format ZSTD_c_experimentalParam2 + +/* Force back-reference distances to remain < windowSize, + * even when referencing into Dictionary content (default:0) */ +#define ZSTD_c_forceMaxWindow ZSTD_c_experimentalParam3 + +/* Controls whether the contents of a CDict + * are used in place, or copied into the working context. + * Accepts values from the ZSTD_dictAttachPref_e enum. + * See the comments on that enum for an explanation of the feature. */ +#define ZSTD_c_forceAttachDict ZSTD_c_experimentalParam4 + +/* Controls how the literals are compressed (default is auto). + * The value must be of type ZSTD_literalCompressionMode_e. + * See ZSTD_literalCompressionMode_t enum definition for details. + */ +#define ZSTD_c_literalCompressionMode ZSTD_c_experimentalParam5 + +/* Tries to fit compressed block size to be around targetCBlockSize. + * No target when targetCBlockSize == 0. + * There is no guarantee on compressed block size (default:0) */ +#define ZSTD_c_targetCBlockSize ZSTD_c_experimentalParam6 + +/* User's best guess of source size. + * Hint is not valid when srcSizeHint == 0. + * There is no guarantee that hint is close to actual source size, + * but compression ratio may regress significantly if guess considerably underestimates */ +#define ZSTD_c_srcSizeHint ZSTD_c_experimentalParam7 + +/* Controls whether the new and experimental "dedicated dictionary search + * structure" can be used. This feature is still rough around the edges, be + * prepared for surprising behavior! + * + * How to use it: + * + * When using a CDict, whether to use this feature or not is controlled at + * CDict creation, and it must be set in a CCtxParams set passed into that + * construction (via ZSTD_createCDict_advanced2()). A compression will then + * use the feature or not based on how the CDict was constructed; the value of + * this param, set in the CCtx, will have no effect. + * + * However, when a dictionary buffer is passed into a CCtx, such as via + * ZSTD_CCtx_loadDictionary(), this param can be set on the CCtx to control + * whether the CDict that is created internally can use the feature or not. + * + * What it does: + * + * Normally, the internal data structures of the CDict are analogous to what + * would be stored in a CCtx after compressing the contents of a dictionary. + * To an approximation, a compression using a dictionary can then use those + * data structures to simply continue what is effectively a streaming + * compression where the simulated compression of the dictionary left off. + * Which is to say, the search structures in the CDict are normally the same + * format as in the CCtx. + * + * It is possible to do better, since the CDict is not like a CCtx: the search + * structures are written once during CDict creation, and then are only read + * after that, while the search structures in the CCtx are both read and + * written as the compression goes along. This means we can choose a search + * structure for the dictionary that is read-optimized. + * + * This feature enables the use of that different structure. + * + * Note that some of the members of the ZSTD_compressionParameters struct have + * different semantics and constraints in the dedicated search structure. It is + * highly recommended that you simply set a compression level in the CCtxParams + * you pass into the CDict creation call, and avoid messing with the cParams + * directly. + * + * Effects: + * + * This will only have any effect when the selected ZSTD_strategy + * implementation supports this feature. Currently, that's limited to + * ZSTD_greedy, ZSTD_lazy, and ZSTD_lazy2. + * + * Note that this means that the CDict tables can no longer be copied into the + * CCtx, so the dict attachment mode ZSTD_dictForceCopy will no longer be + * useable. The dictionary can only be attached or reloaded. + * + * In general, you should expect compression to be faster--sometimes very much + * so--and CDict creation to be slightly slower. Eventually, we will probably + * make this mode the default. + */ +#define ZSTD_c_enableDedicatedDictSearch ZSTD_c_experimentalParam8 + +/* ZSTD_c_stableInBuffer + * Experimental parameter. + * Default is 0 == disabled. Set to 1 to enable. + * + * Tells the compressor that the ZSTD_inBuffer will ALWAYS be the same + * between calls, except for the modifications that zstd makes to pos (the + * caller must not modify pos). This is checked by the compressor, and + * compression will fail if it ever changes. This means the only flush + * mode that makes sense is ZSTD_e_end, so zstd will error if ZSTD_e_end + * is not used. The data in the ZSTD_inBuffer in the range [src, src + pos) + * MUST not be modified during compression or you will get data corruption. + * + * When this flag is enabled zstd won't allocate an input window buffer, + * because the user guarantees it can reference the ZSTD_inBuffer until + * the frame is complete. But, it will still allocate an output buffer + * large enough to fit a block (see ZSTD_c_stableOutBuffer). This will also + * avoid the memcpy() from the input buffer to the input window buffer. + * + * NOTE: ZSTD_compressStream2() will error if ZSTD_e_end is not used. + * That means this flag cannot be used with ZSTD_compressStream(). + * + * NOTE: So long as the ZSTD_inBuffer always points to valid memory, using + * this flag is ALWAYS memory safe, and will never access out-of-bounds + * memory. However, compression WILL fail if you violate the preconditions. + * + * WARNING: The data in the ZSTD_inBuffer in the range [dst, dst + pos) MUST + * not be modified during compression or you will get data corruption. This + * is because zstd needs to reference data in the ZSTD_inBuffer to find + * matches. Normally zstd maintains its own window buffer for this purpose, + * but passing this flag tells zstd to use the user provided buffer. + */ +#define ZSTD_c_stableInBuffer ZSTD_c_experimentalParam9 + +/* ZSTD_c_stableOutBuffer + * Experimental parameter. + * Default is 0 == disabled. Set to 1 to enable. + * + * Tells he compressor that the ZSTD_outBuffer will not be resized between + * calls. Specifically: (out.size - out.pos) will never grow. This gives the + * compressor the freedom to say: If the compressed data doesn't fit in the + * output buffer then return ZSTD_error_dstSizeTooSmall. This allows us to + * always decompress directly into the output buffer, instead of decompressing + * into an internal buffer and copying to the output buffer. + * + * When this flag is enabled zstd won't allocate an output buffer, because + * it can write directly to the ZSTD_outBuffer. It will still allocate the + * input window buffer (see ZSTD_c_stableInBuffer). + * + * Zstd will check that (out.size - out.pos) never grows and return an error + * if it does. While not strictly necessary, this should prevent surprises. + */ +#define ZSTD_c_stableOutBuffer ZSTD_c_experimentalParam10 + +/* ZSTD_c_blockDelimiters + * Default is 0 == ZSTD_sf_noBlockDelimiters. + * + * For use with sequence compression API: ZSTD_compressSequences(). + * + * Designates whether or not the given array of ZSTD_Sequence contains block delimiters + * and last literals, which are defined as sequences with offset == 0 and matchLength == 0. + * See the definition of ZSTD_Sequence for more specifics. + */ +#define ZSTD_c_blockDelimiters ZSTD_c_experimentalParam11 + +/* ZSTD_c_validateSequences + * Default is 0 == disabled. Set to 1 to enable sequence validation. + * + * For use with sequence compression API: ZSTD_compressSequences(). + * Designates whether or not we validate sequences provided to ZSTD_compressSequences() + * during function execution. + * + * Without validation, providing a sequence that does not conform to the zstd spec will cause + * undefined behavior, and may produce a corrupted block. + * + * With validation enabled, a if sequence is invalid (see doc/zstd_compression_format.md for + * specifics regarding offset/matchlength requirements) then the function will bail out and + * return an error. + * + */ +#define ZSTD_c_validateSequences ZSTD_c_experimentalParam12 + +/*! ZSTD_CCtx_getParameter() : + * Get the requested compression parameter value, selected by enum ZSTD_cParameter, + * and store it into int* value. + * @return : 0, or an error code (which can be tested with ZSTD_isError()). + */ +ZSTDLIB_API size_t ZSTD_CCtx_getParameter(const ZSTD_CCtx* cctx, ZSTD_cParameter param, int* value); + + +/*! ZSTD_CCtx_params : + * Quick howto : + * - ZSTD_createCCtxParams() : Create a ZSTD_CCtx_params structure + * - ZSTD_CCtxParams_setParameter() : Push parameters one by one into + * an existing ZSTD_CCtx_params structure. + * This is similar to + * ZSTD_CCtx_setParameter(). + * - ZSTD_CCtx_setParametersUsingCCtxParams() : Apply parameters to + * an existing CCtx. + * These parameters will be applied to + * all subsequent frames. + * - ZSTD_compressStream2() : Do compression using the CCtx. + * - ZSTD_freeCCtxParams() : Free the memory. + * + * This can be used with ZSTD_estimateCCtxSize_advanced_usingCCtxParams() + * for static allocation of CCtx for single-threaded compression. + */ +ZSTDLIB_API ZSTD_CCtx_params* ZSTD_createCCtxParams(void); +ZSTDLIB_API size_t ZSTD_freeCCtxParams(ZSTD_CCtx_params* params); + +/*! ZSTD_CCtxParams_reset() : + * Reset params to default values. + */ +ZSTDLIB_API size_t ZSTD_CCtxParams_reset(ZSTD_CCtx_params* params); + +/*! ZSTD_CCtxParams_init() : + * Initializes the compression parameters of cctxParams according to + * compression level. All other parameters are reset to their default values. + */ +ZSTDLIB_API size_t ZSTD_CCtxParams_init(ZSTD_CCtx_params* cctxParams, int compressionLevel); + +/*! ZSTD_CCtxParams_init_advanced() : + * Initializes the compression and frame parameters of cctxParams according to + * params. All other parameters are reset to their default values. + */ +ZSTDLIB_API size_t ZSTD_CCtxParams_init_advanced(ZSTD_CCtx_params* cctxParams, ZSTD_parameters params); + +/*! ZSTD_CCtxParams_setParameter() : + * Similar to ZSTD_CCtx_setParameter. + * Set one compression parameter, selected by enum ZSTD_cParameter. + * Parameters must be applied to a ZSTD_CCtx using + * ZSTD_CCtx_setParametersUsingCCtxParams(). + * @result : a code representing success or failure (which can be tested with + * ZSTD_isError()). + */ +ZSTDLIB_API size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* params, ZSTD_cParameter param, int value); + +/*! ZSTD_CCtxParams_getParameter() : + * Similar to ZSTD_CCtx_getParameter. + * Get the requested value of one compression parameter, selected by enum ZSTD_cParameter. + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + */ +ZSTDLIB_API size_t ZSTD_CCtxParams_getParameter(const ZSTD_CCtx_params* params, ZSTD_cParameter param, int* value); + +/*! ZSTD_CCtx_setParametersUsingCCtxParams() : + * Apply a set of ZSTD_CCtx_params to the compression context. + * This can be done even after compression is started, + * if nbWorkers==0, this will have no impact until a new compression is started. + * if nbWorkers>=1, new parameters will be picked up at next job, + * with a few restrictions (windowLog, pledgedSrcSize, nbWorkers, jobSize, and overlapLog are not updated). + */ +ZSTDLIB_API size_t ZSTD_CCtx_setParametersUsingCCtxParams( + ZSTD_CCtx* cctx, const ZSTD_CCtx_params* params); + +/*! ZSTD_compressStream2_simpleArgs() : + * Same as ZSTD_compressStream2(), + * but using only integral types as arguments. + * This variant might be helpful for binders from dynamic languages + * which have troubles handling structures containing memory pointers. + */ +ZSTDLIB_API size_t ZSTD_compressStream2_simpleArgs ( + ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, size_t* dstPos, + const void* src, size_t srcSize, size_t* srcPos, + ZSTD_EndDirective endOp); + + +/*************************************** +* Advanced decompression functions +***************************************/ + +/*! ZSTD_isFrame() : + * Tells if the content of `buffer` starts with a valid Frame Identifier. + * Note : Frame Identifier is 4 bytes. If `size < 4`, @return will always be 0. + * Note 2 : Legacy Frame Identifiers are considered valid only if Legacy Support is enabled. + * Note 3 : Skippable Frame Identifiers are considered valid. */ +ZSTDLIB_API unsigned ZSTD_isFrame(const void* buffer, size_t size); + +/*! ZSTD_createDDict_byReference() : + * Create a digested dictionary, ready to start decompression operation without startup delay. + * Dictionary content is referenced, and therefore stays in dictBuffer. + * It is important that dictBuffer outlives DDict, + * it must remain read accessible throughout the lifetime of DDict */ +ZSTDLIB_API ZSTD_DDict* ZSTD_createDDict_byReference(const void* dictBuffer, size_t dictSize); + +/*! ZSTD_DCtx_loadDictionary_byReference() : + * Same as ZSTD_DCtx_loadDictionary(), + * but references `dict` content instead of copying it into `dctx`. + * This saves memory if `dict` remains around., + * However, it's imperative that `dict` remains accessible (and unmodified) while being used, so it must outlive decompression. */ +ZSTDLIB_API size_t ZSTD_DCtx_loadDictionary_byReference(ZSTD_DCtx* dctx, const void* dict, size_t dictSize); + +/*! ZSTD_DCtx_loadDictionary_advanced() : + * Same as ZSTD_DCtx_loadDictionary(), + * but gives direct control over + * how to load the dictionary (by copy ? by reference ?) + * and how to interpret it (automatic ? force raw mode ? full mode only ?). */ +ZSTDLIB_API size_t ZSTD_DCtx_loadDictionary_advanced(ZSTD_DCtx* dctx, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); + +/*! ZSTD_DCtx_refPrefix_advanced() : + * Same as ZSTD_DCtx_refPrefix(), but gives finer control over + * how to interpret prefix content (automatic ? force raw mode (default) ? full mode only ?) */ +ZSTDLIB_API size_t ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx* dctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType); + +/*! ZSTD_DCtx_setMaxWindowSize() : + * Refuses allocating internal buffers for frames requiring a window size larger than provided limit. + * This protects a decoder context from reserving too much memory for itself (potential attack scenario). + * This parameter is only useful in streaming mode, since no internal buffer is allocated in single-pass mode. + * By default, a decompression context accepts all window sizes <= (1 << ZSTD_WINDOWLOG_LIMIT_DEFAULT) + * @return : 0, or an error code (which can be tested using ZSTD_isError()). + */ +ZSTDLIB_API size_t ZSTD_DCtx_setMaxWindowSize(ZSTD_DCtx* dctx, size_t maxWindowSize); + +/*! ZSTD_DCtx_getParameter() : + * Get the requested decompression parameter value, selected by enum ZSTD_dParameter, + * and store it into int* value. + * @return : 0, or an error code (which can be tested with ZSTD_isError()). + */ +ZSTDLIB_API size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value); + +/* ZSTD_d_format + * experimental parameter, + * allowing selection between ZSTD_format_e input compression formats + */ +#define ZSTD_d_format ZSTD_d_experimentalParam1 +/* ZSTD_d_stableOutBuffer + * Experimental parameter. + * Default is 0 == disabled. Set to 1 to enable. + * + * Tells the decompressor that the ZSTD_outBuffer will ALWAYS be the same + * between calls, except for the modifications that zstd makes to pos (the + * caller must not modify pos). This is checked by the decompressor, and + * decompression will fail if it ever changes. Therefore the ZSTD_outBuffer + * MUST be large enough to fit the entire decompressed frame. This will be + * checked when the frame content size is known. The data in the ZSTD_outBuffer + * in the range [dst, dst + pos) MUST not be modified during decompression + * or you will get data corruption. + * + * When this flags is enabled zstd won't allocate an output buffer, because + * it can write directly to the ZSTD_outBuffer, but it will still allocate + * an input buffer large enough to fit any compressed block. This will also + * avoid the memcpy() from the internal output buffer to the ZSTD_outBuffer. + * If you need to avoid the input buffer allocation use the buffer-less + * streaming API. + * + * NOTE: So long as the ZSTD_outBuffer always points to valid memory, using + * this flag is ALWAYS memory safe, and will never access out-of-bounds + * memory. However, decompression WILL fail if you violate the preconditions. + * + * WARNING: The data in the ZSTD_outBuffer in the range [dst, dst + pos) MUST + * not be modified during decompression or you will get data corruption. This + * is because zstd needs to reference data in the ZSTD_outBuffer to regenerate + * matches. Normally zstd maintains its own buffer for this purpose, but passing + * this flag tells zstd to use the user provided buffer. + */ +#define ZSTD_d_stableOutBuffer ZSTD_d_experimentalParam2 + +/* ZSTD_d_forceIgnoreChecksum + * Experimental parameter. + * Default is 0 == disabled. Set to 1 to enable + * + * Tells the decompressor to skip checksum validation during decompression, regardless + * of whether checksumming was specified during compression. This offers some + * slight performance benefits, and may be useful for debugging. + * Param has values of type ZSTD_forceIgnoreChecksum_e + */ +#define ZSTD_d_forceIgnoreChecksum ZSTD_d_experimentalParam3 + +/* ZSTD_d_refMultipleDDicts + * Experimental parameter. + * Default is 0 == disabled. Set to 1 to enable + * + * If enabled and dctx is allocated on the heap, then additional memory will be allocated + * to store references to multiple ZSTD_DDict. That is, multiple calls of ZSTD_refDDict() + * using a given ZSTD_DCtx, rather than overwriting the previous DDict reference, will instead + * store all references. At decompression time, the appropriate dictID is selected + * from the set of DDicts based on the dictID in the frame. + * + * Usage is simply calling ZSTD_refDDict() on multiple dict buffers. + * + * Param has values of byte ZSTD_refMultipleDDicts_e + * + * WARNING: Enabling this parameter and calling ZSTD_DCtx_refDDict(), will trigger memory + * allocation for the hash table. ZSTD_freeDCtx() also frees this memory. + * Memory is allocated as per ZSTD_DCtx::customMem. + * + * Although this function allocates memory for the table, the user is still responsible for + * memory management of the underlying ZSTD_DDict* themselves. + */ +#define ZSTD_d_refMultipleDDicts ZSTD_d_experimentalParam4 + + +/*! ZSTD_DCtx_setFormat() : + * Instruct the decoder context about what kind of data to decode next. + * This instruction is mandatory to decode data without a fully-formed header, + * such ZSTD_f_zstd1_magicless for example. + * @return : 0, or an error code (which can be tested using ZSTD_isError()). */ +ZSTDLIB_API size_t ZSTD_DCtx_setFormat(ZSTD_DCtx* dctx, ZSTD_format_e format); + +/*! ZSTD_decompressStream_simpleArgs() : + * Same as ZSTD_decompressStream(), + * but using only integral types as arguments. + * This can be helpful for binders from dynamic languages + * which have troubles handling structures containing memory pointers. + */ +ZSTDLIB_API size_t ZSTD_decompressStream_simpleArgs ( + ZSTD_DCtx* dctx, + void* dst, size_t dstCapacity, size_t* dstPos, + const void* src, size_t srcSize, size_t* srcPos); + + +/******************************************************************** +* Advanced streaming functions +* Warning : most of these functions are now redundant with the Advanced API. +* Once Advanced API reaches "stable" status, +* redundant functions will be deprecated, and then at some point removed. +********************************************************************/ + +/*===== Advanced Streaming compression functions =====*/ + +/*! ZSTD_initCStream_srcSize() : + * This function is deprecated, and equivalent to: + * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); + * ZSTD_CCtx_refCDict(zcs, NULL); // clear the dictionary (if any) + * ZSTD_CCtx_setParameter(zcs, ZSTD_c_compressionLevel, compressionLevel); + * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize); + * + * pledgedSrcSize must be correct. If it is not known at init time, use + * ZSTD_CONTENTSIZE_UNKNOWN. Note that, for compatibility with older programs, + * "0" also disables frame content size field. It may be enabled in the future. + * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ +ZSTDLIB_API size_t +ZSTD_initCStream_srcSize(ZSTD_CStream* zcs, + int compressionLevel, + unsigned long long pledgedSrcSize); + +/*! ZSTD_initCStream_usingDict() : + * This function is deprecated, and is equivalent to: + * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); + * ZSTD_CCtx_setParameter(zcs, ZSTD_c_compressionLevel, compressionLevel); + * ZSTD_CCtx_loadDictionary(zcs, dict, dictSize); + * + * Creates of an internal CDict (incompatible with static CCtx), except if + * dict == NULL or dictSize < 8, in which case no dict is used. + * Note: dict is loaded with ZSTD_dct_auto (treated as a full zstd dictionary if + * it begins with ZSTD_MAGIC_DICTIONARY, else as raw content) and ZSTD_dlm_byCopy. + * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ +ZSTDLIB_API size_t +ZSTD_initCStream_usingDict(ZSTD_CStream* zcs, + const void* dict, size_t dictSize, + int compressionLevel); + +/*! ZSTD_initCStream_advanced() : + * This function is deprecated, and is approximately equivalent to: + * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); + * // Pseudocode: Set each zstd parameter and leave the rest as-is. + * for ((param, value) : params) { + * ZSTD_CCtx_setParameter(zcs, param, value); + * } + * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize); + * ZSTD_CCtx_loadDictionary(zcs, dict, dictSize); + * + * dict is loaded with ZSTD_dct_auto and ZSTD_dlm_byCopy. + * pledgedSrcSize must be correct. + * If srcSize is not known at init time, use value ZSTD_CONTENTSIZE_UNKNOWN. + * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ +ZSTDLIB_API size_t +ZSTD_initCStream_advanced(ZSTD_CStream* zcs, + const void* dict, size_t dictSize, + ZSTD_parameters params, + unsigned long long pledgedSrcSize); + +/*! ZSTD_initCStream_usingCDict() : + * This function is deprecated, and equivalent to: + * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); + * ZSTD_CCtx_refCDict(zcs, cdict); + * + * note : cdict will just be referenced, and must outlive compression session + * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ +ZSTDLIB_API size_t ZSTD_initCStream_usingCDict(ZSTD_CStream* zcs, const ZSTD_CDict* cdict); + +/*! ZSTD_initCStream_usingCDict_advanced() : + * This function is DEPRECATED, and is approximately equivalent to: + * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); + * // Pseudocode: Set each zstd frame parameter and leave the rest as-is. + * for ((fParam, value) : fParams) { + * ZSTD_CCtx_setParameter(zcs, fParam, value); + * } + * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize); + * ZSTD_CCtx_refCDict(zcs, cdict); + * + * same as ZSTD_initCStream_usingCDict(), with control over frame parameters. + * pledgedSrcSize must be correct. If srcSize is not known at init time, use + * value ZSTD_CONTENTSIZE_UNKNOWN. + * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ +ZSTDLIB_API size_t +ZSTD_initCStream_usingCDict_advanced(ZSTD_CStream* zcs, + const ZSTD_CDict* cdict, + ZSTD_frameParameters fParams, + unsigned long long pledgedSrcSize); + +/*! ZSTD_resetCStream() : + * This function is deprecated, and is equivalent to: + * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); + * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize); + * + * start a new frame, using same parameters from previous frame. + * This is typically useful to skip dictionary loading stage, since it will re-use it in-place. + * Note that zcs must be init at least once before using ZSTD_resetCStream(). + * If pledgedSrcSize is not known at reset time, use macro ZSTD_CONTENTSIZE_UNKNOWN. + * If pledgedSrcSize > 0, its value must be correct, as it will be written in header, and controlled at the end. + * For the time being, pledgedSrcSize==0 is interpreted as "srcSize unknown" for compatibility with older programs, + * but it will change to mean "empty" in future version, so use macro ZSTD_CONTENTSIZE_UNKNOWN instead. + * @return : 0, or an error code (which can be tested using ZSTD_isError()) + * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ +ZSTDLIB_API size_t ZSTD_resetCStream(ZSTD_CStream* zcs, unsigned long long pledgedSrcSize); + + +typedef struct { + unsigned long long ingested; /* nb input bytes read and buffered */ + unsigned long long consumed; /* nb input bytes actually compressed */ + unsigned long long produced; /* nb of compressed bytes generated and buffered */ + unsigned long long flushed; /* nb of compressed bytes flushed : not provided; can be tracked from caller side */ + unsigned currentJobID; /* MT only : latest started job nb */ + unsigned nbActiveWorkers; /* MT only : nb of workers actively compressing at probe time */ +} ZSTD_frameProgression; + +/* ZSTD_getFrameProgression() : + * tells how much data has been ingested (read from input) + * consumed (input actually compressed) and produced (output) for current frame. + * Note : (ingested - consumed) is amount of input data buffered internally, not yet compressed. + * Aggregates progression inside active worker threads. + */ +ZSTDLIB_API ZSTD_frameProgression ZSTD_getFrameProgression(const ZSTD_CCtx* cctx); + +/*! ZSTD_toFlushNow() : + * Tell how many bytes are ready to be flushed immediately. + * Useful for multithreading scenarios (nbWorkers >= 1). + * Probe the oldest active job, defined as oldest job not yet entirely flushed, + * and check its output buffer. + * @return : amount of data stored in oldest job and ready to be flushed immediately. + * if @return == 0, it means either : + * + there is no active job (could be checked with ZSTD_frameProgression()), or + * + oldest job is still actively compressing data, + * but everything it has produced has also been flushed so far, + * therefore flush speed is limited by production speed of oldest job + * irrespective of the speed of concurrent (and newer) jobs. + */ +ZSTDLIB_API size_t ZSTD_toFlushNow(ZSTD_CCtx* cctx); + + +/*===== Advanced Streaming decompression functions =====*/ + +/*! + * This function is deprecated, and is equivalent to: + * + * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only); + * ZSTD_DCtx_loadDictionary(zds, dict, dictSize); + * + * note: no dictionary will be used if dict == NULL or dictSize < 8 + * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ +ZSTDLIB_API size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* dict, size_t dictSize); + +/*! + * This function is deprecated, and is equivalent to: + * + * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only); + * ZSTD_DCtx_refDDict(zds, ddict); + * + * note : ddict is referenced, it must outlive decompression session + * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ +ZSTDLIB_API size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* zds, const ZSTD_DDict* ddict); + +/*! + * This function is deprecated, and is equivalent to: + * + * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only); + * + * re-use decompression parameters from previous init; saves dictionary loading + * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ +ZSTDLIB_API size_t ZSTD_resetDStream(ZSTD_DStream* zds); + + +/********************************************************************* +* Buffer-less and synchronous inner streaming functions +* +* This is an advanced API, giving full control over buffer management, for users which need direct control over memory. +* But it's also a complex one, with several restrictions, documented below. +* Prefer normal streaming API for an easier experience. +********************************************************************* */ + +/** + Buffer-less streaming compression (synchronous mode) + + A ZSTD_CCtx object is required to track streaming operations. + Use ZSTD_createCCtx() / ZSTD_freeCCtx() to manage resource. + ZSTD_CCtx object can be re-used multiple times within successive compression operations. + + Start by initializing a context. + Use ZSTD_compressBegin(), or ZSTD_compressBegin_usingDict() for dictionary compression, + or ZSTD_compressBegin_advanced(), for finer parameter control. + It's also possible to duplicate a reference context which has already been initialized, using ZSTD_copyCCtx() + + Then, consume your input using ZSTD_compressContinue(). + There are some important considerations to keep in mind when using this advanced function : + - ZSTD_compressContinue() has no internal buffer. It uses externally provided buffers only. + - Interface is synchronous : input is consumed entirely and produces 1+ compressed blocks. + - Caller must ensure there is enough space in `dst` to store compressed data under worst case scenario. + Worst case evaluation is provided by ZSTD_compressBound(). + ZSTD_compressContinue() doesn't guarantee recover after a failed compression. + - ZSTD_compressContinue() presumes prior input ***is still accessible and unmodified*** (up to maximum distance size, see WindowLog). + It remembers all previous contiguous blocks, plus one separated memory segment (which can itself consists of multiple contiguous blocks) + - ZSTD_compressContinue() detects that prior input has been overwritten when `src` buffer overlaps. + In which case, it will "discard" the relevant memory section from its history. + + Finish a frame with ZSTD_compressEnd(), which will write the last block(s) and optional checksum. + It's possible to use srcSize==0, in which case, it will write a final empty block to end the frame. + Without last block mark, frames are considered unfinished (hence corrupted) by compliant decoders. + + `ZSTD_CCtx` object can be re-used (ZSTD_compressBegin()) to compress again. +*/ + +/*===== Buffer-less streaming compression functions =====*/ +ZSTDLIB_API size_t ZSTD_compressBegin(ZSTD_CCtx* cctx, int compressionLevel); +ZSTDLIB_API size_t ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel); +ZSTDLIB_API size_t ZSTD_compressBegin_advanced(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, ZSTD_parameters params, unsigned long long pledgedSrcSize); /**< pledgedSrcSize : If srcSize is not known at init time, use ZSTD_CONTENTSIZE_UNKNOWN */ +ZSTDLIB_API size_t ZSTD_compressBegin_usingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); /**< note: fails if cdict==NULL */ +ZSTDLIB_API size_t ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx* const cctx, const ZSTD_CDict* const cdict, ZSTD_frameParameters const fParams, unsigned long long const pledgedSrcSize); /* compression parameters are already set within cdict. pledgedSrcSize must be correct. If srcSize is not known, use macro ZSTD_CONTENTSIZE_UNKNOWN */ +ZSTDLIB_API size_t ZSTD_copyCCtx(ZSTD_CCtx* cctx, const ZSTD_CCtx* preparedCCtx, unsigned long long pledgedSrcSize); /**< note: if pledgedSrcSize is not known, use ZSTD_CONTENTSIZE_UNKNOWN */ + +ZSTDLIB_API size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); +ZSTDLIB_API size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); + + +/** + Buffer-less streaming decompression (synchronous mode) + + A ZSTD_DCtx object is required to track streaming operations. + Use ZSTD_createDCtx() / ZSTD_freeDCtx() to manage it. + A ZSTD_DCtx object can be re-used multiple times. + + First typical operation is to retrieve frame parameters, using ZSTD_getFrameHeader(). + Frame header is extracted from the beginning of compressed frame, so providing only the frame's beginning is enough. + Data fragment must be large enough to ensure successful decoding. + `ZSTD_frameHeaderSize_max` bytes is guaranteed to always be large enough. + @result : 0 : successful decoding, the `ZSTD_frameHeader` structure is correctly filled. + >0 : `srcSize` is too small, please provide at least @result bytes on next attempt. + errorCode, which can be tested using ZSTD_isError(). + + It fills a ZSTD_frameHeader structure with important information to correctly decode the frame, + such as the dictionary ID, content size, or maximum back-reference distance (`windowSize`). + Note that these values could be wrong, either because of data corruption, or because a 3rd party deliberately spoofs false information. + As a consequence, check that values remain within valid application range. + For example, do not allocate memory blindly, check that `windowSize` is within expectation. + Each application can set its own limits, depending on local restrictions. + For extended interoperability, it is recommended to support `windowSize` of at least 8 MB. + + ZSTD_decompressContinue() needs previous data blocks during decompression, up to `windowSize` bytes. + ZSTD_decompressContinue() is very sensitive to contiguity, + if 2 blocks don't follow each other, make sure that either the compressor breaks contiguity at the same place, + or that previous contiguous segment is large enough to properly handle maximum back-reference distance. + There are multiple ways to guarantee this condition. + + The most memory efficient way is to use a round buffer of sufficient size. + Sufficient size is determined by invoking ZSTD_decodingBufferSize_min(), + which can @return an error code if required value is too large for current system (in 32-bits mode). + In a round buffer methodology, ZSTD_decompressContinue() decompresses each block next to previous one, + up to the moment there is not enough room left in the buffer to guarantee decoding another full block, + which maximum size is provided in `ZSTD_frameHeader` structure, field `blockSizeMax`. + At which point, decoding can resume from the beginning of the buffer. + Note that already decoded data stored in the buffer should be flushed before being overwritten. + + There are alternatives possible, for example using two or more buffers of size `windowSize` each, though they consume more memory. + + Finally, if you control the compression process, you can also ignore all buffer size rules, + as long as the encoder and decoder progress in "lock-step", + aka use exactly the same buffer sizes, break contiguity at the same place, etc. + + Once buffers are setup, start decompression, with ZSTD_decompressBegin(). + If decompression requires a dictionary, use ZSTD_decompressBegin_usingDict() or ZSTD_decompressBegin_usingDDict(). + + Then use ZSTD_nextSrcSizeToDecompress() and ZSTD_decompressContinue() alternatively. + ZSTD_nextSrcSizeToDecompress() tells how many bytes to provide as 'srcSize' to ZSTD_decompressContinue(). + ZSTD_decompressContinue() requires this _exact_ amount of bytes, or it will fail. + + @result of ZSTD_decompressContinue() is the number of bytes regenerated within 'dst' (necessarily <= dstCapacity). + It can be zero : it just means ZSTD_decompressContinue() has decoded some metadata item. + It can also be an error code, which can be tested with ZSTD_isError(). + + A frame is fully decoded when ZSTD_nextSrcSizeToDecompress() returns zero. + Context can then be reset to start a new decompression. + + Note : it's possible to know if next input to present is a header or a block, using ZSTD_nextInputType(). + This information is not required to properly decode a frame. + + == Special case : skippable frames == + + Skippable frames allow integration of user-defined data into a flow of concatenated frames. + Skippable frames will be ignored (skipped) by decompressor. + The format of skippable frames is as follows : + a) Skippable frame ID - 4 Bytes, Little endian format, any value from 0x184D2A50 to 0x184D2A5F + b) Frame Size - 4 Bytes, Little endian format, unsigned 32-bits + c) Frame Content - any content (User Data) of length equal to Frame Size + For skippable frames ZSTD_getFrameHeader() returns zfhPtr->frameType==ZSTD_skippableFrame. + For skippable frames ZSTD_decompressContinue() always returns 0 : it only skips the content. +*/ + +/*===== Buffer-less streaming decompression functions =====*/ +typedef enum { ZSTD_frame, ZSTD_skippableFrame } ZSTD_frameType_e; +typedef struct { + unsigned long long frameContentSize; /* if == ZSTD_CONTENTSIZE_UNKNOWN, it means this field is not available. 0 means "empty" */ + unsigned long long windowSize; /* can be very large, up to <= frameContentSize */ + unsigned blockSizeMax; + ZSTD_frameType_e frameType; /* if == ZSTD_skippableFrame, frameContentSize is the size of skippable content */ + unsigned headerSize; + unsigned dictID; + unsigned checksumFlag; +} ZSTD_frameHeader; + +/*! ZSTD_getFrameHeader() : + * decode Frame Header, or requires larger `srcSize`. + * @return : 0, `zfhPtr` is correctly filled, + * >0, `srcSize` is too small, value is wanted `srcSize` amount, + * or an error code, which can be tested using ZSTD_isError() */ +ZSTDLIB_API size_t ZSTD_getFrameHeader(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize); /**< doesn't consume input */ +/*! ZSTD_getFrameHeader_advanced() : + * same as ZSTD_getFrameHeader(), + * with added capability to select a format (like ZSTD_f_zstd1_magicless) */ +ZSTDLIB_API size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize, ZSTD_format_e format); +ZSTDLIB_API size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long long frameContentSize); /**< when frame content size is not known, pass in frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN */ + +ZSTDLIB_API size_t ZSTD_decompressBegin(ZSTD_DCtx* dctx); +ZSTDLIB_API size_t ZSTD_decompressBegin_usingDict(ZSTD_DCtx* dctx, const void* dict, size_t dictSize); +ZSTDLIB_API size_t ZSTD_decompressBegin_usingDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict); + +ZSTDLIB_API size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx); +ZSTDLIB_API size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); + +/* misc */ +ZSTDLIB_API void ZSTD_copyDCtx(ZSTD_DCtx* dctx, const ZSTD_DCtx* preparedDCtx); +typedef enum { ZSTDnit_frameHeader, ZSTDnit_blockHeader, ZSTDnit_block, ZSTDnit_lastBlock, ZSTDnit_checksum, ZSTDnit_skippableFrame } ZSTD_nextInputType_e; +ZSTDLIB_API ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx); + + + + +/* ============================ */ +/** Block level API */ +/* ============================ */ + +/*! + Block functions produce and decode raw zstd blocks, without frame metadata. + Frame metadata cost is typically ~12 bytes, which can be non-negligible for very small blocks (< 100 bytes). + But users will have to take in charge needed metadata to regenerate data, such as compressed and content sizes. + + A few rules to respect : + - Compressing and decompressing require a context structure + + Use ZSTD_createCCtx() and ZSTD_createDCtx() + - It is necessary to init context before starting + + compression : any ZSTD_compressBegin*() variant, including with dictionary + + decompression : any ZSTD_decompressBegin*() variant, including with dictionary + + copyCCtx() and copyDCtx() can be used too + - Block size is limited, it must be <= ZSTD_getBlockSize() <= ZSTD_BLOCKSIZE_MAX == 128 KB + + If input is larger than a block size, it's necessary to split input data into multiple blocks + + For inputs larger than a single block, consider using regular ZSTD_compress() instead. + Frame metadata is not that costly, and quickly becomes negligible as source size grows larger than a block. + - When a block is considered not compressible enough, ZSTD_compressBlock() result will be 0 (zero) ! + ===> In which case, nothing is produced into `dst` ! + + User __must__ test for such outcome and deal directly with uncompressed data + + A block cannot be declared incompressible if ZSTD_compressBlock() return value was != 0. + Doing so would mess up with statistics history, leading to potential data corruption. + + ZSTD_decompressBlock() _doesn't accept uncompressed data as input_ !! + + In case of multiple successive blocks, should some of them be uncompressed, + decoder must be informed of their existence in order to follow proper history. + Use ZSTD_insertBlock() for such a case. +*/ + +/*===== Raw zstd block functions =====*/ +ZSTDLIB_API size_t ZSTD_getBlockSize (const ZSTD_CCtx* cctx); +ZSTDLIB_API size_t ZSTD_compressBlock (ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); +ZSTDLIB_API size_t ZSTD_decompressBlock(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); +ZSTDLIB_API size_t ZSTD_insertBlock (ZSTD_DCtx* dctx, const void* blockStart, size_t blockSize); /**< insert uncompressed block into `dctx` history. Useful for multi-blocks decompression. */ + + +#endif /* ZSTD_H_ZSTD_STATIC_LINKING_ONLY */ + +#if defined (__cplusplus) +} +#endif From 554b31abd7a2adc08bbfe8b1a54f323511c8162d Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 12 May 2021 21:21:48 -0400 Subject: [PATCH 25/57] fixed memory leak --- Connection.cc | 1 + mutilate.cc | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Connection.cc b/Connection.cc index f7b236b..e3d3158 100644 --- a/Connection.cc +++ b/Connection.cc @@ -508,6 +508,7 @@ int Connection::issue_getsetorset(double now) { break; } + } } diff --git a/mutilate.cc b/mutilate.cc index 9a66694..b184f46 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -680,7 +680,7 @@ void go(const vector& servers, options_t& options, } #endif - ConcurrentQueue *trace_queue = new ConcurrentQueue; + ConcurrentQueue *trace_queue = new ConcurrentQueue(200000000); struct reader_data *rdata = (struct reader_data*)malloc(sizeof(struct reader_data)); rdata->trace_queue = trace_queue; if (options.read_file) { @@ -911,9 +911,14 @@ void* reader_thread(void *arg) { strncpy(line_p,line,strlen(line)); } string full_line(line); - trace_queue->enqueue(full_line); + bool res = trace_queue->try_enqueue(full_line); + while (!res) { + usleep(10); + res = trace_queue->try_enqueue(full_line); + nwrites++; + } n++; - if (n % 1000000 == 0) fprintf(stderr,"decompressed requests: %lu, writes: %lu\n",n,nwrites); + if (n % 1000000 == 0) fprintf(stderr,"decompressed requests: %lu, waits: %lu\n",n,nwrites); } free(line_p); From a70477a8efb5f55ddb45cc283e25b9412300311d Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 9 Jun 2021 08:39:35 -0400 Subject: [PATCH 26/57] minor updates --- Connection.cc | 4 +++- mutilate.cc | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Connection.cc b/Connection.cc index e3d3158..9d3402d 100644 --- a/Connection.cc +++ b/Connection.cc @@ -876,7 +876,9 @@ void Connection::drive_write_machine(double now) { double delay; struct timeval tv; - if (check_exit_condition(now)) return; + if (check_exit_condition(now)) { + return; + } while (1) { switch (write_state) { diff --git a/mutilate.cc b/mutilate.cc index b184f46..1637a7f 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -680,13 +680,17 @@ void go(const vector& servers, options_t& options, } #endif - ConcurrentQueue *trace_queue = new ConcurrentQueue(200000000); + ConcurrentQueue *trace_queue = new ConcurrentQueue(20000000); struct reader_data *rdata = (struct reader_data*)malloc(sizeof(struct reader_data)); + memset(rdata,0,sizeof(struct reader_data)); rdata->trace_queue = trace_queue; + pthread_t rtid; if (options.read_file) { - pthread_t tid; rdata->trace_filename = options.file_name; - pthread_create(&tid, NULL,reader_thread,rdata); + int error = 0; + if ((error = pthread_create(&rtid, NULL,reader_thread,rdata)) != 0) { + printf("reader thread failed to be created with error code %d\n", error); + } usleep(10); } @@ -762,6 +766,8 @@ void go(const vector& servers, options_t& options, stats.accumulate(*cs); delete cs; } + delete trace_queue; + } else if (options.threads == 1) { do_mutilate(servers, options, stats, trace_queue, true #ifdef HAVE_LIBZMQ @@ -952,6 +958,7 @@ void* reader_thread(void *arg) { trace_queue->enqueue(eof); } } + return NULL; } From 81996f1eab3d84f5874e240885b0a3902ce49a14 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Tue, 6 Jul 2021 11:24:35 -0400 Subject: [PATCH 27/57] updated string and sock handling --- Connection.cc | 132 ++++++++++++++++++-------------------------------- Connection.h | 2 + common.h | 2 +- mutilate.cc | 28 +++++++++-- 4 files changed, 75 insertions(+), 89 deletions(-) diff --git a/Connection.cc b/Connection.cc index 9d3402d..5f0faa6 100644 --- a/Connection.cc +++ b/Connection.cc @@ -48,10 +48,6 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, eof = 0; keygen = new KeyGenerator(keysize, options.records); - //if (options.read_file && (options.getset || options.getsetorset)) { - // kvfile.open(options.file_name); - // - //} if (options.lambda <= 0) { iagen = createGenerator("0"); @@ -68,44 +64,38 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, last_miss = 0; + + timer = evtimer_new(base, timer_cb, this); +} + +int Connection::do_connect() { + + int connected = 0; if (options.unix_socket) { - srand(time(NULL)); - int tries = 10000; - int connected = 0; - int s = 1; - for (int i = 0; i < tries; i++) { - bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); - bufferevent_setcb(bev, bev_read_cb, bev_write_cb, bev_event_cb, this); - bufferevent_enable(bev, EV_READ | EV_WRITE); - + bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, bev_read_cb, bev_write_cb, bev_event_cb, this); + bufferevent_enable(bev, EV_READ | EV_WRITE); - struct sockaddr_un sin; - memset(&sin, 0, sizeof(sin)); - sin.sun_family = AF_LOCAL; - strcpy(sin.sun_path, hostname.c_str()); + struct sockaddr_un sin; + memset(&sin, 0, sizeof(sin)); + sin.sun_family = AF_LOCAL; + strcpy(sin.sun_path, hostname.c_str()); - static int addrlen; - addrlen = sizeof(sin); + static int addrlen; + addrlen = sizeof(sin); - if (bufferevent_socket_connect(bev, (struct sockaddr*)&sin, addrlen) == 0) { - connected = 1; - if (options.binary) { - prot = new ProtocolBinary(options, this, bev); - } else if (options.redis) { - prot = new ProtocolRESP(options, this, bev); - } else { - prot = new ProtocolAscii(options, this, bev); - } - break; + if (bufferevent_socket_connect(bev, (struct sockaddr*)&sin, addrlen) == 0) { + connected = 1; + if (options.binary) { + prot = new ProtocolBinary(options, this, bev); + } else if (options.redis) { + prot = new ProtocolRESP(options, this, bev); + } else { + prot = new ProtocolAscii(options, this, bev); } + } else { bufferevent_free(bev); - s = rand() % 10; - usleep(s); - - } - if (connected == 0) { - DIE("bufferevent_socket_connect()"); } } else { bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); @@ -119,14 +109,16 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, } else { prot = new ProtocolAscii(options, this, bev); } - if (bufferevent_socket_connect_hostname(bev, evdns, AF_UNSPEC, - hostname.c_str(), - atoi(port.c_str()))) { - DIE("bufferevent_socket_connect_hostname()"); + if (bufferevent_socket_connect_hostname(bev, evdns, AF_UNSPEC, + hostname.c_str(), + atoi(port.c_str())) == 0) { + connected = 1; + } else { + bufferevent_free(bev); + connected = 0; } } - - timer = evtimer_new(base, timer_cb, this); + return connected; } /** @@ -188,15 +180,10 @@ void Connection::start_loading() { */ void Connection::issue_something(double now) { char key[256]; - char skey[256]; memset(key,0,256); - memset(skey,0,256); // FIXME: generate key distribution here! string keystr = keygen->generate(lrand48() % options.records); - strncpy(skey, keystr.c_str(),strlen(keystr.c_str())); - if (args.prefix_given) - strncpy(key, options.prefix,strlen(options.prefix)); - strncat(key,skey,strlen(skey)); + strncpy(key, keystr.c_str(),255); if (drand48() < options.update) { int index = lrand48() % (1024 * 1024); @@ -217,17 +204,11 @@ void Connection::issue_getset(double now) { { string keystr; char key[256]; - char skey[256]; memset(key,0,256); - memset(skey,0,256); keystr = keygen->generate(lrand48() % options.records); - strncpy(skey, keystr.c_str(),strlen(keystr.c_str())); - if (args.prefix_given) { - strncpy(key, options.prefix,strlen(options.prefix)); - } - strncat(key,skey,strlen(skey)); + strncpy(key, keystr.c_str(),255); - char log[256]; + char log[1024]; int length = valuesize->generate(); sprintf(log,"%s,%d\n",key,length); write(2,log,strlen(log)); @@ -256,13 +237,8 @@ void Connection::issue_getset(double now) { int vl = atoi(rvaluelen.c_str()); char key[256]; - char skey[256]; memset(key,0,256); - memset(skey,0,256); - strncpy(skey, rKey.c_str(),strlen(rKey.c_str())); - if (args.prefix_given) - strncpy(key, options.prefix,strlen(options.prefix)); - strncat(key,skey,strlen(skey)); + strncpy(key, rKey.c_str(),255); issue_get_with_len(key, vl, now); } @@ -380,17 +356,11 @@ int Connection::issue_getsetorset(double now) { { string keystr; char key[256]; - char skey[256]; memset(key,0,256); - memset(skey,0,256); keystr = keygen->generate(lrand48() % options.records); - strncpy(skey, keystr.c_str(),strlen(keystr.c_str())); - if (args.prefix_given) - strncpy(key, options.prefix,strlen(options.prefix)); - - strncat(key,skey,strlen(skey)); + strncpy(key, keystr.c_str(),255); - char log[256]; + char log[1024]; int length = valuesize->generate(); sprintf(log,"%s,%d\n",key,length); write(2,log,strlen(log)); @@ -480,19 +450,10 @@ int Connection::issue_getsetorset(double now) { } - char key[1024]; - char skey[1024]; - memset(key,0,1024); - memset(skey,0,1024); - strncpy(skey, rKey.c_str(),strlen(rKey.c_str())); + char key[256]; + memset(key,0,256); + strncpy(key, rKey.c_str(),255); - if (args.prefix_given) { - strncpy(key, options.prefix,strlen(options.prefix)); - } - strncat(key,skey,strlen(skey)); - //if (strcmp(key,"100004781") == 0) { - // fprintf(stderr,"ready!\n"); - //} switch(Op) { case 0: @@ -799,7 +760,8 @@ void Connection::event_callback(short events) { int err = bufferevent_socket_get_dns_error(bev); //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); - return; + fprintf(stderr,"Got an error: %s\n", + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); //stats.print_header(); //stats.print_stats("read", stats.get_sampler); @@ -827,8 +789,9 @@ void Connection::event_callback(short events) { //printf("TX %10" PRIu64 " bytes : %6.1f MB/s\n", // stats.tx_bytes, // (double) stats.tx_bytes / 1024 / 1024 / (stats.stop - stats.start)); - - DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + + //if ( + //DIE("BEV_EVENT_ERROR: %s", strerror(errno)); } else if (events & BEV_EVENT_EOF) { //stats.print_header(); @@ -1101,6 +1064,7 @@ void Connection::timer_callback() { drive_write_machine(); } /* The follow are C trampolines for libevent callbacks. */ void bev_event_cb(struct bufferevent *bev, short events, void *ptr) { + Connection* conn = (Connection*) ptr; conn->event_callback(events); } diff --git a/Connection.h b/Connection.h index 20ed4e9..0e5b5b3 100644 --- a/Connection.h +++ b/Connection.h @@ -40,6 +40,8 @@ class Connection { bool sampling = true); ~Connection(); + int do_connect(); + double start_time; // Time when this connection began operations. ConnectionStats stats; options_t options; diff --git a/common.h b/common.h index 05e10df..8d21e69 100644 --- a/common.h +++ b/common.h @@ -19,7 +19,7 @@ #include // strerror #include // errno #include // stat -#include +#include "zstd.h" /* * Define the returned error code from utility functions. diff --git a/mutilate.cc b/mutilate.cc index 1637a7f..5cd610d 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -913,9 +913,7 @@ void* reader_thread(void *arg) { char *line = NULL; char *line_p = (char*)calloc(2048,sizeof(char)); while ((line = strsep(&trace,"\n"))) { - if (strlen(line) < 2048) { - strncpy(line_p,line,strlen(line)); - } + strncpy(line_p,line,2048); string full_line(line); bool res = trace_queue->try_enqueue(full_line); while (!res) { @@ -1039,12 +1037,34 @@ void do_mutilate(const vector& servers, options_t& options, int conns = args.measure_connections_given ? args.measure_connections_arg : options.connections; + srand(time(NULL)); for (int c = 0; c < conns; c++) { Connection* conn = new Connection(base, evdns, hostname, port, options, trace_queue, args.agentmode_given ? false : true); - connections.push_back(conn); + int tries = 20; + int connected = 0; + int s = 2; + for (int i = 0; i < tries; i++) { + + int ret = conn->do_connect(); + if (ret) { + connected = 1; + break; + } + s *= 2; + int d = s + rand() % 100; + + fprintf(stderr,"conn: %d, sleeping %d\n",c,d); + usleep(d); + } + if (connected) { + connections.push_back(conn); + } else { + fprintf(stderr,"conn: %d, not connected!!\n",c); + + } if (c == 0) server_lead.push_back(conn); } } From 045864b3577abd0ca9e40783ef98e603bf80a074 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Tue, 13 Jul 2021 21:16:25 -0400 Subject: [PATCH 28/57] lots of updates --- Connection.cc | 743 +++++++++++++++++++++++++++++++++++--------------- Connection.h | 14 +- Operation.h | 3 + Protocol.cc | 27 +- Protocol.h | 24 +- SConstruct | 21 +- mutilate.cc | 25 +- 7 files changed, 594 insertions(+), 263 deletions(-) diff --git a/Connection.cc b/Connection.cc index 5f0faa6..7511363 100644 --- a/Connection.cc +++ b/Connection.cc @@ -3,6 +3,8 @@ #include #include +#include + #include #include #include @@ -27,9 +29,107 @@ using namespace moodycamel; +std::hash hashstr; extern ifstream kvfile; extern pthread_mutex_t flock; +extern pthread_mutex_t *item_locks; +extern int item_lock_hashpower; + +#define hashsize(n) ((unsigned long int)1<<(n)) +#define hashmask(n) (hashsize(n)-1) + +pthread_mutex_t cid_lock = PTHREAD_MUTEX_INITIALIZER; +uint32_t connids = 0; + +pthread_mutex_t opaque_lock = PTHREAD_MUTEX_INITIALIZER; +uint32_t g_opaque = 0; + +void item_lock(size_t hv, uint32_t cid) { + //char out[128]; + //sprintf(out,"conn: %u, locking %lu\n",cid,hv); + //write(2,out,strlen(out)); + pthread_mutex_lock(&item_locks[hv & hashmask(item_lock_hashpower)]); +} + +void item_unlock(size_t hv, uint32_t cid) { + //char out[128]; + //sprintf(out,"conn: %u, unlocking %lu\n",cid,hv); + //write(2,out,strlen(out)); + pthread_mutex_unlock(&item_locks[hv & hashmask(item_lock_hashpower)]); +} + +void *item_trylock(uint32_t hv, uint32_t cid) { + pthread_mutex_t *lock = &item_locks[hv & hashmask(item_lock_hashpower)]; + if (pthread_mutex_trylock(lock) == 0) { + //char out[128]; + //sprintf(out,"conn: %u, locking %u\n",cid,hv); + //write(2,out,strlen(out)); + return lock; + } + return NULL; +} + +void item_trylock_unlock(void *lock, uint32_t cid) { + //char out[128]; + //sprintf(out,"conn: %u, unlocking\n",cid); + //write(2,out,strlen(out)); + pthread_mutex_unlock((pthread_mutex_t *) lock); +} + +void Connection::output_op(Operation *op, int type, bool found) { + char output[1024]; + char k[256]; + char a[256]; + char s[256]; + memset(k,0,256); + memset(a,0,256); + memset(s,0,256); + strcpy(k,op->key.c_str()); + switch (type) { + case 0: //get + sprintf(a,"issue_get"); + break; + case 1: //set + sprintf(a,"issue_set"); + break; + case 2: //resp + sprintf(a,"resp"); + break; + } + switch(read_state) { + case INIT_READ: + sprintf(s,"init"); + break; + case CONN_SETUP: + sprintf(s,"setup"); + break; + case LOADING: + sprintf(s,"load"); + break; + case IDLE: + sprintf(s,"idle"); + break; + case WAITING_FOR_GET: + sprintf(s,"waiting for get"); + break; + case WAITING_FOR_SET: + sprintf(s,"waiting for set"); + break; + case WAITING_FOR_DELETE: + sprintf(s,"waiting for del"); + break; + case MAX_READ_STATE: + sprintf(s,"max"); + break; + } + if (type == 2) { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, found: %d, type: %d\n",cid,a,k,op->opaque,found,op->type); + } else { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, type: %d\n",cid,a,k,op->opaque,op->type); + } + write(2,output,strlen(output)); +} /** * Create a new connection to a server endpoint. @@ -63,8 +163,9 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, last_tx = last_rx = 0.0; last_miss = 0; - - + pthread_mutex_lock(&cid_lock); + cid = connids++; + pthread_mutex_unlock(&cid_lock); timer = evtimer_new(base, timer_cb, this); } @@ -82,10 +183,10 @@ int Connection::do_connect() { sin.sun_family = AF_LOCAL; strcpy(sin.sun_path, hostname.c_str()); - static int addrlen; + int addrlen; addrlen = sizeof(sin); - - if (bufferevent_socket_connect(bev, (struct sockaddr*)&sin, addrlen) == 0) { + int err = bufferevent_socket_connect(bev, (struct sockaddr*)&sin, addrlen); + if (err == 0) { connected = 1; if (options.binary) { prot = new ProtocolBinary(options, this, bev); @@ -95,7 +196,11 @@ int Connection::do_connect() { prot = new ProtocolAscii(options, this, bev); } } else { + connected = 0; + err = errno; + fprintf(stderr,"error %s\n",strerror(err)); bufferevent_free(bev); + //event_base_free(_evbase_ptr); } } else { bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); @@ -377,101 +482,96 @@ int Connection::issue_getsetorset(double now) { string rKeySize; string rvaluelen; - bool res = trace_queue->try_dequeue(line); if (res) { if (line.compare("EOF") == 0) { eof = 1; return 1; } - /* - pthread_mutex_lock(&flock); - if (kvfile.good()) { - getline(kvfile,line); - pthread_mutex_unlock(&flock); - } - else { - pthread_mutex_unlock(&flock); - return 1; - } - */ - stringstream ss(line); - int Op = 0; - int vl = 0; - - if (options.twitter_trace == 1) { - getline( ss, rT, ',' ); - getline( ss, rKey, ',' ); - getline( ss, rKeySize, ',' ); - getline( ss, rvaluelen, ',' ); - getline( ss, rApp, ',' ); - getline( ss, rOp, ',' ); - vl = atoi(rvaluelen.c_str()); - if (vl < 1) vl = 1; - if (vl > 524000) vl = 524000; - if (rOp.compare("get") == 0) { - Op = 1; - } else if (rOp.compare("set") == 0) { - Op = 2; - } else { - Op = 0; + int issued = 0; + while (!issued) { + /* + pthread_mutex_lock(&flock); + if (kvfile.good()) { + getline(kvfile,line); + pthread_mutex_unlock(&flock); } - - //char buf[1024]; - //sprintf(buf,"%s,%d\n",rKey.c_str(),vl); - //write(1,buf,strlen(buf)); - - } else if (options.twitter_trace == 2) { - getline( ss, rT, ',' ); - getline( ss, rKey, ',' ); - getline( ss, rvaluelen, ',' ); - getline( ss, rOp, ',' ); - int op_n = atoi(rOp.c_str()); - - if (op_n == 1) - Op = 1; - else if (op_n == 0) { - Op = 2; + else { + pthread_mutex_unlock(&flock); + return 1; + } + */ + stringstream ss(line); + int Op = 0; + int vl = 0; + + if (options.twitter_trace == 1) { + getline( ss, rT, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rKeySize, ',' ); + getline( ss, rvaluelen, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + vl = atoi(rvaluelen.c_str()); + if (vl < 1) vl = 1; + if (vl > 524000) vl = 524000; + if (rOp.compare("get") == 0) { + Op = 1; + } else if (rOp.compare("set") == 0) { + Op = 2; + } else { + Op = 0; + } + + //char buf[1024]; + //sprintf(buf,"%s,%d\n",rKey.c_str(),vl); + //write(1,buf,strlen(buf)); + + } else if (options.twitter_trace == 2) { + getline( ss, rT, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + Op = atoi(rOp.c_str()); + vl = atoi(rvaluelen.c_str()); + } + else { + getline( ss, rT, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + vl = atoi(rvaluelen.c_str()); + if (rOp.compare("read") == 0) + Op = 1; + if (rOp.compare("write") == 0) + Op = 2; } - vl = atoi(rvaluelen.c_str()) - 76; - vl = vl - strlen(rKey.c_str()); - } - else { - getline( ss, rT, ',' ); - getline( ss, rApp, ',' ); - getline( ss, rOp, ',' ); - getline( ss, rKey, ',' ); - getline( ss, rvaluelen, ',' ); - vl = atoi(rvaluelen.c_str()); - if (rOp.compare("read") == 0) - Op = 1; - if (rOp.compare("write") == 0) - Op = 2; - } - char key[256]; - memset(key,0,256); - strncpy(key, rKey.c_str(),255); - - switch(Op) - { - case 0: - fprintf(stderr,"invalid line: %s, vl: %d @T: %d\n", - key,vl,atoi(rT.c_str())); - break; - case 1: - issue_get_with_len(key, vl, now); - break; - case 2: - int index = lrand48() % (1024 * 1024); - issue_set(key, &random_char[index], vl, now,true); - break; - + char key[256]; + memset(key,0,256); + strncpy(key, rKey.c_str(),255); + + switch(Op) + { + case 0: + fprintf(stderr,"invalid line: %s, vl: %d @T: %d\n", + key,vl,atoi(rT.c_str())); + break; + case 1: + issued = issue_get_with_len(key, vl, now); + break; + case 2: + int index = lrand48() % (1024 * 1024); + issued = issue_set(key, &random_char[index], vl, now,true); + break; + + } } - } - } + } return ret; } @@ -479,7 +579,7 @@ int Connection::issue_getsetorset(double now) { /** * Issue a get request to the server. */ -void Connection::issue_get_with_len(const char* key, int valuelen, double now) { +int Connection::issue_get_with_len(const char* key, int valuelen, double now) { Operation op; int l; @@ -501,17 +601,29 @@ void Connection::issue_get_with_len(const char* key, int valuelen, double now) { //record before rx //r_vsize = stats.rx_bytes % 100000; - + pthread_mutex_lock(&opaque_lock); + op.opaque = g_opaque++; + pthread_mutex_unlock(&opaque_lock); + op.key = string(key); op.valuelen = valuelen; op.type = Operation::GET; - op_queue.push(op); - - if (read_state == IDLE) read_state = WAITING_FOR_GET; - l = prot->get_request(key); - if (read_state != LOADING) stats.tx_bytes += l; - - stats.log_access(op); + op.hv = hashstr(op.key); + //item_lock(op.hv,cid); + //pthread_mutex_t *lock = (pthread_mutex_t*)item_trylock(op.hv,cid); + //if (lock != NULL) { + op_queue[op.opaque] = op; + //output_op(&op,0,0); + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + l = prot->get_request(key,op.opaque); + if (read_state != LOADING) stats.tx_bytes += l; + + stats.log_access(op); + return 1; + //} else { + // return 0; + //} } /** @@ -540,12 +652,18 @@ void Connection::issue_get(const char* key, double now) { //record before rx //r_vsize = stats.rx_bytes % 100000; + pthread_mutex_lock(&opaque_lock); + op.opaque = g_opaque++; + pthread_mutex_unlock(&opaque_lock); + op.key = string(key); op.type = Operation::GET; - op_queue.push(op); + op.hv = hashstr(op.key); + //item_lock(op.hv,cid); + op_queue[op.opaque] = op; if (read_state == IDLE) read_state = WAITING_FOR_GET; - l = prot->get_request(key); + l = prot->get_request(key,op.opaque); if (read_state != LOADING) stats.tx_bytes += l; stats.log_access(op); @@ -575,7 +693,8 @@ void Connection::issue_delete90(double now) { #endif op.type = Operation::DELETE; - op_queue.push(op); + op.opaque = 0; + op_queue[op.opaque] = op; if (read_state == IDLE) read_state = WAITING_FOR_DELETE; l = prot->delete90_request(); @@ -583,9 +702,17 @@ void Connection::issue_delete90(double now) { } /** - * Issue a set request to the server. + * Issue a set request as a result of a miss to the server. + * The difference here is that we will yield to any outstanding SETs to this + * key, i.e. while waiting for GET response a SET to the key was issued. + * + * + * or v2? + * - works with the lock held, since we want to beat any incoming writes + * - maintains program order, total set ordering + * - currenlty using this design */ -void Connection::issue_set(const char* key, const char* value, int length, +void Connection::issue_set_miss(const char* key, const char* value, int length, double now, bool is_access) { Operation op; int l; @@ -605,39 +732,102 @@ void Connection::issue_set(const char* key, const char* value, int length, //r_key = atoi(kptr); //r_ksize = strlen(kptr); + pthread_mutex_lock(&opaque_lock); + op.opaque = g_opaque++; + pthread_mutex_unlock(&opaque_lock); + op.key = string(key); + op.valuelen = length; op.type = Operation::SET; - op_queue.push(op); + op.hv = hashstr(op.key); + op_queue[op.opaque] = op; + //output_op(&op,1,0); - if (read_state == IDLE) read_state = WAITING_FOR_SET; - l = prot->set_request(key, value, length); + //if (read_state == IDLE) read_state = WAITING_FOR_SET; + l = prot->set_request(key, value, length, op.opaque); if (read_state != LOADING) stats.tx_bytes += l; if (is_access) stats.log_access(op); } +/** + * Issue a set request to the server. + */ +int Connection::issue_set(const char* key, const char* value, int length, + double now, bool is_access) { + Operation op; + int l; + +#if HAVE_CLOCK_GETTIME + op.start_time = get_time_accurate(); +#else + if (now == 0.0) op.start_time = get_time(); + else op.start_time = now; +#endif + + //record value size + //r_vsize = length; + //r_appid = key[0] - '0'; + //const char* kptr = key; + //kptr += 2; + //r_key = atoi(kptr); + //r_ksize = strlen(kptr); + + pthread_mutex_lock(&opaque_lock); + op.opaque = g_opaque++; + pthread_mutex_unlock(&opaque_lock); + op.key = string(key); + op.valuelen = length; + op.type = Operation::SET; + op.hv = hashstr(op.key); + //pthread_mutex_t *lock = (pthread_mutex_t*)item_trylock(op.hv,cid); + //if (lock != NULL) { + //item_lock(op.hv,cid); + op_queue[op.opaque] = op; + + //output_op(&op,1,0); + + //if (read_state == IDLE) read_state = WAITING_FOR_SET; + l = prot->set_request(key, value, length, op.opaque); + if (read_state != LOADING) stats.tx_bytes += l; + + if (is_access) + stats.log_access(op); + return 1; + //} else { + // return 0; + //} +} + /** * Return the oldest live operation in progress. */ -void Connection::pop_op() { +void Connection::pop_op(Operation *op) { + assert(op_queue.size() > 0); - op_queue.pop(); + //op_queue.pop(); + size_t hv = op->hv; + //pthread_mutex_t *l = op->lock; + op_queue.erase(op->opaque); + + //item_trylock_unlock(l,cid); + //item_unlock(hv,cid); if (read_state == LOADING) return; read_state = IDLE; // Advance the read state machine. - if (op_queue.size() > 0) { - Operation& op = op_queue.front(); - switch (op.type) { - case Operation::GET: read_state = WAITING_FOR_GET; break; - case Operation::SET: read_state = WAITING_FOR_SET; break; - case Operation::DELETE: read_state = WAITING_FOR_DELETE; break; - default: DIE("Not implemented."); - } - } + //if (op_queue.size() > 0) { + // Operation& op = op_queue.front(); + // switch (op.type) { + // case Operation::GET: read_state = WAITING_FOR_GET; break; + // case Operation::SET: read_state = WAITING_FOR_SET; break; + // case Operation::DELETE: read_state = WAITING_FOR_DELETE; break; + // default: DIE("Not implemented."); + // } + //} } /** @@ -676,7 +866,7 @@ void Connection::finish_op(Operation *op, int was_hit) { } last_rx = now; - pop_op(); + pop_op(op); //lets check if we should output stats for the window //Do the binning for percentile outputs @@ -697,6 +887,61 @@ void Connection::finish_op(Operation *op, int was_hit) { drive_write_machine(); } +void Connection::finish_op_miss(Operation *op, int was_hit) { + double now; +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); +#else + now = get_time(); +#endif +#if HAVE_CLOCK_GETTIME + op->end_time = get_time_accurate(); +#else + op->end_time = now; +#endif + + if (options.successful_queries && was_hit) { + switch (op->type) { + case Operation::GET: stats.log_get(*op); break; + case Operation::SET: stats.log_set(*op); break; + case Operation::DELETE: break; + default: DIE("Not implemented."); + } + } else { + switch (op->type) { + case Operation::GET: stats.log_get(*op); break; + case Operation::SET: stats.log_set(*op); break; + case Operation::DELETE: break; + default: DIE("Not implemented."); + } + } + + last_rx = now; + //we are atomically issuing a set + op_queue.erase(op->opaque); + read_state = IDLE; + + + //lets check if we should output stats for the window + //Do the binning for percentile outputs + //crude at start + if ((options.misswindow != 0) && ( ((stats.window_accesses) % options.misswindow) == 0)) + { + if (stats.window_gets != 0) + { + //printf("%lu,%.4f\n",(stats.accesses), + // ((double)stats.window_get_misses/(double)stats.window_accesses)); + stats.window_gets = 0; + stats.window_get_misses = 0; + stats.window_sets = 0; + stats.window_accesses = 0; + } + } + +} + /** * Check if our testing is done and we should exit. @@ -927,7 +1172,7 @@ void Connection::read_callback() { struct evbuffer *input = bufferevent_get_input(bev); Operation *op = NULL; - bool done, found, full_read; + bool done, found; //initially assume found (for sets that may come through here) //is this correct? do we want to assume true in case that @@ -938,116 +1183,172 @@ void Connection::read_callback() { if (op_queue.size() == 0) V("Spurious read callback."); while (1) { - if (op_queue.size() > 0) op = &op_queue.front(); - - switch (read_state) { - case INIT_READ: DIE("event from uninitialized connection"); - case IDLE: return; // We munched all the data we expected? - - case WAITING_FOR_GET: - assert(op_queue.size() > 0); - - int obj_size; - full_read = prot->handle_response(input, done, found, obj_size); - - if (!full_read) { - return; - } else if (done) { - - if ((!found && options.getset) || - (!found && options.getsetorset)) { - char key[256]; - string keystr = op->key; - strcpy(key, keystr.c_str()); - int valuelen = op->valuelen; - - finish_op(op,0); // sets read_state = IDLE - //if not found and in getset mode, issue set - if (options.read_file) { - int index = lrand48() % (1024 * 1024); - issue_set(key, &random_char[index], valuelen); - } - else { - int index = lrand48() % (1024 * 1024); - issue_set(key, &random_char[index], valuelen); - } - } else { - if (!found) { - finish_op(op,1); - } else { - finish_op(op,0); - } - } - - - //char log[256]; - //sprintf(log,"%f,%d,%d,%d,%d,%d,%d\n", - // r_time,r_appid,r_type,r_ksize,r_vsize,r_key,r_hit); - //write(2,log,strlen(log)); - - - } - break; - - case WAITING_FOR_SET: - - assert(op_queue.size() > 0); - full_read = prot->handle_response(input, done, found, obj_size); - if (!full_read) { - - //char key[256]; - //char log[1024]; - //string keystr = op->key; - //strcpy(key, keystr.c_str()); - //int valuelen = op->valuelen; - //sprintf(log,"ERROR SETTING: %s,%d\n",key,valuelen); - //write(2,log,strlen(log)); - return; - } - - - - finish_op(op,1); - break; - case WAITING_FOR_DELETE: - if (!prot->handle_response(input,done,found, obj_size)) return; - finish_op(op,1); - break; - - case LOADING: - assert(op_queue.size() > 0); - if (!prot->handle_response(input, done, found, obj_size)) return; - loader_completed++; - pop_op(); - - if (loader_completed == options.records) { - D("Finished loading."); - read_state = IDLE; - } else { - while (loader_issued < loader_completed + LOADER_CHUNK) { - if (loader_issued >= options.records) break; - - char key[256]; - string keystr = keygen->generate(loader_issued); - strcpy(key, keystr.c_str()); - int index = lrand48() % (1024 * 1024); - issue_set(key, &random_char[index], valuesize->generate()); - - loader_issued++; - } - } - - break; - - case CONN_SETUP: + if (read_state == CONN_SETUP) { assert(options.binary); if (!prot->setup_connection_r(input)) return; read_state = IDLE; break; + } + + int obj_size; + uint32_t opaque; + bool full_read = prot->handle_response(input, done, found, obj_size, opaque); + if (full_read) { + op = &op_queue[opaque]; + //char out[128]; + //sprintf(out,"conn: %u, reading opaque: %u\n",cid,opaque); + //write(2,out,strlen(out)); + //output_op(op,2,found); + } else { + return; + } + - default: DIE("not implemented"); + switch (op->type) { + case Operation::GET: + if (done) { + if ((!found && options.getset) || + (!found && options.getsetorset)) { + char key[256]; + string keystr = op->key; + strcpy(key, keystr.c_str()); + int valuelen = op->valuelen; + + finish_op_miss(op,0); // sets read_state = IDLE + //if not found and in getset mode, issue set + if (options.read_file) { + int index = lrand48() % (1024 * 1024); + issue_set_miss(key, &random_char[index], valuelen); + } + else { + int index = lrand48() % (1024 * 1024); + issue_set_miss(key, &random_char[index], valuelen); + } + + } else { + if (found) { + finish_op(op,1); + } else { + finish_op(op,0); + } + } + } else { + char out[128]; + sprintf(out,"conn: %u, not done reading, should do something",cid); + write(2,out,strlen(out)); + } + break; + case Operation::SET: + finish_op(op,1); + break; + default: DIE("not implemented"); } + + + //switch (read_state) { + //case INIT_READ: DIE("event from uninitialized connection"); + //case IDLE: return; // We munched all the data we expected? + + //case WAITING_FOR_GET: + // assert(op_queue.size() > 0); + // if (!full_read) { + // return; + // } else if (done) { + + // + // if ((!found && options.getset) || + // (!found && options.getsetorset)) { + // char key[256]; + // string keystr = op->key; + // strcpy(key, keystr.c_str()); + // int valuelen = op->valuelen; + // + // finish_op(op,0); // sets read_state = IDLE + // //if not found and in getset mode, issue set + // if (options.read_file) { + // int index = lrand48() % (1024 * 1024); + // issue_set(key, &random_char[index], valuelen); + // } + // else { + // int index = lrand48() % (1024 * 1024); + // issue_set(key, &random_char[index], valuelen); + // } + // } else { + // if (!found) { + // finish_op(op,1); + // } else { + // finish_op(op,0); + // } + // } + + + // //char log[256]; + // //sprintf(log,"%f,%d,%d,%d,%d,%d,%d\n", + // // r_time,r_appid,r_type,r_ksize,r_vsize,r_key,r_hit); + // //write(2,log,strlen(log)); + // + + // } + // break; + + //case WAITING_FOR_SET: + // + // assert(op_queue.size() > 0); + // //full_read = prot->handle_response(input, done, found, obj_size, opaque); + // if (!full_read) { + + // //char key[256]; + // //char log[1024]; + // //string keystr = op->key; + // //strcpy(key, keystr.c_str()); + // //int valuelen = op->valuelen; + // //sprintf(log,"ERROR SETTING: %s,%d\n",key,valuelen); + // //write(2,log,strlen(log)); + // return; + // } + // + // finish_op(op,1); + // break; + // + //case WAITING_FOR_DELETE: + // if (!full_read) return; + // finish_op(op,1); + // break; + + //case LOADING: + // assert(op_queue.size() > 0); + // if (!full_read) return; + // loader_completed++; + // pop_op(op); + + // if (loader_completed == options.records) { + // D("Finished loading."); + // read_state = IDLE; + // } else { + // while (loader_issued < loader_completed + LOADER_CHUNK) { + // if (loader_issued >= options.records) break; + + // char key[256]; + // string keystr = keygen->generate(loader_issued); + // strcpy(key, keystr.c_str()); + // int index = lrand48() % (1024 * 1024); + // issue_set(key, &random_char[index], valuesize->generate()); + + // loader_issued++; + // } + // } + + // break; + + //case CONN_SETUP: + // assert(options.binary); + // if (!prot->setup_connection_r(input)) return; + // read_state = IDLE; + // break; + + //default: DIE("not implemented"); + //} } } diff --git a/Connection.h b/Connection.h index 0e5b5b3..cab66c6 100644 --- a/Connection.h +++ b/Connection.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -105,6 +106,7 @@ class Connection { char last_key[256]; int last_miss; + uint32_t cid; int eof; //trace format variables @@ -121,14 +123,16 @@ class Connection { Generator *keysize; KeyGenerator *keygen; Generator *iagen; - std::queue op_queue; + std::unordered_map op_queue; ConcurrentQueue *trace_queue; // state machine functions / event processing - void pop_op(); + void pop_op(Operation *op); + void output_op(Operation *op, int type, bool was_found); //void finish_op(Operation *op); void finish_op(Operation *op,int was_hit); + void finish_op_miss(Operation *op,int was_hit); void issue_something(double now = 0.0); int issue_something_trace(double now = 0.0); void issue_getset(double now = 0.0); @@ -138,8 +142,10 @@ class Connection { // request functions void issue_sasl(); void issue_get(const char* key, double now = 0.0); - void issue_get_with_len(const char* key, int valuelen, double now = 0.0); - void issue_set(const char* key, const char* value, int length, + int issue_get_with_len(const char* key, int valuelen, double now = 0.0); + int issue_set(const char* key, const char* value, int length, + double now = 0.0, bool is_access = false); + void issue_set_miss(const char* key, const char* value, int length, double now = 0.0, bool is_access = false); void issue_delete90(double now = 0.0); diff --git a/Operation.h b/Operation.h index 9e499e3..861de7e 100644 --- a/Operation.h +++ b/Operation.h @@ -18,6 +18,9 @@ class Operation { string key; int valuelen; + uint32_t opaque; + size_t hv; + pthread_mutex_t *lock; double time() const { return (end_time - start_time) * 1000000; } }; diff --git a/Protocol.cc b/Protocol.cc index 29f079e..b0dd551 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -46,7 +46,7 @@ * fprintf(stderr,"--\n"); * */ -int ProtocolRESP::set_request(const char* key, const char* value, int len) { +int ProtocolRESP::set_request(const char* key, const char* value, int len, uint32_t opaque) { //need to make the real value char *val = (char*)malloc(len*sizeof(char)+1); @@ -78,7 +78,7 @@ int ProtocolRESP::set_request(const char* key, const char* value, int len) { /** * Send a RESP get request. */ -int ProtocolRESP::get_request(const char* key) { +int ProtocolRESP::get_request(const char* key, uint32_t opaque) { //check if we should use assoc if (opts.use_assoc && strlen(key) > ((unsigned int)(opts.assoc+1)) ) @@ -188,7 +188,8 @@ int ProtocolRESP::delete90_request() { * * */ -bool ProtocolRESP::handle_response(evbuffer *input, bool &done, bool &found, int &obj_size) { +bool ProtocolRESP::handle_response(evbuffer *input, bool &done, bool &found, int &obj_size, uint32_t &opaque) { + opaque = 0; char *buf = NULL; char *databuf = NULL; @@ -353,7 +354,7 @@ bool ProtocolRESP::handle_response(evbuffer *input, bool &done, bool &found, int /** * Send an ascii get request. */ -int ProtocolAscii::get_request(const char* key) { +int ProtocolAscii::get_request(const char* key, uint32_t opaque) { int l; l = evbuffer_add_printf( bufferevent_get_output(bev), "get %s\r\n", key); @@ -366,7 +367,7 @@ int ProtocolAscii::get_request(const char* key) { /** * Send an ascii set request. */ -int ProtocolAscii::set_request(const char* key, const char* value, int len) { +int ProtocolAscii::set_request(const char* key, const char* value, int len, uint32_t opaque) { int l; l = evbuffer_add_printf(bufferevent_get_output(bev), "set %s 0 0 %d\r\n", key, len); @@ -397,7 +398,8 @@ int ProtocolAscii::delete90_request() { /** * Handle an ascii response. */ -bool ProtocolAscii::handle_response(evbuffer *input, bool &done, bool &found, int &obj_size) { +bool ProtocolAscii::handle_response(evbuffer *input, bool &done, bool &found, int &obj_size, uint32_t &opaque) { + opaque = 0; char *buf = NULL; int len; size_t n_read_out; @@ -521,19 +523,21 @@ bool ProtocolBinary::setup_connection_r(evbuffer* input) { bool b,c; int obj_size; - return handle_response(input, b, c, obj_size); + uint32_t opaque; + return handle_response(input, b, c, obj_size, opaque); } /** * Send a binary get request. */ -int ProtocolBinary::get_request(const char* key) { +int ProtocolBinary::get_request(const char* key, uint32_t opaque) { uint16_t keylen = strlen(key); // each line is 4-bytes binary_header_t h = { 0x80, CMD_GET, htons(keylen), 0x00, 0x00, {htons(0)}, htonl(keylen) }; + h.opaque = htonl(opaque); bufferevent_write(bev, &h, 24); // size does not include extras bufferevent_write(bev, key, keylen); @@ -545,14 +549,14 @@ int ProtocolBinary::get_request(const char* key) { /** * Send a binary set request. */ -int ProtocolBinary::set_request(const char* key, const char* value, int len) { +int ProtocolBinary::set_request(const char* key, const char* value, int len, uint32_t opaque) { uint16_t keylen = strlen(key); // each line is 4-bytes binary_header_t h = { 0x80, CMD_SET, htons(keylen), 0x08, 0x00, {htons(0)}, htonl(keylen + 8 + len) }; - + h.opaque = htonl(opaque); bufferevent_write(bev, &h, 32); // With extras bufferevent_write(bev, key, keylen); bufferevent_write(bev, value, len); @@ -574,7 +578,7 @@ int ProtocolBinary::delete90_request() { * @param input evBuffer to read response from * @return true if consumed, false if not enough data in buffer. */ -bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, int &obj_size) { +bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, int &obj_size, uint32_t &opaque) { // Read the first 24 bytes as a header int length = evbuffer_get_length(input); if (length < 24) return false; @@ -587,6 +591,7 @@ bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, i if (length < targetLen) return false; obj_size = ntohl(h->body_len)-4; + opaque = ntohl(h->opaque); // If something other than success, count it as a miss if (h->opcode == CMD_GET && h->status) { conn->stats.get_misses++; diff --git a/Protocol.h b/Protocol.h index cd4c776..11b6a54 100644 --- a/Protocol.h +++ b/Protocol.h @@ -18,10 +18,10 @@ class Protocol { virtual bool setup_connection_w() = 0; virtual bool setup_connection_r(evbuffer* input) = 0; - virtual int get_request(const char* key) = 0; - virtual int set_request(const char* key, const char* value, int len) = 0; + virtual int get_request(const char* key, uint32_t opaque) = 0; + virtual int set_request(const char* key, const char* value, int len, uint32_t opaque) = 0; virtual int delete90_request() = 0; - virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size) = 0; + virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size, uint32_t &opaque) = 0; protected: options_t opts; @@ -40,10 +40,10 @@ class ProtocolAscii : public Protocol { virtual bool setup_connection_w() { return true; } virtual bool setup_connection_r(evbuffer* input) { return true; } - virtual int get_request(const char* key); - virtual int set_request(const char* key, const char* value, int len); + virtual int get_request(const char* key, uint32_t opaque); + virtual int set_request(const char* key, const char* value, int len, uint32_t opaque); virtual int delete90_request(); - virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size); + virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size, uint32_t &opaque); private: enum read_fsm { @@ -67,10 +67,10 @@ class ProtocolBinary : public Protocol { virtual bool setup_connection_w(); virtual bool setup_connection_r(evbuffer* input); - virtual int get_request(const char* key); - virtual int set_request(const char* key, const char* value, int len); + virtual int get_request(const char* key, uint32_t opaque); + virtual int set_request(const char* key, const char* value, int len, uint32_t opaque); virtual int delete90_request(); - virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size); + virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size, uint32_t &opaque); }; class ProtocolRESP : public Protocol { @@ -81,12 +81,12 @@ class ProtocolRESP : public Protocol { virtual bool setup_connection_w() { return true; } virtual bool setup_connection_r(evbuffer* input) { return true; } - virtual int get_request(const char* key); - virtual int set_request(const char* key, const char* value, int len); + virtual int get_request(const char* key, uint32_t opaque); + virtual int set_request(const char* key, const char* value, int len, uint32_t opaque); virtual int hget_request(const char* key); virtual int hset_request(const char* key, const char* value, int len); virtual int delete90_request(); - virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size); + virtual bool handle_response(evbuffer* input, bool &done, bool &found, int &obj_size, uint32_t &opaque); private: enum read_fsm { diff --git a/SConstruct b/SConstruct index 98024c8..affb2a6 100644 --- a/SConstruct +++ b/SConstruct @@ -6,12 +6,14 @@ env = Environment(ENV = os.environ) env['HAVE_POSIX_BARRIER'] = True -env.Append(CPPPATH = ['/usr/local/include', '/opt/local/include']) -env.Append(LIBPATH = ['/opt/local/lib']) -env.Append(CCFLAGS = '-std=c++11 -D_GNU_SOURCE') -if sys.platform == 'darwin': - env['CC'] = 'clang' - env['CXX'] = 'clang++' +#env.Append(CPPPATH = ['/u/dbyrne99/local/include']) +#env.Append(CPATH = ['/u/dbyrne99/local/include']) +#env.Append(LIBPATH = ['/u/dbyrne99/local/lib']) +#env.Append(CFLAGS = '-std=c++11 -D_GNU_SOURCE -static-libsan -fsanitize=thread -I/u/dbyrne99/local/include' ) +#env.Append(CCFLAGS = '-std=c++11 -D_GNU_SOURCE -static-libsan -fsanitize=thread -I/u/dbyrne99/local/include' ) +#if sys.platform == 'darwin': +#env['CC'] = 'clang' +#env['CXX'] = 'clang++' conf = env.Configure(config_h = "config.h") conf.Define("__STDC_FORMAT_MACROS") @@ -30,6 +32,7 @@ conf.CheckDeclaration("EVENT_BASE_FLAG_PRECISE_TIMER", '#include & servers, options_t& options, ConcurrentQueue *trace_queue = new ConcurrentQueue(20000000); struct reader_data *rdata = (struct reader_data*)malloc(sizeof(struct reader_data)); - memset(rdata,0,sizeof(struct reader_data)); rdata->trace_queue = trace_queue; pthread_t rtid; if (options.read_file) { @@ -695,6 +697,14 @@ void go(const vector& servers, options_t& options, } + /* initialize item locks */ + uint32_t item_lock_count = hashsize(item_lock_hashpower); + item_locks = (pthread_mutex_t*)calloc(item_lock_count, sizeof(pthread_mutex_t)); + for (size_t i = 0; i < item_lock_count; i++) { + pthread_mutex_init(&item_locks[i], NULL); + } + + if (options.threads > 1) { struct thread_data td[options.threads]; #ifdef __clang__ @@ -1017,7 +1027,6 @@ void do_mutilate(const vector& servers, options_t& options, vector connections; vector server_lead; - for (auto s: servers) { // Split args.server_arg[s] into host:port using strtok(). char *s_copy = new char[s.length() + 1]; @@ -1043,21 +1052,23 @@ void do_mutilate(const vector& servers, options_t& options, trace_queue, args.agentmode_given ? false : true); - int tries = 20; + int tries = 120; int connected = 0; int s = 2; for (int i = 0; i < tries; i++) { - + pthread_mutex_lock(&flock); int ret = conn->do_connect(); + pthread_mutex_unlock(&flock); if (ret) { connected = 1; + fprintf(stderr,"thread %lu, conn: %d, connected!\n",pthread_self(),c); break; } - s *= 2; int d = s + rand() % 100; + //s = s + d; - fprintf(stderr,"conn: %d, sleeping %d\n",c,d); - usleep(d); + //fprintf(stderr,"conn: %d, sleeping %d\n",c,d); + sleep(d); } if (connected) { connections.push_back(conn); From 52e4947e87f7bba6d8b2ad276b4151d6fb623d29 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 14 Jul 2021 21:13:02 -0400 Subject: [PATCH 29/57] updates --- Connection.cc | 81 ++++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/Connection.cc b/Connection.cc index 7511363..8224810 100644 --- a/Connection.cc +++ b/Connection.cc @@ -482,14 +482,14 @@ int Connection::issue_getsetorset(double now) { string rKeySize; string rvaluelen; - bool res = trace_queue->try_dequeue(line); - if (res) { - if (line.compare("EOF") == 0) { - eof = 1; - return 1; - } - int issued = 0; - while (!issued) { + int issued = 0; + while (issued < options.depth) { + bool res = trace_queue->try_dequeue(line); + if (res) { + if (line.compare("EOF") == 0) { + eof = 1; + return 1; + } /* pthread_mutex_lock(&flock); if (kvfile.good()) { @@ -866,7 +866,8 @@ void Connection::finish_op(Operation *op, int was_hit) { } last_rx = now; - pop_op(op); + op_queue.erase(op->opaque); + read_state = IDLE; //lets check if we should output stats for the window //Do the binning for percentile outputs @@ -939,6 +940,8 @@ void Connection::finish_op_miss(Operation *op, int was_hit) { stats.window_accesses = 0; } } + + drive_write_machine(); } @@ -1102,45 +1105,45 @@ void Connection::drive_write_machine(double now) { if (op_queue.size() >= (size_t) options.depth) { write_state = WAITING_FOR_OPQ; return; - } else if (now < next_time) { - write_state = WAITING_FOR_TIME; - break; // We want to run through the state machine one more time - // to make sure the timer is armed. - } else if (options.moderate && now < last_rx + 0.00025) { - write_state = WAITING_FOR_TIME; - if (!event_pending(timer, EV_TIMEOUT, NULL)) { - delay = last_rx + 0.00025 - now; - double_to_tv(delay, &tv); - evtimer_add(timer, &tv); - } - return; } - - if (options.getset) - issue_getset(now); - else if (options.getsetorset) { + //uncommenting for lowest delay possible + //if (op_queue.size() >= (size_t) options.depth) { + // write_state = WAITING_FOR_OPQ; + // return; + //} else if (now < next_time) { + // write_state = WAITING_FOR_TIME; + // break; // We want to run through the state machine one more time + // // to make sure the timer is armed. + //} else if (options.moderate && now < last_rx + 0.00025) { + // write_state = WAITING_FOR_TIME; + // if (!event_pending(timer, EV_TIMEOUT, NULL)) { + // delay = last_rx + 0.00025 - now; + // double_to_tv(delay, &tv); + // evtimer_add(timer, &tv); + // } + // return; + //} + + if (options.getsetorset) { int ret = issue_getsetorset(now); if (ret) return; //if at EOF - } else if (options.read_file) { - issue_something_trace(now); - } - else { + } else { issue_something(now); } last_tx = now; stats.log_op(op_queue.size()); - next_time += iagen->generate(); + //next_time += iagen->generate(); - if (options.skip && options.lambda > 0.0 && - now - next_time > 0.005000 && - op_queue.size() >= (size_t) options.depth) { + //if (options.skip && options.lambda > 0.0 && + // now - next_time > 0.005000 && + // op_queue.size() >= (size_t) options.depth) { - while (next_time < now - 0.004000) { - stats.skips++; - next_time += iagen->generate(); - } - } + // while (next_time < now - 0.004000) { + // stats.skips++; + // next_time += iagen->generate(); + // } + //} break; case WAITING_FOR_TIME: @@ -1215,7 +1218,6 @@ void Connection::read_callback() { strcpy(key, keystr.c_str()); int valuelen = op->valuelen; - finish_op_miss(op,0); // sets read_state = IDLE //if not found and in getset mode, issue set if (options.read_file) { int index = lrand48() % (1024 * 1024); @@ -1225,6 +1227,7 @@ void Connection::read_callback() { int index = lrand48() % (1024 * 1024); issue_set_miss(key, &random_char[index], valuelen); } + finish_op(op,0); // sets read_state = IDLE } else { if (found) { From fb3eb5b570689441f0a98a7dc6b4ee37ffc633a9 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Thu, 15 Jul 2021 11:57:46 -0400 Subject: [PATCH 30/57] updates --- Connection.cc | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Connection.cc b/Connection.cc index 8224810..1ba0394 100644 --- a/Connection.cc +++ b/Connection.cc @@ -482,8 +482,8 @@ int Connection::issue_getsetorset(double now) { string rKeySize; string rvaluelen; - int issued = 0; - while (issued < options.depth) { + int nissued = 0; + while (nissued < options.depth) { bool res = trace_queue->try_dequeue(line); if (res) { if (line.compare("EOF") == 0) { @@ -553,7 +553,7 @@ int Connection::issue_getsetorset(double now) { char key[256]; memset(key,0,256); strncpy(key, rKey.c_str(),255); - + int issued = 0; switch(Op) { case 0: @@ -569,6 +569,13 @@ int Connection::issue_getsetorset(double now) { break; } + if (issued) { + nissued++; + } else { + fprintf(stderr,"failed to issue line: %s, vl: %d @T: %d\n", + key,vl,atoi(rT.c_str())); + break; + } } } } From 9a76d89d81bfe48e8627dd75511c0014079c02ae Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Thu, 15 Jul 2021 13:03:27 -0400 Subject: [PATCH 31/57] update to use evbuffer_add --- Protocol.cc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Protocol.cc b/Protocol.cc index b0dd551..dfd19f2 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -539,8 +539,10 @@ int ProtocolBinary::get_request(const char* key, uint32_t opaque) { htonl(keylen) }; h.opaque = htonl(opaque); - bufferevent_write(bev, &h, 24); // size does not include extras - bufferevent_write(bev, key, keylen); + evbuffer_add(bev, &h, 24); + evbuffer_add(bev, key, keylen); + //bufferevent_write(bev, &h, 24); // size does not include extras + //bufferevent_write(bev, key, keylen); return 24 + keylen; } @@ -557,9 +559,12 @@ int ProtocolBinary::set_request(const char* key, const char* value, int len, uin 0x08, 0x00, {htons(0)}, htonl(keylen + 8 + len) }; h.opaque = htonl(opaque); - bufferevent_write(bev, &h, 32); // With extras - bufferevent_write(bev, key, keylen); - bufferevent_write(bev, value, len); + //bufferevent_write(bev, &h, 32); // With extras + //bufferevent_write(bev, key, keylen); + //bufferevent_write(bev, value, len); + evbuffer_add(bev, &h, 32); + evbuffer_add(bev, key, keylen); + evbuffer_add(bev, value, len); return 24 + ntohl(h.body_len); } From c7036cd4cb53d8b13c7260d8c7b4cbae5795f693 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 21 Jul 2021 01:00:08 -0400 Subject: [PATCH 32/57] updates but probs not working --- Connection.cc | 429 ++++++++++++++++++++------------------------ Connection.h | 35 ++-- ConnectionOptions.h | 1 + Protocol.cc | 15 +- Protocol.h | 10 +- SConstruct | 10 +- cmdline.ggo | 1 + mutilate.cc | 104 +++++++---- 8 files changed, 318 insertions(+), 287 deletions(-) diff --git a/Connection.cc b/Connection.cc index 1ba0394..cc074b9 100644 --- a/Connection.cc +++ b/Connection.cc @@ -36,14 +36,12 @@ extern pthread_mutex_t flock; extern pthread_mutex_t *item_locks; extern int item_lock_hashpower; -#define hashsize(n) ((unsigned long int)1<<(n)) -#define hashmask(n) (hashsize(n)-1) pthread_mutex_t cid_lock = PTHREAD_MUTEX_INITIALIZER; -uint32_t connids = 0; +uint32_t connids = 1; -pthread_mutex_t opaque_lock = PTHREAD_MUTEX_INITIALIZER; -uint32_t g_opaque = 0; +//pthread_mutex_t opaque_lock = PTHREAD_MUTEX_INITIALIZER; +//uint32_t g_opaque = 0; void item_lock(size_t hv, uint32_t cid) { //char out[128]; @@ -136,7 +134,7 @@ void Connection::output_op(Operation *op, int type, bool found) { */ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, string _hostname, string _port, options_t _options, - ConcurrentQueue* a_trace_queue, + //ConcurrentQueue* a_trace_queue, bool sampling ) : start_time(0), stats(sampling), options(_options), hostname(_hostname), port(_port), base(_base), evdns(_evdns) @@ -144,7 +142,12 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, valuesize = createGenerator(options.valuesize); keysize = createGenerator(options.keysize); - trace_queue = a_trace_queue; + //trace_queue = a_trace_queue; + opaque = 0; + total = 0; + op_queue_size = 0; + //; + //op_queue = (Operation**)malloc(sizeof(Operation*)*OPAQUE_MAX); eof = 0; keygen = new KeyGenerator(keysize, options.records); @@ -160,13 +163,35 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, read_state = INIT_READ; write_state = INIT_WRITE; + //op_queue.reserve(OPAQUE_MAX); //new std::vector(OPAQUE_MAX); + last_tx = last_rx = 0.0; - last_miss = 0; pthread_mutex_lock(&cid_lock); cid = connids++; pthread_mutex_unlock(&cid_lock); - timer = evtimer_new(base, timer_cb, this); + + issue_buf_size = 0; + issue_buf = (unsigned char*)malloc(sizeof(unsigned char)*MAX_BUFFER_SIZE); + memset(issue_buf,0,MAX_BUFFER_SIZE); + issue_buf_pos = issue_buf; + +} + +//void Connection::set_queue(ConcurrentQueue* a_trace_queue) { +// trace_queue = a_trace_queue; +//} + +void Connection::set_queue(queue* a_trace_queue) { + trace_queue = a_trace_queue; +} + +void Connection::set_lock(pthread_mutex_t* a_lock) { + lock = a_lock; +} + +uint32_t Connection::get_cid() { + return cid; } int Connection::do_connect() { @@ -230,7 +255,8 @@ int Connection::do_connect() { * Destroy a connection, performing cleanup. */ Connection::~Connection() { - event_free(timer); + + //event_free(timer); timer = NULL; // FIXME: W("Drain op_q?"); bufferevent_free(bev); @@ -246,8 +272,8 @@ Connection::~Connection() { */ void Connection::reset() { // FIXME: Actually check the connection, drain all bufferevents, drain op_q. - assert(op_queue.size() == 0); - evtimer_del(timer); + //assert(op_queue.size() == 0); + //evtimer_del(timer); read_state = IDLE; write_state = INIT_WRITE; stats = ConnectionStats(stats.sampling); @@ -481,11 +507,17 @@ int Connection::issue_getsetorset(double now) { string rKey; string rKeySize; string rvaluelen; + int nissued = 0; while (nissued < options.depth) { - bool res = trace_queue->try_dequeue(line); - if (res) { + //bool res = trace_queue->try_dequeue(line); + + if (trace_queue->size() > 0) { + pthread_mutex_lock(lock); + line = trace_queue->front(); + trace_queue->pop(); + pthread_mutex_unlock(lock); if (line.compare("EOF") == 0) { eof = 1; return 1; @@ -512,7 +544,8 @@ int Connection::issue_getsetorset(double now) { getline( ss, rvaluelen, ',' ); getline( ss, rApp, ',' ); getline( ss, rOp, ',' ); - vl = atoi(rvaluelen.c_str()); + //vl = atoi(rvaluelen.c_str()); + vl = stoi(rvaluelen); if (vl < 1) vl = 1; if (vl > 524000) vl = 524000; if (rOp.compare("get") == 0) { @@ -533,8 +566,8 @@ int Connection::issue_getsetorset(double now) { getline( ss, rOp, ',' ); getline( ss, rKey, ',' ); getline( ss, rvaluelen, ',' ); - Op = atoi(rOp.c_str()); - vl = atoi(rvaluelen.c_str()); + Op = stoi(rOp); + vl = stoi(rvaluelen); } else { getline( ss, rT, ',' ); @@ -542,7 +575,7 @@ int Connection::issue_getsetorset(double now) { getline( ss, rOp, ',' ); getline( ss, rKey, ',' ); getline( ss, rvaluelen, ',' ); - vl = atoi(rvaluelen.c_str()); + vl = stoi(rvaluelen); if (rOp.compare("read") == 0) Op = 1; if (rOp.compare("write") == 0) @@ -558,26 +591,33 @@ int Connection::issue_getsetorset(double now) { { case 0: fprintf(stderr,"invalid line: %s, vl: %d @T: %d\n", - key,vl,atoi(rT.c_str())); + key,vl,stoi(rT)); break; case 1: issued = issue_get_with_len(key, vl, now); break; case 2: int index = lrand48() % (1024 * 1024); - issued = issue_set(key, &random_char[index], vl, now,true); + issued = issue_set(key, &random_char[index], vl, now, true); break; } if (issued) { nissued++; + total++; } else { fprintf(stderr,"failed to issue line: %s, vl: %d @T: %d\n", - key,vl,atoi(rT.c_str())); + key,vl,stoi(rT)); break; } } } + //buffer is ready to go! + bufferevent_write(bev, issue_buf, issue_buf_size); + memset(issue_buf,0,issue_buf_size); + issue_buf_pos = issue_buf; + issue_buf_size = 0; + } return ret; @@ -587,7 +627,8 @@ int Connection::issue_getsetorset(double now) { * Issue a get request to the server. */ int Connection::issue_get_with_len(const char* key, int valuelen, double now) { - Operation op; + //Operation *op = new Operation; + Operation op; // = new Operation; int l; #if HAVE_CLOCK_GETTIME @@ -608,29 +649,44 @@ int Connection::issue_get_with_len(const char* key, int valuelen, double now) { //record before rx //r_vsize = stats.rx_bytes % 100000; - pthread_mutex_lock(&opaque_lock); - op.opaque = g_opaque++; - pthread_mutex_unlock(&opaque_lock); + //pthread_mutex_lock(&opaque_lock); + op.opaque = opaque++; + if (opaque > OPAQUE_MAX) { + opaque = 0; + } + //pthread_mutex_unlock(&opaque_lock); op.key = string(key); op.valuelen = valuelen; op.type = Operation::GET; - op.hv = hashstr(op.key); + //op.hv = hashstr(op.key); //item_lock(op.hv,cid); //pthread_mutex_t *lock = (pthread_mutex_t*)item_trylock(op.hv,cid); //if (lock != NULL) { op_queue[op.opaque] = op; + op_queue_size++; //output_op(&op,0,0); //if (read_state == IDLE) read_state = WAITING_FOR_GET; - l = prot->get_request(key,op.opaque); - if (read_state != LOADING) stats.tx_bytes += l; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_GET, htons(keylen), + 0x00, 0x00, {htons(0)}, + htonl(keylen) }; + h.opaque = htonl(op.opaque); + + memcpy(issue_buf_pos,&h,24); + issue_buf_pos += 24; + issue_buf_size += 24; + memcpy(issue_buf_pos,key,keylen); + issue_buf_pos += keylen; + issue_buf_size += keylen; + + if (read_state != LOADING) stats.tx_bytes += 24 + keylen; stats.log_access(op); return 1; - //} else { - // return 0; - //} } /** @@ -659,15 +715,17 @@ void Connection::issue_get(const char* key, double now) { //record before rx //r_vsize = stats.rx_bytes % 100000; - pthread_mutex_lock(&opaque_lock); - op.opaque = g_opaque++; - pthread_mutex_unlock(&opaque_lock); + op.opaque = opaque++; + if (opaque > OPAQUE_MAX) { + opaque = 0; + } op.key = string(key); op.type = Operation::GET; op.hv = hashstr(op.key); //item_lock(op.hv,cid); op_queue[op.opaque] = op; + op_queue_size++; if (read_state == IDLE) read_state = WAITING_FOR_GET; l = prot->get_request(key,op.opaque); @@ -702,6 +760,7 @@ void Connection::issue_delete90(double now) { op.type = Operation::DELETE; op.opaque = 0; op_queue[op.opaque] = op; + op_queue_size++; if (read_state == IDLE) read_state = WAITING_FOR_DELETE; l = prot->delete90_request(); @@ -719,10 +778,11 @@ void Connection::issue_delete90(double now) { * - maintains program order, total set ordering * - currenlty using this design */ -void Connection::issue_set_miss(const char* key, const char* value, int length, - double now, bool is_access) { - Operation op; +void Connection::issue_set_miss(const char* key, const char* value, int length) { + //Operation *op = new Operation; + Operation op; // = new Operation; int l; + double now = 0; #if HAVE_CLOCK_GETTIME op.start_time = get_time_accurate(); @@ -738,15 +798,17 @@ void Connection::issue_set_miss(const char* key, const char* value, int length, //kptr += 2; //r_key = atoi(kptr); //r_ksize = strlen(kptr); + op.opaque = opaque++; + if (opaque > OPAQUE_MAX) { + opaque = 0; + } - pthread_mutex_lock(&opaque_lock); - op.opaque = g_opaque++; - pthread_mutex_unlock(&opaque_lock); op.key = string(key); op.valuelen = length; op.type = Operation::SET; op.hv = hashstr(op.key); op_queue[op.opaque] = op; + op_queue_size++; //output_op(&op,1,0); @@ -754,8 +816,8 @@ void Connection::issue_set_miss(const char* key, const char* value, int length, l = prot->set_request(key, value, length, op.opaque); if (read_state != LOADING) stats.tx_bytes += l; - if (is_access) - stats.log_access(op); + //if (is_access) + stats.log_access(op); } /** @@ -763,7 +825,8 @@ void Connection::issue_set_miss(const char* key, const char* value, int length, */ int Connection::issue_set(const char* key, const char* value, int length, double now, bool is_access) { - Operation op; + //Operation *op = new Operation; + Operation op; // = new Operation; int l; #if HAVE_CLOCK_GETTIME @@ -780,10 +843,11 @@ int Connection::issue_set(const char* key, const char* value, int length, //kptr += 2; //r_key = atoi(kptr); //r_ksize = strlen(kptr); + op.opaque = opaque++; + if (opaque > OPAQUE_MAX) { + opaque = 0; + } - pthread_mutex_lock(&opaque_lock); - op.opaque = g_opaque++; - pthread_mutex_unlock(&opaque_lock); op.key = string(key); op.valuelen = length; op.type = Operation::SET; @@ -792,15 +856,33 @@ int Connection::issue_set(const char* key, const char* value, int length, //if (lock != NULL) { //item_lock(op.hv,cid); op_queue[op.opaque] = op; + op_queue_size++; //output_op(&op,1,0); + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_SET, htons(keylen), + 0x08, 0x00, {htons(0)}, + htonl(keylen + 8 + length) }; + h.opaque = htonl(op.opaque); + + memcpy(issue_buf_pos,&h,32); + issue_buf_pos += 32; + issue_buf_size += 32; + memcpy(issue_buf_pos,key,keylen); + issue_buf_pos += keylen; + issue_buf_size += keylen; + memcpy(issue_buf_pos,value,length); + issue_buf_pos += length; + issue_buf_size += length; //if (read_state == IDLE) read_state = WAITING_FOR_SET; - l = prot->set_request(key, value, length, op.opaque); - if (read_state != LOADING) stats.tx_bytes += l; + //l = prot->set_request(key, value, length, op->opaque); + if (read_state != LOADING) stats.tx_bytes += length + 32 + keylen; - if (is_access) - stats.log_access(op); + //if (is_access) + stats.log_access(op); return 1; //} else { // return 0; @@ -812,12 +894,12 @@ int Connection::issue_set(const char* key, const char* value, int length, */ void Connection::pop_op(Operation *op) { - assert(op_queue.size() > 0); - - //op_queue.pop(); - size_t hv = op->hv; + //assert(op_queue.size() > 0); + uint32_t opopq = op->opaque; //pthread_mutex_t *l = op->lock; - op_queue.erase(op->opaque); + //delete op_queue[opopq]; + op_queue.erase(opopq); + op_queue_size--; //item_trylock_unlock(l,cid); //item_unlock(hv,cid); @@ -873,7 +955,11 @@ void Connection::finish_op(Operation *op, int was_hit) { } last_rx = now; - op_queue.erase(op->opaque); + uint32_t opopq = op->opaque; + op_queue.erase(opopq); + //op_queue.erase(op_queue.begin()+opopq); + //delete op_queue[opopq]; + op_queue_size--; read_state = IDLE; //lets check if we should output stats for the window @@ -892,65 +978,8 @@ void Connection::finish_op(Operation *op, int was_hit) { } } - drive_write_machine(); } -void Connection::finish_op_miss(Operation *op, int was_hit) { - double now; -#if USE_CACHED_TIME - struct timeval now_tv; - event_base_gettimeofday_cached(base, &now_tv); - now = tv_to_double(&now_tv); -#else - now = get_time(); -#endif -#if HAVE_CLOCK_GETTIME - op->end_time = get_time_accurate(); -#else - op->end_time = now; -#endif - - if (options.successful_queries && was_hit) { - switch (op->type) { - case Operation::GET: stats.log_get(*op); break; - case Operation::SET: stats.log_set(*op); break; - case Operation::DELETE: break; - default: DIE("Not implemented."); - } - } else { - switch (op->type) { - case Operation::GET: stats.log_get(*op); break; - case Operation::SET: stats.log_set(*op); break; - case Operation::DELETE: break; - default: DIE("Not implemented."); - } - } - - last_rx = now; - //we are atomically issuing a set - op_queue.erase(op->opaque); - read_state = IDLE; - - - //lets check if we should output stats for the window - //Do the binning for percentile outputs - //crude at start - if ((options.misswindow != 0) && ( ((stats.window_accesses) % options.misswindow) == 0)) - { - if (stats.window_gets != 0) - { - //printf("%lu,%.4f\n",(stats.accesses), - // ((double)stats.window_get_misses/(double)stats.window_accesses)); - stats.window_gets = 0; - stats.window_get_misses = 0; - stats.window_sets = 0; - stats.window_accesses = 0; - } - } - - drive_write_machine(); - -} /** @@ -1010,6 +1039,7 @@ void Connection::event_callback(short events) { if (prot->setup_connection_w()) { read_state = IDLE; } + drive_write_machine(); } else if (events & BEV_EVENT_ERROR) { int err = bufferevent_socket_get_dns_error(bev); @@ -1104,12 +1134,13 @@ void Connection::drive_write_machine(double now) { delay = iagen->generate(); next_time = now + delay; double_to_tv(delay, &tv); - evtimer_add(timer, &tv); - write_state = WAITING_FOR_TIME; + //evtimer_add(timer, &tv); + //write_state = WAITING_FOR_TIME; + write_state = ISSUING; break; case ISSUING: - if (op_queue.size() >= (size_t) options.depth) { + if (op_queue_size >= (size_t) options.depth) { write_state = WAITING_FOR_OPQ; return; } @@ -1139,7 +1170,8 @@ void Connection::drive_write_machine(double now) { } last_tx = now; - stats.log_op(op_queue.size()); + //stats.log_op(op_queue.size()); + stats.log_op(op_queue_size); //next_time += iagen->generate(); //if (options.skip && options.lambda > 0.0 && @@ -1155,18 +1187,19 @@ void Connection::drive_write_machine(double now) { case WAITING_FOR_TIME: if (now < next_time) { - if (!event_pending(timer, EV_TIMEOUT, NULL)) { - delay = next_time - now; - double_to_tv(delay, &tv); - evtimer_add(timer, &tv); - } - return; + //if (!event_pending(timer, EV_TIMEOUT, NULL)) { + // delay = next_time - now; + // double_to_tv(delay, &tv); + // //evtimer_add(timer, &tv); + //} + //return; } write_state = ISSUING; break; case WAITING_FOR_OPQ: - if (op_queue.size() >= (size_t) options.depth) return; + if (op_queue_size >= (size_t) options.depth) return; + //if (op_queue.size() >= (size_t) options.depth) return; write_state = ISSUING; break; @@ -1189,10 +1222,11 @@ void Connection::read_callback() { //GET was found, but wrong value size (i.e. update value) // found = true; + //bool full_read = true; - if (op_queue.size() == 0) V("Spurious read callback."); - - while (1) { + //if (op_queue.size() == 0) V("Spurious read callback."); + bool full_read = true; + while (full_read) { if (read_state == CONN_SETUP) { assert(options.binary); @@ -1203,7 +1237,7 @@ void Connection::read_callback() { int obj_size; uint32_t opaque; - bool full_read = prot->handle_response(input, done, found, obj_size, opaque); + full_read = prot->handle_response(input, done, found, obj_size, opaque); if (full_read) { op = &op_queue[opaque]; //char out[128]; @@ -1211,7 +1245,7 @@ void Connection::read_callback() { //write(2,out,strlen(out)); //output_op(op,2,found); } else { - return; + break; } @@ -1252,125 +1286,50 @@ void Connection::read_callback() { case Operation::SET: finish_op(op,1); break; - default: DIE("not implemented"); + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + DIE("not implemented"); } + } - //switch (read_state) { - //case INIT_READ: DIE("event from uninitialized connection"); - //case IDLE: return; // We munched all the data we expected? - - //case WAITING_FOR_GET: - // assert(op_queue.size() > 0); - // if (!full_read) { - // return; - // } else if (done) { - - // - // if ((!found && options.getset) || - // (!found && options.getsetorset)) { - // char key[256]; - // string keystr = op->key; - // strcpy(key, keystr.c_str()); - // int valuelen = op->valuelen; - // - // finish_op(op,0); // sets read_state = IDLE - // //if not found and in getset mode, issue set - // if (options.read_file) { - // int index = lrand48() % (1024 * 1024); - // issue_set(key, &random_char[index], valuelen); - // } - // else { - // int index = lrand48() % (1024 * 1024); - // issue_set(key, &random_char[index], valuelen); - // } - // } else { - // if (!found) { - // finish_op(op,1); - // } else { - // finish_op(op,0); - // } - // } - - - // //char log[256]; - // //sprintf(log,"%f,%d,%d,%d,%d,%d,%d\n", - // // r_time,r_appid,r_type,r_ksize,r_vsize,r_key,r_hit); - // //write(2,log,strlen(log)); - // - - // } - // break; - - //case WAITING_FOR_SET: - // - // assert(op_queue.size() > 0); - // //full_read = prot->handle_response(input, done, found, obj_size, opaque); - // if (!full_read) { - - // //char key[256]; - // //char log[1024]; - // //string keystr = op->key; - // //strcpy(key, keystr.c_str()); - // //int valuelen = op->valuelen; - // //sprintf(log,"ERROR SETTING: %s,%d\n",key,valuelen); - // //write(2,log,strlen(log)); - // return; - // } - // - // finish_op(op,1); - // break; - // - //case WAITING_FOR_DELETE: - // if (!full_read) return; - // finish_op(op,1); - // break; - - //case LOADING: - // assert(op_queue.size() > 0); - // if (!full_read) return; - // loader_completed++; - // pop_op(op); - - // if (loader_completed == options.records) { - // D("Finished loading."); - // read_state = IDLE; - // } else { - // while (loader_issued < loader_completed + LOADER_CHUNK) { - // if (loader_issued >= options.records) break; - - // char key[256]; - // string keystr = keygen->generate(loader_issued); - // strcpy(key, keystr.c_str()); - // int index = lrand48() % (1024 * 1024); - // issue_set(key, &random_char[index], valuesize->generate()); - - // loader_issued++; - // } - // } - - // break; - - //case CONN_SETUP: - // assert(options.binary); - // if (!prot->setup_connection_r(input)) return; - // read_state = IDLE; - // break; - - //default: DIE("not implemented"); - //} + double now = get_time(); + if (check_exit_condition(now)) { + return; + } + if (op_queue_size >= (size_t) options.depth) { + return; } + issue_getsetorset(now); + last_tx = now; + stats.log_op(op_queue_size); + //drive_write_machine(); + + //fprintf(stderr,"read_cb done with current queue of ops: %u\n",op_queue.size()); + // update events + //if (bev != NULL) { + // // no pending response (nothing to read) and output buffer empty (nothing to write) + // if ((op_queue.size() == 0) && (evbuffer_get_length(bufferevent_get_output(bev)) == 0)) { + // bufferevent_disable(bev, EV_WRITE|EV_READ); + // } + //} } /** * Callback called when write requests finish. */ -void Connection::write_callback() {} +void Connection::write_callback() { + + //fprintf(stderr,"loaded evbuffer with ops: %u\n",op_queue.size()); +} /** * Callback for timer timeouts. */ -void Connection::timer_callback() { drive_write_machine(); } +void Connection::timer_callback() { + fprintf(stderr,"timer callback issuing requests!\n"); + //drive_write_machine(); +} /* The follow are C trampolines for libevent callbacks. */ diff --git a/Connection.h b/Connection.h index cab66c6..613b841 100644 --- a/Connection.h +++ b/Connection.h @@ -23,6 +23,12 @@ #include "blockingconcurrentqueue.h" #include "Protocol.h" +#define OPAQUE_MAX 16384 +#define hashsize(n) ((unsigned long int)1<<(n)) +#define hashmask(n) (hashsize(n)-1) + +#define MAX_BUFFER_SIZE 8*1024*1024 + using namespace std; using namespace moodycamel; @@ -37,8 +43,9 @@ class Connection { public: Connection(struct event_base* _base, struct evdns_base* _evdns, string _hostname, string _port, options_t options, - ConcurrentQueue *a_trace_queue, + //ConcurrentQueue *a_trace_queue, bool sampling = true); + ~Connection(); int do_connect(); @@ -61,6 +68,11 @@ class Connection { void read_callback(); void write_callback(); void timer_callback(); + + uint32_t get_cid(); + //void set_queue(ConcurrentQueue *a_trace_queue); + void set_queue(queue *a_trace_queue); + void set_lock(pthread_mutex_t* a_lock); private: string hostname; @@ -97,15 +109,15 @@ class Connection { read_state_enum read_state; write_state_enum write_state; - //need to keep value length for read key - map key_len; // Parameters to track progress of the data loader. int loader_issued, loader_completed; - //was the last op a miss - char last_key[256]; - int last_miss; + uint32_t opaque; + int issue_buf_size; + unsigned char *issue_buf_pos; + unsigned char *issue_buf; + uint32_t total; uint32_t cid; int eof; @@ -124,15 +136,17 @@ class Connection { KeyGenerator *keygen; Generator *iagen; std::unordered_map op_queue; - - ConcurrentQueue *trace_queue; + //std::vector op_queue; + uint32_t op_queue_size; + pthread_mutex_t* lock; + //ConcurrentQueue *trace_queue; + queue *trace_queue; // state machine functions / event processing void pop_op(Operation *op); void output_op(Operation *op, int type, bool was_found); //void finish_op(Operation *op); void finish_op(Operation *op,int was_hit); - void finish_op_miss(Operation *op,int was_hit); void issue_something(double now = 0.0); int issue_something_trace(double now = 0.0); void issue_getset(double now = 0.0); @@ -145,8 +159,7 @@ class Connection { int issue_get_with_len(const char* key, int valuelen, double now = 0.0); int issue_set(const char* key, const char* value, int length, double now = 0.0, bool is_access = false); - void issue_set_miss(const char* key, const char* value, int length, - double now = 0.0, bool is_access = false); + void issue_set_miss(const char* key, const char* value, int length); void issue_delete90(double now = 0.0); // protocol fucntions diff --git a/ConnectionOptions.h b/ConnectionOptions.h index dbfee45..8b4266f 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -4,6 +4,7 @@ #include "distributions.h" typedef struct { + int apps; int connections; bool blocking; double lambda; diff --git a/Protocol.cc b/Protocol.cc index dfd19f2..d918d15 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -531,6 +531,9 @@ bool ProtocolBinary::setup_connection_r(evbuffer* input) { * Send a binary get request. */ int ProtocolBinary::get_request(const char* key, uint32_t opaque) { + + struct evbuffer *output = bufferevent_get_output(bev); + uint16_t keylen = strlen(key); // each line is 4-bytes @@ -539,8 +542,8 @@ int ProtocolBinary::get_request(const char* key, uint32_t opaque) { htonl(keylen) }; h.opaque = htonl(opaque); - evbuffer_add(bev, &h, 24); - evbuffer_add(bev, key, keylen); + evbuffer_add(output, &h, 24); + evbuffer_add(output, key, keylen); //bufferevent_write(bev, &h, 24); // size does not include extras //bufferevent_write(bev, key, keylen); return 24 + keylen; @@ -552,6 +555,8 @@ int ProtocolBinary::get_request(const char* key, uint32_t opaque) { * Send a binary set request. */ int ProtocolBinary::set_request(const char* key, const char* value, int len, uint32_t opaque) { + struct evbuffer *output = bufferevent_get_output(bev); + uint16_t keylen = strlen(key); // each line is 4-bytes @@ -562,9 +567,9 @@ int ProtocolBinary::set_request(const char* key, const char* value, int len, uin //bufferevent_write(bev, &h, 32); // With extras //bufferevent_write(bev, key, keylen); //bufferevent_write(bev, value, len); - evbuffer_add(bev, &h, 32); - evbuffer_add(bev, key, keylen); - evbuffer_add(bev, value, len); + evbuffer_add(output, &h, 32); + evbuffer_add(output, key, keylen); + evbuffer_add(output, value, len); return 24 + ntohl(h.body_len); } diff --git a/Protocol.h b/Protocol.h index 11b6a54..ccd2293 100644 --- a/Protocol.h +++ b/Protocol.h @@ -62,7 +62,12 @@ class ProtocolAscii : public Protocol { class ProtocolBinary : public Protocol { public: ProtocolBinary(options_t opts, Connection* conn, bufferevent* bev): - Protocol(opts, conn, bev) {}; + Protocol(opts, conn, bev) { + //int wbuf_written; + //int wbuf_towrite; + //unsigned char *wbuf_pos; + //unsigned char wbuf[65536]; + }; ~ProtocolBinary() {}; virtual bool setup_connection_w(); @@ -76,7 +81,8 @@ class ProtocolBinary : public Protocol { class ProtocolRESP : public Protocol { public: ProtocolRESP(options_t opts, Connection* conn, bufferevent* bev): - Protocol(opts, conn, bev) {}; + Protocol(opts, conn, bev) { + }; ~ProtocolRESP() {}; virtual bool setup_connection_w() { return true; } diff --git a/SConstruct b/SConstruct index affb2a6..0e78096 100644 --- a/SConstruct +++ b/SConstruct @@ -40,10 +40,12 @@ if not conf.CheckFunc('pthread_barrier_init'): env = conf.Finish() -env.Append(CFLAGS = ' -O3 -Wall -g') -env.Append(CPPFLAGS = ' -O3 -Wall -g') -#env.Append(CFLAGS = ' -O0 -Wall -g -fsanitize=thread') -#env.Append(CPPFLAGS = ' -O0 -Wall -g -fsanitize=thread') +env.Append(CFLAGS = ' -O0 -Wall -g') +env.Append(CPPFLAGS = ' -O0 -Wall -g') +#env.Append(CFLAGS = ' -O3 -Wall -g') +#env.Append(CPPFLAGS = ' -O3 -Wall -g') +#env.Append(CFLAGS = ' -O2 -Wall -g -pg') +#env.Append(CPPFLAGS = ' -O2 -Wall -g -pg') env.Command(['cmdline.cc', 'cmdline.h'], 'cmdline.ggo', 'gengetopt < $SOURCE') diff --git a/cmdline.ggo b/cmdline.ggo index 0ef36d6..5e378c5 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -34,6 +34,7 @@ option "assoc" - "We create hash tables by taking the truncating the \ sizes we just use normal method of (key,value) store. No hash table." int default="4" option "qps" q "Target aggregate QPS. 0 = peak QPS." int default="0" option "time" t "Maximum time to run (seconds)." int default="5" +option "apps" - "Number of apps, should eqaul total conns" int default="1" option "read_file" - "Read keys from file." string default="" option "twitter_trace" - "use twitter memcached trace format from file." int default="0" diff --git a/mutilate.cc b/mutilate.cc index c51c077..f44f522 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -71,11 +72,15 @@ struct thread_data { zmq::socket_t *socket; #endif int id; - ConcurrentQueue *trace_queue; + //std::vector*> trace_queue; + std::vector*> trace_queue; + std::vector mutexes; }; struct reader_data { - ConcurrentQueue *trace_queue; + //std::vector*> trace_queue; + std::vector*> trace_queue; + std::vector mutexes; string trace_filename; }; @@ -96,8 +101,10 @@ void go(const vector &servers, options_t &options, #endif ); +//void do_mutilate(const vector &servers, options_t &options, +// ConnectionStats &stats,std::vector*> trace_queue, bool master = true void do_mutilate(const vector &servers, options_t &options, - ConnectionStats &stats,ConcurrentQueue *trace_queue, bool master = true + ConnectionStats &stats,std::vector*> trace_queue, std::vector mutexes, bool master = true #ifdef HAVE_LIBZMQ , zmq::socket_t* socket = NULL #endif @@ -683,9 +690,20 @@ void go(const vector& servers, options_t& options, } #endif - ConcurrentQueue *trace_queue = new ConcurrentQueue(20000000); + //std::vector*> trace_queue; // = (ConcurrentQueue**)malloc(sizeof(ConcurrentQueue) + std::vector*> trace_queue; // = (ConcurrentQueue**)malloc(sizeof(ConcurrentQueue) + std::vector mutexes; + for (int i = 0; i <= options.apps; i++) { + //trace_queue.push_back(new ConcurrentQueue(2000000)); + trace_queue.push_back(new std::queue()); + pthread_mutex_t *lock = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); + *lock = PTHREAD_MUTEX_INITIALIZER; + mutexes.push_back(lock); + } + //ConcurrentQueue *trace_queue = new ConcurrentQueue(20000000); struct reader_data *rdata = (struct reader_data*)malloc(sizeof(struct reader_data)); rdata->trace_queue = trace_queue; + rdata->mutexes = mutexes; pthread_t rtid; if (options.read_file) { rdata->trace_filename = options.file_name; @@ -722,6 +740,7 @@ void go(const vector& servers, options_t& options, td[t].options = &options; td[t].id = t; td[t].trace_queue = trace_queue; + td[t].mutexes = mutexes; #ifdef HAVE_LIBZMQ td[t].socket = socket; #endif @@ -776,10 +795,10 @@ void go(const vector& servers, options_t& options, stats.accumulate(*cs); delete cs; } - delete trace_queue; + //delete trace_queue; } else if (options.threads == 1) { - do_mutilate(servers, options, stats, trace_queue, true + do_mutilate(servers, options, stats, trace_queue, mutexes, true #ifdef HAVE_LIBZMQ , socket #endif @@ -899,7 +918,9 @@ static char *get_stream(ZSTD_DCtx* dctx, FILE *fin, size_t const buffInSize, voi void* reader_thread(void *arg) { struct reader_data *rdata = (struct reader_data *) arg; - ConcurrentQueue *trace_queue = (ConcurrentQueue*) rdata->trace_queue; + //std::vector*> trace_queue = (std::vector*>) rdata->trace_queue; + std::vector*> trace_queue = (std::vector*>) rdata->trace_queue; + std::vector mutexes = (std::vector) rdata->mutexes; if (hasEnding(rdata->trace_filename,".zst")) { //init @@ -925,12 +946,27 @@ void* reader_thread(void *arg) { while ((line = strsep(&trace,"\n"))) { strncpy(line_p,line,2048); string full_line(line); - bool res = trace_queue->try_enqueue(full_line); - while (!res) { - usleep(10); - res = trace_queue->try_enqueue(full_line); - nwrites++; + //check the appid + int appid = 0; + if (trace_queue.size() > 1) { + stringstream ss(full_line); + + string rT; + string rApp; + getline( ss, rT, ','); + getline( ss, rApp, ','); + appid = stoi(rApp); + } + pthread_mutex_lock(mutexes[appid]); + trace_queue[appid]->push(full_line); + pthread_mutex_unlock(mutexes[appid]); + //bool res = trace_queue[appid]->try_enqueue(full_line); + //while (!res) { + // //usleep(10); + // //res = trace_queue[appid]->try_enqueue(full_line); + // nwrites++; + //} n++; if (n % 1000000 == 0) fprintf(stderr,"decompressed requests: %lu, waits: %lu\n",n,nwrites); @@ -941,7 +977,10 @@ void* reader_thread(void *arg) { } string eof = "EOF"; for (int i = 0; i < 1000; i++) { - trace_queue->enqueue(eof); + for (int j = 0; j < trace_queue.size(); j++) { + //trace_queue[j]->enqueue(eof); + trace_queue[j]->push(eof); + } } if (trace) { free(trace); @@ -952,20 +991,20 @@ void* reader_thread(void *arg) { free(buffOut); - } else { + } //else { - ifstream trace_file; - trace_file.open(rdata->trace_filename); - while (trace_file.good()) { - string line; - getline(trace_file,line); - trace_queue->enqueue(line); - } - string eof = "EOF"; - for (int i = 0; i < 1000; i++) { - trace_queue->enqueue(eof); - } - } + //ifstream trace_file; + //trace_file.open(rdata->trace_filename); + //while (trace_file.good()) { + // string line; + // getline(trace_file,line); + // trace_queue->enqueue(line); + //} + //string eof = "EOF"; + //for (int i = 0; i < 1000; i++) { + // trace_queue->enqueue(eof); + //} + //} return NULL; } @@ -980,7 +1019,7 @@ void* thread_main(void *arg) { } ConnectionStats *cs = new ConnectionStats(); - do_mutilate(*td->servers, *td->options, *cs, td->trace_queue, td->master + do_mutilate(*td->servers, *td->options, *cs, td->trace_queue, td->mutexes, td->master #ifdef HAVE_LIBZMQ , td->socket #endif @@ -990,7 +1029,9 @@ void* thread_main(void *arg) { } void do_mutilate(const vector& servers, options_t& options, - ConnectionStats& stats, ConcurrentQueue *trace_queue, bool master + ConnectionStats& stats, vector*> trace_queue, vector mutexes, bool master +//void do_mutilate(const vector& servers, options_t& options, +// ConnectionStats& stats, vector*> trace_queue, bool master #ifdef HAVE_LIBZMQ , zmq::socket_t* socket #endif @@ -1049,7 +1090,7 @@ void do_mutilate(const vector& servers, options_t& options, srand(time(NULL)); for (int c = 0; c < conns; c++) { Connection* conn = new Connection(base, evdns, hostname, port, options, - trace_queue, + //NULL,//trace_queue, args.agentmode_given ? false : true); int tries = 120; @@ -1061,7 +1102,7 @@ void do_mutilate(const vector& servers, options_t& options, pthread_mutex_unlock(&flock); if (ret) { connected = 1; - fprintf(stderr,"thread %lu, conn: %d, connected!\n",pthread_self(),c); + fprintf(stderr,"thread %lu, conn: %d, connected!\n",pthread_self(),c+1); break; } int d = s + rand() % 100; @@ -1071,6 +1112,8 @@ void do_mutilate(const vector& servers, options_t& options, sleep(d); } if (connected) { + conn->set_queue(trace_queue[conn->get_cid()]); + conn->set_lock(mutexes[conn->get_cid()]); connections.push_back(conn); } else { fprintf(stderr,"conn: %d, not connected!!\n",c); @@ -1286,6 +1329,7 @@ void args_to_options(options_t* options) { options->threads = args.threads_arg; options->server_given = args.server_given; options->roundrobin = args.roundrobin_given; + options->apps = args.apps_arg; int connections = options->connections; if (options->roundrobin) { From 06a9e7d81a3d2f530bb02afc0d5b6bf4cb5f451a Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 4 Aug 2021 17:00:31 -0400 Subject: [PATCH 33/57] working --- Connection.cc | 139 +++++++++++++++++++++++++++++++++++++--------- Connection.h | 20 ++++--- Operation.h | 4 +- Protocol.cc | 7 ++- SConstruct | 8 +-- binary_protocol.h | 3 + mutilate.cc | 31 +++++++---- 7 files changed, 155 insertions(+), 57 deletions(-) diff --git a/Connection.cc b/Connection.cc index cc074b9..e258c26 100644 --- a/Connection.cc +++ b/Connection.cc @@ -146,6 +146,7 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, opaque = 0; total = 0; op_queue_size = 0; + issue_buf_n = 0; //; //op_queue = (Operation**)malloc(sizeof(Operation*)*OPAQUE_MAX); eof = 0; @@ -162,7 +163,7 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, read_state = INIT_READ; write_state = INIT_WRITE; - + last_quiet = false; //op_queue.reserve(OPAQUE_MAX); //new std::vector(OPAQUE_MAX); last_tx = last_rx = 0.0; @@ -556,9 +557,6 @@ int Connection::issue_getsetorset(double now) { Op = 0; } - //char buf[1024]; - //sprintf(buf,"%s,%d\n",rKey.c_str(),vl); - //write(1,buf,strlen(buf)); } else if (options.twitter_trace == 2) { getline( ss, rT, ',' ); @@ -568,6 +566,9 @@ int Connection::issue_getsetorset(double now) { getline( ss, rvaluelen, ',' ); Op = stoi(rOp); vl = stoi(rvaluelen); + //char outbuf[1024]; + //sprintf(outbuf,"%s\n",line.c_str()); + //write(1,outbuf,strlen(outbuf)); } else { getline( ss, rT, ',' ); @@ -594,11 +595,24 @@ int Connection::issue_getsetorset(double now) { key,vl,stoi(rT)); break; case 1: - issued = issue_get_with_len(key, vl, now); + if (nissued < options.depth-1) { + issued = issue_get_with_len(key, vl, now, true); + last_quiet = true; + } else { + issued = issue_get_with_len(key, vl, now, false); + last_quiet = false; + } + //} else { + // issued = issue_get_with_len(key, vl, now, false); + //} break; case 2: + if (last_quiet) { + issue_noop(now); + } int index = lrand48() % (1024 * 1024); issued = issue_set(key, &random_char[index], vl, now, true); + last_quiet = false; break; } @@ -612,11 +626,21 @@ int Connection::issue_getsetorset(double now) { } } } + //fprintf(stderr,"getsetorset issuing %d reqs\n",issue_buf_n); + //char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); + //fprintf(stderr,"-------------------------------------\n"); + //memcpy(output,issue_buf,issue_buf_size); + //write(2,output,issue_buf_size); + //fprintf(stderr,"\n-------------------------------------\n"); + + //free(output); //buffer is ready to go! bufferevent_write(bev, issue_buf, issue_buf_size); + memset(issue_buf,0,issue_buf_size); issue_buf_pos = issue_buf; issue_buf_size = 0; + issue_buf_n = 0; } @@ -626,7 +650,7 @@ int Connection::issue_getsetorset(double now) { /** * Issue a get request to the server. */ -int Connection::issue_get_with_len(const char* key, int valuelen, double now) { +int Connection::issue_get_with_len(const char* key, int valuelen, double now, bool quiet) { //Operation *op = new Operation; Operation op; // = new Operation; int l; @@ -674,6 +698,10 @@ int Connection::issue_get_with_len(const char* key, int valuelen, double now) { binary_header_t h = { 0x80, CMD_GET, htons(keylen), 0x00, 0x00, {htons(0)}, htonl(keylen) }; + + if (quiet) { + h.opcode = CMD_GETQ; + } h.opaque = htonl(op.opaque); memcpy(issue_buf_pos,&h,24); @@ -682,6 +710,7 @@ int Connection::issue_get_with_len(const char* key, int valuelen, double now) { memcpy(issue_buf_pos,key,keylen); issue_buf_pos += keylen; issue_buf_size += keylen; + issue_buf_n++; if (read_state != LOADING) stats.tx_bytes += 24 + keylen; @@ -722,7 +751,7 @@ void Connection::issue_get(const char* key, double now) { op.key = string(key); op.type = Operation::GET; - op.hv = hashstr(op.key); + //op.hv = hashstr(op.key); //item_lock(op.hv,cid); op_queue[op.opaque] = op; op_queue_size++; @@ -806,7 +835,7 @@ void Connection::issue_set_miss(const char* key, const char* value, int length) op.key = string(key); op.valuelen = length; op.type = Operation::SET; - op.hv = hashstr(op.key); + //op.hv = hashstr(op.key); op_queue[op.opaque] = op; op_queue_size++; @@ -820,6 +849,37 @@ void Connection::issue_set_miss(const char* key, const char* value, int length) stats.log_access(op); } + +void Connection::issue_noop(double now) { + Operation op; + + if (now == 0.0) op.start_time = get_time(); + else op.start_time = now; + + //op.opaque = opaque++; + //if (opaque > OPAQUE_MAX) { + // opaque = 0; + //} + + //op.valuelen = 0; + //op.type = Operation::NOOP; + //op.hv = hashstr(op.key); + //pthread_mutex_t *lock = (pthread_mutex_t*)item_trylock(op.hv,cid); + //if (lock != NULL) { + //item_lock(op.hv,cid); + //op_queue[op.opaque] = op; + //op_queue_size++; + binary_header_t h = { 0x80, CMD_NOOP, 0x0000, + 0x00, 0x00, {htons(0)}, + 0x00 }; + //h.opaque = htonl(op.opaque); + + memcpy(issue_buf_pos,&h,24); + issue_buf_pos += 24; + issue_buf_size += 24; + issue_buf_n++; +} + /** * Issue a set request to the server. */ @@ -851,7 +911,7 @@ int Connection::issue_set(const char* key, const char* value, int length, op.key = string(key); op.valuelen = length; op.type = Operation::SET; - op.hv = hashstr(op.key); + //op.hv = hashstr(op.key); //pthread_mutex_t *lock = (pthread_mutex_t*)item_trylock(op.hv,cid); //if (lock != NULL) { //item_lock(op.hv,cid); @@ -876,13 +936,15 @@ int Connection::issue_set(const char* key, const char* value, int length, memcpy(issue_buf_pos,value,length); issue_buf_pos += length; issue_buf_size += length; + issue_buf_n++; //if (read_state == IDLE) read_state = WAITING_FOR_SET; //l = prot->set_request(key, value, length, op->opaque); - if (read_state != LOADING) stats.tx_bytes += length + 32 + keylen; - //if (is_access) - stats.log_access(op); + //if (is_access) { + if (read_state != LOADING) stats.tx_bytes += length + 32 + keylen; + stats.log_access(op); + //} return 1; //} else { // return 0; @@ -1223,6 +1285,7 @@ void Connection::read_callback() { // found = true; //bool full_read = true; + //fprintf(stderr,"read_cb start with current queue of ops: %lu and issue_buf_n: %d\n",op_queue.size(),issue_buf_n); //if (op_queue.size() == 0) V("Spurious read callback."); bool full_read = true; @@ -1235,10 +1298,13 @@ void Connection::read_callback() { break; } - int obj_size; + int opcode; uint32_t opaque; - full_read = prot->handle_response(input, done, found, obj_size, opaque); + full_read = prot->handle_response(input, done, found, opcode, opaque); if (full_read) { + if (opcode == CMD_NOOP) { + continue; + } op = &op_queue[opaque]; //char out[128]; //sprintf(out,"conn: %u, reading opaque: %u\n",cid,opaque); @@ -1258,16 +1324,13 @@ void Connection::read_callback() { string keystr = op->key; strcpy(key, keystr.c_str()); int valuelen = op->valuelen; - - //if not found and in getset mode, issue set - if (options.read_file) { - int index = lrand48() % (1024 * 1024); - issue_set_miss(key, &random_char[index], valuelen); - } - else { - int index = lrand48() % (1024 * 1024); - issue_set_miss(key, &random_char[index], valuelen); + int index = lrand48() % (1024 * 1024); + if (last_quiet) { + issue_noop(); } + //issue_set_miss(key, &random_char[index], valuelen); + issue_set(key, &random_char[index], valuelen, false); + last_quiet = false; finish_op(op,0); // sets read_state = IDLE } else { @@ -1297,15 +1360,37 @@ void Connection::read_callback() { if (check_exit_condition(now)) { return; } - if (op_queue_size >= (size_t) options.depth) { - return; + //fprintf(stderr,"read_cb done with current queue of ops: %d and issue_buf_n: %d\n",op_queue_size,issue_buf_n); + //for (auto x : op_queue) { + // cerr << x.first << ": " << x.second.key << endl; + //} + //buffer is ready to go! + //if (issue_buf_n >= options.depth) { + if (issue_buf_n > 0) { + //fprintf(stderr,"read_cb writing %d reqs\n",issue_buf_n); + // char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); + // fprintf(stderr,"-------------------------------------\n"); + // memcpy(output,issue_buf,issue_buf_size); + // write(2,output,issue_buf_size); + // fprintf(stderr,"\n-------------------------------------\n"); + // + // free(output); + bufferevent_write(bev, issue_buf, issue_buf_size); + memset(issue_buf,0,issue_buf_size); + issue_buf_pos = issue_buf; + issue_buf_size = 0; + issue_buf_n = 0; + } + + if (op_queue_size >= (uint32_t) options.depth) { + return; + } else { + issue_getsetorset(now); } - issue_getsetorset(now); last_tx = now; stats.log_op(op_queue_size); //drive_write_machine(); - //fprintf(stderr,"read_cb done with current queue of ops: %u\n",op_queue.size()); // update events //if (bev != NULL) { // // no pending response (nothing to read) and output buffer empty (nothing to write) diff --git a/Connection.h b/Connection.h index 613b841..1c8236a 100644 --- a/Connection.h +++ b/Connection.h @@ -114,21 +114,22 @@ class Connection { uint32_t opaque; int issue_buf_size; + int issue_buf_n; unsigned char *issue_buf_pos; unsigned char *issue_buf; - + bool last_quiet; uint32_t total; uint32_t cid; int eof; //trace format variables - double r_time; // time in seconds - int r_appid; // prefix minus ':' char - int r_type; //1 = get, 2 = set - int r_ksize; //key size - int r_vsize; //-1 or size of value if hit - int r_key; //op->key as int - int r_hit; //1 if hit, 0 if miss + //double r_time; // time in seconds + //int r_appid; // prefix minus ':' char + //int r_type; //1 = get, 2 = set + //int r_ksize; //key size + //int r_vsize; //-1 or size of value if hit + //int r_key; //op->key as int + //int r_hit; //1 if hit, 0 if miss Protocol *prot; Generator *valuesize; @@ -155,8 +156,9 @@ class Connection { // request functions void issue_sasl(); + void issue_noop(double now = 0.0); void issue_get(const char* key, double now = 0.0); - int issue_get_with_len(const char* key, int valuelen, double now = 0.0); + int issue_get_with_len(const char* key, int valuelen, double now = 0.0, bool quiet = false); int issue_set(const char* key, const char* value, int length, double now = 0.0, bool is_access = false); void issue_set_miss(const char* key, const char* value, int length); diff --git a/Operation.h b/Operation.h index 861de7e..3e119b1 100644 --- a/Operation.h +++ b/Operation.h @@ -11,7 +11,7 @@ class Operation { double start_time, end_time; enum type_enum { - GET, SET, DELETE, SASL + GET, SET, DELETE, SASL, NOOP }; type_enum type; @@ -19,8 +19,6 @@ class Operation { string key; int valuelen; uint32_t opaque; - size_t hv; - pthread_mutex_t *lock; double time() const { return (end_time - start_time) * 1000000; } }; diff --git a/Protocol.cc b/Protocol.cc index d918d15..c76aaff 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -588,7 +588,7 @@ int ProtocolBinary::delete90_request() { * @param input evBuffer to read response from * @return true if consumed, false if not enough data in buffer. */ -bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, int &obj_size, uint32_t &opaque) { +bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, int &opcode, uint32_t &opaque) { // Read the first 24 bytes as a header int length = evbuffer_get_length(input); if (length < 24) return false; @@ -599,8 +599,11 @@ bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, i // Not whole response int targetLen = 24 + ntohl(h->body_len); if (length < targetLen) return false; + //fprintf(stderr,"handle resp - opcode: %u opaque: %u len: %u status: %u\n", + // h->opcode,ntohl(h->opaque), + // ntohl(h->body_len),ntohl(h->status)); - obj_size = ntohl(h->body_len)-4; + opcode = h->opcode; opaque = ntohl(h->opaque); // If something other than success, count it as a miss if (h->opcode == CMD_GET && h->status) { diff --git a/SConstruct b/SConstruct index 0e78096..6964c46 100644 --- a/SConstruct +++ b/SConstruct @@ -40,12 +40,12 @@ if not conf.CheckFunc('pthread_barrier_init'): env = conf.Finish() -env.Append(CFLAGS = ' -O0 -Wall -g') -env.Append(CPPFLAGS = ' -O0 -Wall -g') +#env.Append(CFLAGS = ' -O0 -Wall -g') +#env.Append(CPPFLAGS = ' -O0 -Wall -g') #env.Append(CFLAGS = ' -O3 -Wall -g') #env.Append(CPPFLAGS = ' -O3 -Wall -g') -#env.Append(CFLAGS = ' -O2 -Wall -g -pg') -#env.Append(CPPFLAGS = ' -O2 -Wall -g -pg') +env.Append(CFLAGS = ' -O2 -Wall -g') +env.Append(CPPFLAGS = ' -O2 -Wall -g') env.Command(['cmdline.cc', 'cmdline.h'], 'cmdline.ggo', 'gengetopt < $SOURCE') diff --git a/binary_protocol.h b/binary_protocol.h index 2b5ef66..26f95cd 100644 --- a/binary_protocol.h +++ b/binary_protocol.h @@ -2,7 +2,10 @@ #define BINARY_PROTOCOL_H #define CMD_GET 0x00 +#define CMD_GETQ 0x09 #define CMD_SET 0x01 +#define CMD_NOOP 0x0a +#define CMD_SETQ 0x11 #define CMD_SASL 0x21 #define RESP_OK 0x00 diff --git a/mutilate.cc b/mutilate.cc index f44f522..5d3da9c 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -948,19 +948,26 @@ void* reader_thread(void *arg) { string full_line(line); //check the appid int appid = 0; - if (trace_queue.size() > 1) { - stringstream ss(full_line); - - string rT; - string rApp; - getline( ss, rT, ','); - getline( ss, rApp, ','); - appid = stoi(rApp); - + if (full_line.length() > 4) { + + if (trace_queue.size() > 1) { + stringstream ss(full_line); + string rT; + string rApp; + getline( ss, rT, ','); + getline( ss, rApp, ','); + appid = stoi(rApp); + if (appid < trace_queue.size()) { + pthread_mutex_lock(mutexes[appid]); + trace_queue[appid]->push(full_line); + pthread_mutex_unlock(mutexes[appid]); + } + } else { + pthread_mutex_lock(mutexes[appid]); + trace_queue[appid]->push(full_line); + pthread_mutex_unlock(mutexes[appid]); + } } - pthread_mutex_lock(mutexes[appid]); - trace_queue[appid]->push(full_line); - pthread_mutex_unlock(mutexes[appid]); //bool res = trace_queue[appid]->try_enqueue(full_line); //while (!res) { // //usleep(10); From faa18eb5246227217c06f850cd93c6764b72a643 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Mon, 6 Sep 2021 12:53:55 -0400 Subject: [PATCH 34/57] stable for exps done on labor day --- Connection.cc | 229 ++++++++++++++++++++++++++------------------ ConnectionOptions.h | 1 + Protocol.cc | 17 +++- cmdline.ggo | 1 + mutilate.cc | 37 ++++++- 5 files changed, 181 insertions(+), 104 deletions(-) diff --git a/Connection.cc b/Connection.cc index e258c26..d696f2a 100644 --- a/Connection.cc +++ b/Connection.cc @@ -176,7 +176,8 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, issue_buf = (unsigned char*)malloc(sizeof(unsigned char)*MAX_BUFFER_SIZE); memset(issue_buf,0,MAX_BUFFER_SIZE); issue_buf_pos = issue_buf; - + timer = evtimer_new(base, timer_cb, this); + } //void Connection::set_queue(ConcurrentQueue* a_trace_queue) { @@ -185,6 +186,8 @@ Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, void Connection::set_queue(queue* a_trace_queue) { trace_queue = a_trace_queue; + //while (trace_queue->size() < 1); + //usleep(1000); } void Connection::set_lock(pthread_mutex_t* a_lock) { @@ -257,7 +260,7 @@ int Connection::do_connect() { */ Connection::~Connection() { - //event_free(timer); + event_free(timer); timer = NULL; // FIXME: W("Drain op_q?"); bufferevent_free(bev); @@ -484,8 +487,7 @@ int Connection::issue_getsetorset(double now) { int ret = 0; - if (!options.read_file) - { + if (!options.read_file) { string keystr; char key[256]; memset(key,0,256); @@ -498,9 +500,9 @@ int Connection::issue_getsetorset(double now) { write(2,log,strlen(log)); issue_get_with_len(key, length, now); - } - else - { + + } else { + string line; string rT; string rApp; @@ -511,6 +513,7 @@ int Connection::issue_getsetorset(double now) { int nissued = 0; + //fprintf(stderr,"starting to issue, current %d\n",issue_buf_n); while (nissued < options.depth) { //bool res = trace_queue->try_dequeue(line); @@ -523,17 +526,7 @@ int Connection::issue_getsetorset(double now) { eof = 1; return 1; } - /* - pthread_mutex_lock(&flock); - if (kvfile.good()) { - getline(kvfile,line); - pthread_mutex_unlock(&flock); - } - else { - pthread_mutex_unlock(&flock); - return 1; - } - */ + stringstream ss(line); int Op = 0; int vl = 0; @@ -547,7 +540,8 @@ int Connection::issue_getsetorset(double now) { getline( ss, rOp, ',' ); //vl = atoi(rvaluelen.c_str()); vl = stoi(rvaluelen); - if (vl < 1) vl = 1; + //vl = 100000; + if (vl < 1) continue; if (vl > 524000) vl = 524000; if (rOp.compare("get") == 0) { Op = 1; @@ -566,11 +560,7 @@ int Connection::issue_getsetorset(double now) { getline( ss, rvaluelen, ',' ); Op = stoi(rOp); vl = stoi(rvaluelen); - //char outbuf[1024]; - //sprintf(outbuf,"%s\n",line.c_str()); - //write(1,outbuf,strlen(outbuf)); - } - else { + } else { getline( ss, rT, ',' ); getline( ss, rApp, ',' ); getline( ss, rOp, ',' ); @@ -591,8 +581,8 @@ int Connection::issue_getsetorset(double now) { switch(Op) { case 0: - fprintf(stderr,"invalid line: %s, vl: %d @T: %d\n", - key,vl,stoi(rT)); + //fprintf(stderr,"invalid line: %s, vl: %d @T: %d\n", + // key,vl,stoi(rT)); break; case 1: if (nissued < options.depth-1) { @@ -602,9 +592,6 @@ int Connection::issue_getsetorset(double now) { issued = issue_get_with_len(key, vl, now, false); last_quiet = false; } - //} else { - // issued = issue_get_with_len(key, vl, now, false); - //} break; case 2: if (last_quiet) { @@ -620,19 +607,30 @@ int Connection::issue_getsetorset(double now) { nissued++; total++; } else { - fprintf(stderr,"failed to issue line: %s, vl: %d @T: %d\n", - key,vl,stoi(rT)); + if (Op != 0) { + fprintf(stderr,"failed to issue line: %s, vl: %d @T: %d\n", + key,vl,stoi(rT)); + } break; } + } else { + if (stats.accesses > 10) { + eof = 1; + return 1; + } } } - //fprintf(stderr,"getsetorset issuing %d reqs\n",issue_buf_n); + //fprintf(stderr,"done issue, current %d\n",issue_buf_n); + if (last_quiet) { + issue_noop(); + last_quiet = false; + } + //fprintf(stderr,"getsetorset issuing %d reqs last quiet %d\n",issue_buf_n,last_quiet); //char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); //fprintf(stderr,"-------------------------------------\n"); //memcpy(output,issue_buf,issue_buf_size); //write(2,output,issue_buf_size); //fprintf(stderr,"\n-------------------------------------\n"); - //free(output); //buffer is ready to go! bufferevent_write(bev, issue_buf, issue_buf_size); @@ -641,10 +639,10 @@ int Connection::issue_getsetorset(double now) { issue_buf_pos = issue_buf; issue_buf_size = 0; issue_buf_n = 0; + } + + return ret; - } - - return ret; } /** @@ -924,12 +922,32 @@ int Connection::issue_set(const char* key, const char* value, int length, // each line is 4-bytes binary_header_t h = { 0x80, CMD_SET, htons(keylen), 0x08, 0x00, {htons(0)}, - htonl(keylen + 8 + length) }; + htonl(keylen + 8 + length) }; h.opaque = htonl(op.opaque); + + memcpy(issue_buf_pos,&h,24); + issue_buf_pos += 24; + issue_buf_size += 24; + if (options.miss_through && is_access) { + uint32_t flags = htonl(16384); + memcpy(issue_buf_pos,&flags,4); + issue_buf_pos += 4; + issue_buf_size += 4; + uint32_t exp = 0; + memcpy(issue_buf_pos,&exp,4); + issue_buf_pos += 4; + issue_buf_size += 4; - memcpy(issue_buf_pos,&h,32); - issue_buf_pos += 32; - issue_buf_size += 32; + } else { + uint32_t flags = 0; + memcpy(issue_buf_pos,&flags,4); + issue_buf_pos += 4; + issue_buf_size += 4; + uint32_t exp = 0; + memcpy(issue_buf_pos,&exp,4); + issue_buf_pos += 4; + issue_buf_size += 4; + } memcpy(issue_buf_pos,key,keylen); issue_buf_pos += keylen; issue_buf_size += keylen; @@ -938,6 +956,7 @@ int Connection::issue_set(const char* key, const char* value, int length, issue_buf_size += length; issue_buf_n++; + //if (read_state == IDLE) read_state = WAITING_FOR_SET; //l = prot->set_request(key, value, length, op->opaque); @@ -1196,8 +1215,8 @@ void Connection::drive_write_machine(double now) { delay = iagen->generate(); next_time = now + delay; double_to_tv(delay, &tv); - //evtimer_add(timer, &tv); - //write_state = WAITING_FOR_TIME; + evtimer_add(timer, &tv); + write_state = WAITING_FOR_TIME; write_state = ISSUING; break; @@ -1206,23 +1225,22 @@ void Connection::drive_write_machine(double now) { write_state = WAITING_FOR_OPQ; return; } - //uncommenting for lowest delay possible - //if (op_queue.size() >= (size_t) options.depth) { - // write_state = WAITING_FOR_OPQ; - // return; - //} else if (now < next_time) { - // write_state = WAITING_FOR_TIME; - // break; // We want to run through the state machine one more time - // // to make sure the timer is armed. - //} else if (options.moderate && now < last_rx + 0.00025) { - // write_state = WAITING_FOR_TIME; - // if (!event_pending(timer, EV_TIMEOUT, NULL)) { - // delay = last_rx + 0.00025 - now; - // double_to_tv(delay, &tv); - // evtimer_add(timer, &tv); - // } - // return; - //} + if (op_queue.size() >= (size_t) options.depth) { + write_state = WAITING_FOR_OPQ; + return; + } else if (now < next_time) { + write_state = WAITING_FOR_TIME; + break; // We want to run through the state machine one more time + // to make sure the timer is armed. + } else if (options.moderate && now < last_rx + 0.00025) { + write_state = WAITING_FOR_TIME; + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + delay = last_rx + 0.00025 - now; + double_to_tv(delay, &tv); + evtimer_add(timer, &tv); + } + return; + } if (options.getsetorset) { int ret = issue_getsetorset(now); @@ -1232,36 +1250,35 @@ void Connection::drive_write_machine(double now) { } last_tx = now; - //stats.log_op(op_queue.size()); + stats.log_op(op_queue.size()); stats.log_op(op_queue_size); - //next_time += iagen->generate(); + next_time += iagen->generate(); - //if (options.skip && options.lambda > 0.0 && - // now - next_time > 0.005000 && - // op_queue.size() >= (size_t) options.depth) { + if (options.skip && options.lambda > 0.0 && + now - next_time > 0.005000 && + op_queue.size() >= (size_t) options.depth) { - // while (next_time < now - 0.004000) { - // stats.skips++; - // next_time += iagen->generate(); - // } - //} + while (next_time < now - 0.004000) { + stats.skips++; + next_time += iagen->generate(); + } + } break; case WAITING_FOR_TIME: if (now < next_time) { - //if (!event_pending(timer, EV_TIMEOUT, NULL)) { - // delay = next_time - now; - // double_to_tv(delay, &tv); - // //evtimer_add(timer, &tv); - //} - //return; + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + delay = next_time - now; + double_to_tv(delay, &tv); + evtimer_add(timer, &tv); + } + return; } write_state = ISSUING; break; case WAITING_FOR_OPQ: if (op_queue_size >= (size_t) options.depth) return; - //if (op_queue.size() >= (size_t) options.depth) return; write_state = ISSUING; break; @@ -1303,6 +1320,9 @@ void Connection::read_callback() { full_read = prot->handle_response(input, done, found, opcode, opaque); if (full_read) { if (opcode == CMD_NOOP) { + //char out[128]; + //sprintf(out,"conn: %u, reading noop\n",cid); + //write(2,out,strlen(out)); continue; } op = &op_queue[opaque]; @@ -1310,6 +1330,12 @@ void Connection::read_callback() { //sprintf(out,"conn: %u, reading opaque: %u\n",cid,opaque); //write(2,out,strlen(out)); //output_op(op,2,found); + if (op->key.length() < 1) { + //char out2[128]; + //sprintf(out2,"conn: %u, bad op: %s\n",cid,op->key.c_str()); + //write(2,out2,strlen(out2)); + continue; + } } else { break; } @@ -1318,20 +1344,20 @@ void Connection::read_callback() { switch (op->type) { case Operation::GET: if (done) { - if ((!found && options.getset) || - (!found && options.getsetorset)) { + if ( !found && (options.getset || options.getsetorset) ) {// && + //(options.twitter_trace != 1)) { char key[256]; string keystr = op->key; strcpy(key, keystr.c_str()); int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); + finish_op(op,0); // sets read_state = IDLE if (last_quiet) { issue_noop(); } //issue_set_miss(key, &random_char[index], valuelen); issue_set(key, &random_char[index], valuelen, false); last_quiet = false; - finish_op(op,0); // sets read_state = IDLE } else { if (found) { @@ -1367,14 +1393,18 @@ void Connection::read_callback() { //buffer is ready to go! //if (issue_buf_n >= options.depth) { if (issue_buf_n > 0) { - //fprintf(stderr,"read_cb writing %d reqs\n",issue_buf_n); - // char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); - // fprintf(stderr,"-------------------------------------\n"); - // memcpy(output,issue_buf,issue_buf_size); - // write(2,output,issue_buf_size); - // fprintf(stderr,"\n-------------------------------------\n"); - // - // free(output); + if (last_quiet) { + issue_noop(); + last_quiet = false; + } + //fprintf(stderr,"read_cb writing %d reqs, last quiet %d\n",issue_buf_n,last_quiet); + //char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); + //fprintf(stderr,"-------------------------------------\n"); + //memcpy(output,issue_buf,issue_buf_size); + //write(2,output,issue_buf_size); + //fprintf(stderr,"\n-------------------------------------\n"); + //free(output); + bufferevent_write(bev, issue_buf, issue_buf_size); memset(issue_buf,0,issue_buf_size); issue_buf_pos = issue_buf; @@ -1382,14 +1412,16 @@ void Connection::read_callback() { issue_buf_n = 0; } - if (op_queue_size >= (uint32_t) options.depth) { - return; - } else { - issue_getsetorset(now); - } + //if (op_queue_size > (uint32_t) options.depth) { + // fprintf(stderr,"read_cb opqueue too big %d\n",op_queue_size); + // return; + //} else { + // fprintf(stderr,"read_cb issing %d\n",op_queue_size); + // issue_getsetorset(now); + //} last_tx = now; stats.log_op(op_queue_size); - //drive_write_machine(); + drive_write_machine(); // update events //if (bev != NULL) { @@ -1412,9 +1444,16 @@ void Connection::write_callback() { * Callback for timer timeouts. */ void Connection::timer_callback() { - fprintf(stderr,"timer callback issuing requests!\n"); - //drive_write_machine(); + drive_write_machine(); } +// //fprintf(stderr,"timer callback issuing requests!\n"); +// if (op_queue_size >= (size_t) options.depth) { +// return; +// } else { +// double now = get_time(); +// issue_getsetorset(now); +// } +//} /* The follow are C trampolines for libevent callbacks. */ diff --git a/ConnectionOptions.h b/ConnectionOptions.h index 8b4266f..59bf6a1 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -5,6 +5,7 @@ typedef struct { int apps; + bool miss_through; int connections; bool blocking; double lambda; diff --git a/Protocol.cc b/Protocol.cc index c76aaff..545ef4f 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -596,8 +596,9 @@ bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, i reinterpret_cast(evbuffer_pullup(input, 24)); assert(h); + int bl = ntohl(h->body_len); // Not whole response - int targetLen = 24 + ntohl(h->body_len); + int targetLen = 24 + bl; if (length < targetLen) return false; //fprintf(stderr,"handle resp - opcode: %u opaque: %u len: %u status: %u\n", // h->opcode,ntohl(h->opaque), @@ -606,21 +607,29 @@ bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, i opcode = h->opcode; opaque = ntohl(h->opaque); // If something other than success, count it as a miss - if (h->opcode == CMD_GET && h->status) { + if (opcode == CMD_GET && h->status) { conn->stats.get_misses++; conn->stats.window_get_misses++; found = false; } - if (unlikely(h->opcode == CMD_SASL)) { + if (unlikely(opcode == CMD_SASL)) { if (h->status == RESP_OK) { V("SASL authentication succeeded"); } else { DIE("SASL authentication failed"); } } + + if (bl > 0 && opcode == 1) { + //fprintf(stderr,"set resp len: %u\n",bl); + void *data = malloc(bl); + data = evbuffer_pullup(input, bl); + free(data); + } else { + evbuffer_drain(input, targetLen); + } - evbuffer_drain(input, targetLen); conn->stats.rx_bytes += targetLen; done = true; return true; diff --git a/cmdline.ggo b/cmdline.ggo index 5e378c5..b0b2499 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -35,6 +35,7 @@ option "assoc" - "We create hash tables by taking the truncating the \ option "qps" q "Target aggregate QPS. 0 = peak QPS." int default="0" option "time" t "Maximum time to run (seconds)." int default="5" option "apps" - "Number of apps, should eqaul total conns" int default="1" +option "miss_through" - "All sets are considered dirty, expect for miss driven sets" option "read_file" - "Read keys from file." string default="" option "twitter_trace" - "use twitter memcached trace format from file." int default="0" diff --git a/mutilate.cc b/mutilate.cc index 5d3da9c..3c86a5c 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -82,6 +82,7 @@ struct reader_data { std::vector*> trace_queue; std::vector mutexes; string trace_filename; + int twitter_trace; }; // struct evdns_base *evdns; @@ -704,6 +705,7 @@ void go(const vector& servers, options_t& options, struct reader_data *rdata = (struct reader_data*)malloc(sizeof(struct reader_data)); rdata->trace_queue = trace_queue; rdata->mutexes = mutexes; + rdata->twitter_trace = options.twitter_trace; pthread_t rtid; if (options.read_file) { rdata->trace_filename = options.file_name; @@ -921,7 +923,7 @@ void* reader_thread(void *arg) { //std::vector*> trace_queue = (std::vector*>) rdata->trace_queue; std::vector*> trace_queue = (std::vector*>) rdata->trace_queue; std::vector mutexes = (std::vector) rdata->mutexes; - + int twitter_trace = rdata->twitter_trace; if (hasEnding(rdata->trace_filename,".zst")) { //init const char *filename = rdata->trace_filename.c_str(); @@ -948,15 +950,39 @@ void* reader_thread(void *arg) { string full_line(line); //check the appid int appid = 0; - if (full_line.length() > 4) { + if (full_line.length() > 10) { if (trace_queue.size() > 1) { stringstream ss(full_line); string rT; string rApp; - getline( ss, rT, ','); - getline( ss, rApp, ','); - appid = stoi(rApp); + if (twitter_trace == 1) { + string rKey; + string rKeySize; + string rvaluelen; + size_t n = std::count(full_line.begin(), full_line.end(), ','); + if (n == 6) { + getline( ss, rT, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rKeySize, ',' ); + getline( ss, rvaluelen, ',' ); + getline( ss, rApp, ',' ); + appid = stoi(rApp) % trace_queue.size(); + } else { + continue; + } + + } + else if (twitter_trace == 2) { + size_t n = std::count(full_line.begin(), full_line.end(), ','); + if (n == 4) { + getline( ss, rT, ','); + getline( ss, rApp, ','); + appid = stoi(rApp); + } else { + continue; + } + } if (appid < trace_queue.size()) { pthread_mutex_lock(mutexes[appid]); trace_queue[appid]->push(full_line); @@ -1372,6 +1398,7 @@ void args_to_options(options_t* options) { options->twitter_trace = args.twitter_trace_arg; options->unix_socket = args.unix_socket_given; + options->miss_through = args.miss_through_given; options->successful_queries = args.successful_given; options->binary = args.binary_given; options->redis = args.redis_given; From d2a279d855b3f1c740c585bb6b04bf24ff588fc7 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Tue, 7 Sep 2021 10:55:02 -0400 Subject: [PATCH 35/57] halfway to multi connection --- Connection.cc | 136 ++---- Connection.h | 136 +++++- ConnectionMulti.cc | 1023 ++++++++++++++++++++++++++++++++++++++++++++ Operation.h | 3 +- Protocol.cc | 7 +- mutilate.cc | 26 +- 6 files changed, 1228 insertions(+), 103 deletions(-) create mode 100644 ConnectionMulti.cc diff --git a/Connection.cc b/Connection.cc index d696f2a..33cdd36 100644 --- a/Connection.cc +++ b/Connection.cc @@ -27,6 +27,7 @@ #include #include "blockingconcurrentqueue.h" +//#define DEBUGC using namespace moodycamel; std::hash hashstr; @@ -598,6 +599,7 @@ int Connection::issue_getsetorset(double now) { issue_noop(now); } int index = lrand48() % (1024 * 1024); + //issued = issue_get_with_len(key, vl, now, false); issued = issue_set(key, &random_char[index], vl, now, true); last_quiet = false; break; @@ -614,10 +616,13 @@ int Connection::issue_getsetorset(double now) { break; } } else { - if (stats.accesses > 10) { - eof = 1; - return 1; - } +//#ifdef DEBUGC + return 0; + //fprintf(stderr,"trace_queue size: %d\n",trace_queue->size()); + //if (stats.accesses > 10) { + // eof = 1; + // return 1; + //} } } //fprintf(stderr,"done issue, current %d\n",issue_buf_n); @@ -625,13 +630,15 @@ int Connection::issue_getsetorset(double now) { issue_noop(); last_quiet = false; } - //fprintf(stderr,"getsetorset issuing %d reqs last quiet %d\n",issue_buf_n,last_quiet); - //char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); - //fprintf(stderr,"-------------------------------------\n"); - //memcpy(output,issue_buf,issue_buf_size); - //write(2,output,issue_buf_size); - //fprintf(stderr,"\n-------------------------------------\n"); - //free(output); +#ifdef DEBUGC + fprintf(stderr,"getsetorset issuing %d reqs last quiet %d\n",issue_buf_n,last_quiet); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); + fprintf(stderr,"-------------------------------------\n"); + memcpy(output,issue_buf,issue_buf_size); + write(2,output,issue_buf_size); + fprintf(stderr,"\n-------------------------------------\n"); + free(output); +#endif //buffer is ready to go! bufferevent_write(bev, issue_buf, issue_buf_size); @@ -698,7 +705,8 @@ int Connection::issue_get_with_len(const char* key, int valuelen, double now, bo htonl(keylen) }; if (quiet) { - h.opcode = CMD_GETQ; + //h.opcode = CMD_GETQ; + h.opcode = CMD_GET; } h.opaque = htonl(op.opaque); @@ -1124,69 +1132,12 @@ void Connection::event_callback(short events) { } else if (events & BEV_EVENT_ERROR) { int err = bufferevent_socket_get_dns_error(bev); - //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); fprintf(stderr,"Got an error: %s\n", evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); - - //stats.print_header(); - //stats.print_stats("read", stats.get_sampler); - //stats.print_stats("update", stats.set_sampler); - //stats.print_stats("op_q", stats.op_sampler); - - //int total = stats.gets + stats.sets; - - //printf("\nTotal QPS = %.1f (%d / %.1fs)\n", - // total / (stats.stop - stats.start), - // total, stats.stop - stats.start); - - - //printf("\n"); - - //printf("Misses = %" PRIu64 " (%.1f%%)\n", stats.get_misses, - // (double) stats.get_misses/stats.gets*100); - - //printf("Skipped TXs = %" PRIu64 " (%.1f%%)\n\n", stats.skips, - // (double) stats.skips / total * 100); - - //printf("RX %10" PRIu64 " bytes : %6.1f MB/s\n", - // stats.rx_bytes, - // (double) stats.rx_bytes / 1024 / 1024 / (stats.stop - stats.start)); - //printf("TX %10" PRIu64 " bytes : %6.1f MB/s\n", - // stats.tx_bytes, - // (double) stats.tx_bytes / 1024 / 1024 / (stats.stop - stats.start)); - - //if ( - //DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + DIE("BEV_EVENT_ERROR: %s", strerror(errno)); } else if (events & BEV_EVENT_EOF) { - //stats.print_header(); - //stats.print_stats("read", stats.get_sampler); - //stats.print_stats("update", stats.set_sampler); - //stats.print_stats("op_q", stats.op_sampler); - - //int total = stats.gets + stats.sets; - - //printf("\nTotal QPS = %.1f (%d / %.1fs)\n", - // total / (stats.stop - stats.start), - // total, stats.stop - stats.start); - - - //printf("\n"); - - //printf("Misses = %" PRIu64 " (%.1f%%)\n", stats.get_misses, - // (double) stats.get_misses/stats.gets*100); - - //printf("Skipped TXs = %" PRIu64 " (%.1f%%)\n\n", stats.skips, - // (double) stats.skips / total * 100); - - //printf("RX %10" PRIu64 " bytes : %6.1f MB/s\n", - // stats.rx_bytes, - // (double) stats.rx_bytes / 1024 / 1024 / (stats.stop - stats.start)); - //printf("TX %10" PRIu64 " bytes : %6.1f MB/s\n", - // stats.tx_bytes, - // (double) stats.tx_bytes / 1024 / 1024 / (stats.stop - stats.start)); - //DIE("Unexpected EOF from server."); fprintf(stderr,"Unexpected EOF from server."); return; @@ -1250,7 +1201,6 @@ void Connection::drive_write_machine(double now) { } last_tx = now; - stats.log_op(op_queue.size()); stats.log_op(op_queue_size); next_time += iagen->generate(); @@ -1320,16 +1270,20 @@ void Connection::read_callback() { full_read = prot->handle_response(input, done, found, opcode, opaque); if (full_read) { if (opcode == CMD_NOOP) { - //char out[128]; - //sprintf(out,"conn: %u, reading noop\n",cid); - //write(2,out,strlen(out)); +#ifdef DEBUGC + char out[128]; + sprintf(out,"conn: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif continue; } op = &op_queue[opaque]; - //char out[128]; - //sprintf(out,"conn: %u, reading opaque: %u\n",cid,opaque); - //write(2,out,strlen(out)); - //output_op(op,2,found); +#ifdef DEBUGC + char out[128]; + sprintf(out,"conn: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif if (op->key.length() < 1) { //char out2[128]; //sprintf(out2,"conn: %u, bad op: %s\n",cid,op->key.c_str()); @@ -1386,10 +1340,12 @@ void Connection::read_callback() { if (check_exit_condition(now)) { return; } - //fprintf(stderr,"read_cb done with current queue of ops: %d and issue_buf_n: %d\n",op_queue_size,issue_buf_n); - //for (auto x : op_queue) { - // cerr << x.first << ": " << x.second.key << endl; - //} +#ifdef DEBUGC + fprintf(stderr,"read_cb done with current queue of ops: %d and issue_buf_n: %d\n",op_queue_size,issue_buf_n); + for (auto x : op_queue) { + cerr << x.first << ": " << x.second.key << endl; + } +#endif //buffer is ready to go! //if (issue_buf_n >= options.depth) { if (issue_buf_n > 0) { @@ -1397,13 +1353,15 @@ void Connection::read_callback() { issue_noop(); last_quiet = false; } - //fprintf(stderr,"read_cb writing %d reqs, last quiet %d\n",issue_buf_n,last_quiet); - //char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); - //fprintf(stderr,"-------------------------------------\n"); - //memcpy(output,issue_buf,issue_buf_size); - //write(2,output,issue_buf_size); - //fprintf(stderr,"\n-------------------------------------\n"); - //free(output); +#ifdef DEBUGC + fprintf(stderr,"read_cb writing %d reqs, last quiet %d\n",issue_buf_n,last_quiet); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); + fprintf(stderr,"-------------------------------------\n"); + memcpy(output,issue_buf,issue_buf_size); + write(2,output,issue_buf_size); + fprintf(stderr,"\n-------------------------------------\n"); + free(output); +#endif bufferevent_write(bev, issue_buf, issue_buf_size); memset(issue_buf,0,issue_buf_size); diff --git a/Connection.h b/Connection.h index 1c8236a..3b314a4 100644 --- a/Connection.h +++ b/Connection.h @@ -122,14 +122,6 @@ class Connection { uint32_t cid; int eof; - //trace format variables - //double r_time; // time in seconds - //int r_appid; // prefix minus ':' char - //int r_type; //1 = get, 2 = set - //int r_ksize; //key size - //int r_vsize; //-1 or size of value if hit - //int r_key; //op->key as int - //int r_hit; //1 if hit, 0 if miss Protocol *prot; Generator *valuesize; @@ -137,7 +129,6 @@ class Connection { KeyGenerator *keygen; Generator *iagen; std::unordered_map op_queue; - //std::vector op_queue; uint32_t op_queue_size; pthread_mutex_t* lock; //ConcurrentQueue *trace_queue; @@ -178,4 +169,131 @@ class Connection { bool consume_resp_line(evbuffer *input, bool &done); }; +class ConnectionMulti { +public: + Connection(struct event_base* _base1, struct event_base* _base2, struct evdns_base* _evdns, + string _hostname1, string _hostname2, string _port, options_t options, + bool sampling = true); + + ~Connection(); + + int do_connect(); + + double start_time; // Time when this connection began operations. + ConnectionStats stats; + options_t options; + + bool is_ready() { return read_state == IDLE; } + void set_priority(int pri); + + // state commands + void start() { drive_write_machine(); } + void start_loading(); + void reset(); + bool check_exit_condition(double now = 0.0); + + // event callbacks + void event_callback(short events); + void read_callback(); + void write_callback(); + void timer_callback(); + + uint32_t get_cid(); + //void set_queue(ConcurrentQueue *a_trace_queue); + void set_queue(queue *a_trace_queue); + void set_lock(pthread_mutex_t* a_lock); + +private: + string hostname; + string port; + + struct event_base *base1; + struct event_base *base2; + struct evdns_base *evdns; + struct bufferevent *bev1; + struct bufferevent *bev2; + + struct event *timer; // Used to control inter-transmission time. + double next_time; // Inter-transmission time parameters. + double last_rx; // Used to moderate transmission rate. + double last_tx; + + enum read_state_enum { + INIT_READ, + CONN_SETUP, + LOADING, + IDLE, + WAITING_FOR_GET, + WAITING_FOR_SET, + WAITING_FOR_DELETE, + MAX_READ_STATE, + }; + + enum write_state_enum { + INIT_WRITE, + ISSUING, + WAITING_FOR_TIME, + WAITING_FOR_OPQ, + MAX_WRITE_STATE, + }; + + read_state_enum read_state; + write_state_enum write_state; + + // Parameters to track progress of the data loader. + int loader_issued, loader_completed; + + uint32_t **opaque; + int *issue_buf_size; + int *issue_buf_n; + unsigned char **issue_buf_pos; + unsigned char **issue_buf; + bool last_quiet1; + bool last_quiet2; + uint32_t total; + uint32_t cid; + int eof; + + + Protocol *prot; + Generator *valuesize; + Generator *keysize; + KeyGenerator *keygen; + Generator *iagen; + std::vector> op_queue; + uint32_t op_queue_size; + pthread_mutex_t* lock; + //ConcurrentQueue *trace_queue; + queue *trace_queue; + + // state machine functions / event processing + void pop_op(Operation *op); + void output_op(Operation *op, int type, bool was_found); + //void finish_op(Operation *op); + void finish_op(Operation *op,int was_hit); + int issue_getsetorset(double now = 0.0); + void drive_write_machine(double now = 0.0); + + // request functions + void issue_sasl(); + void issue_noop(double now = 0.0); + void issue_get(const char* key, double now = 0.0); + int issue_get_with_len(const char* key, int valuelen, double now = 0.0, bool quiet = false); + int issue_set(const char* key, const char* value, int length, + double now = 0.0, bool is_access = false); + + // protocol fucntions + int set_request_ascii(const char* key, const char* value, int length); + int set_request_binary(const char* key, const char* value, int length); + int set_request_resp(const char* key, const char* value, int length); + + int get_request_ascii(const char* key); + int get_request_binary(const char* key); + int get_request_resp(const char* key); + + bool consume_binary_response(evbuffer *input); + bool consume_ascii_line(evbuffer *input, bool &done); + bool consume_resp_line(evbuffer *input, bool &done); +}; + #endif diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc new file mode 100644 index 0000000..0fba149 --- /dev/null +++ b/ConnectionMulti.cc @@ -0,0 +1,1023 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "Connection.h" +#include "distributions.h" +#include "Generator.h" +#include "mutilate.h" +#include "binary_protocol.h" +#include "util.h" +#include +#include +#include +#include +#include +#include "blockingconcurrentqueue.h" + +#define LEVELS 2 + +//#define DEBUGMC + +using namespace moodycamel; +std::hash hashstr; + +extern ifstream kvfile; +extern pthread_mutex_t flock; +extern int item_lock_hashpower; + + +pthread_mutex_t cid_lock = PTHREAD_MUTEX_INITIALIZER; +static uint32_t connids = 1; + + +void ConnectionMulti::output_op(Operation *op, int type, bool found) { + char output[1024]; + char k[256]; + char a[256]; + char s[256]; + memset(k,0,256); + memset(a,0,256); + memset(s,0,256); + strcpy(k,op->key.c_str()); + switch (type) { + case 0: //get + sprintf(a,"issue_get"); + break; + case 1: //set + sprintf(a,"issue_set"); + break; + case 2: //resp + sprintf(a,"resp"); + break; + } + switch(read_state) { + case INIT_READ: + sprintf(s,"init"); + break; + case CONN_SETUP: + sprintf(s,"setup"); + break; + case LOADING: + sprintf(s,"load"); + break; + case IDLE: + sprintf(s,"idle"); + break; + case WAITING_FOR_GET: + sprintf(s,"waiting for get"); + break; + case WAITING_FOR_SET: + sprintf(s,"waiting for set"); + break; + case WAITING_FOR_DELETE: + sprintf(s,"waiting for del"); + break; + case MAX_READ_STATE: + sprintf(s,"max"); + break; + } + if (type == 2) { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, found: %d, type: %d\n",cid,a,k,op->opaque,found,op->type); + } else { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, type: %d\n",cid,a,k,op->opaque,op->type); + } + write(2,output,strlen(output)); +} + +/** + * Create a new connection to a server endpoint. + */ +ConnectionMulti::ConnectionMulti(struct event_base* _base1, struct event_base* _base2, struct evdns_base* _evdns, + string _hostname1, string _hostname2, string _port, options_t _options, + bool sampling ) : + start_time(0), stats(sampling), options(_options), + hostname1(_hostname1), hostname2(_hostname2), port(_port), base1(_base1), base2(_base2), evdns(_evdns) +{ + valuesize = createGenerator(options.valuesize); + keysize = createGenerator(options.keysize); + + keygen = new KeyGenerator(keysize, options.records); + + total = 0; + eof = 0; + + if (options.lambda <= 0) { + iagen = createGenerator("0"); + } else { + D("iagen = createGenerator(%s)", options.ia); + iagen = createGenerator(options.ia); + iagen->set_lambda(options.lambda); + } + + read_state = INIT_READ; + write_state = INIT_WRITE; + last_quiet1 = false; + last_quiet2 = false; + + last_tx = last_rx = 0.0; + + pthread_mutex_lock(&cid_lock); + cid = connids++; + pthread_mutex_unlock(&cid_lock); + + op_queue_size = malloc(sizeof(int)*LEVELS+1); + opaque = malloc(sizeof(int)*LEVELS+1); + + issue_buf_n = malloc(sizeof(int)*LEVELS+1); + issue_buf = malloc(sizeof(unsigned char*)*LEVELS+1); + issue_buf_pos = malloc(sizeof(unsigned char*)*LEVELS+1); + + for (int i = 1; i <= LEVELS; i++) { + op_queue_size[i] = 0; + opaque[i] = 0; + + issue_buf[i] = (unsigned char*)malloc(sizeof(unsigned char)*MAX_BUFFER_SIZE); + issue_buf_pos[i] = issue_buf; + issue_buf_size[i] = 0; + + } + + timer = evtimer_new(base1, timer_cb, this); + +} + + +void ConnectionMulti::set_queue(queue* a_trace_queue) { + trace_queue = a_trace_queue; +} + +void ConnectionMulti::set_lock(pthread_mutex_t* a_lock) { + lock = a_lock; +} + +uint32_t ConnectionMulti::get_cid() { + return cid; +} + +int ConnectionMulti::do_connect() { + + int connected = 0; + if (options.unix_socket) { + + bev1 = bufferevent_socket_new(base1, -1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev1, bev1_read_cb, bev1_write_cb, bev1_event_cb, this); + bufferevent_enable(bev1, EV_READ | EV_WRITE); + + bev2 = bufferevent_socket_new(base2, -1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev2, bev2_read_cb, bev2_write_cb, bev2_event_cb, this); + bufferevent_enable(bev2, EV_READ | EV_WRITE); + + struct sockaddr_un sin1; + memset(&sin1, 0, sizeof(sin1)); + sin1.sun_family = AF_LOCAL; + strcpy(sin1.sun_path, hostname1.c_str()); + + int addrlen; + addrlen = sizeof(sin1); + int err = bufferevent_socket_connect(bev1, (struct sockaddr*)&sin1, addrlen); + if (err == 0) { + connected = 1; + if (options.binary) { + prot = new ProtocolBinary(options, this, bev1); + } else if (options.redis) { + prot = new ProtocolRESP(options, this, bev1); + } else { + prot = new ProtocolAscii(options, this, bev1); + } + } else { + connected = 0; + err = errno; + fprintf(stderr,"error %s\n",strerror(err)); + bufferevent_free(bev1); + } + + struct sockaddr_un sin2; + memset(&sin2, 0, sizeof(sin2)); + sin2.sun_family = AF_LOCAL; + strcpy(sin2.sun_path, hostname2.c_str()); + + addrlen = sizeof(sin2); + int err = bufferevent_socket_connect(bev2, (struct sockaddr*)&sin2, addrlen); + if (err == 0) { + connected = 1; + if (options.binary) { + prot = new ProtocolBinary(options, this, bev2); + } else if (options.redis) { + prot = new ProtocolRESP(options, this, bev2); + } else { + prot = new ProtocolAscii(options, this, bev2); + } + } else { + connected = 0; + err = errno; + fprintf(stderr,"error %s\n",strerror(err)); + bufferevent_free(bev2); + } + } + return connected; +} + +/** + * Destroy a connection, performing cleanup. + */ +ConnectionMulti::~ConnectionMulti() { + + event_free(timer); + timer = NULL; + // FIXME: W("Drain op_q?"); + bufferevent_free(bev1); + bufferevent_free(bev2); + + delete iagen; + delete keygen; + delete keysize; + delete valuesize; +} + +/** + * Reset the connection back to an initial, fresh state. + */ +void ConnectionMulti::reset() { + // FIXME: Actually check the connection, drain all bufferevents, drain op_q. + //assert(op_queue.size() == 0); + //evtimer_del(timer); + read_state = IDLE; + write_state = INIT_WRITE; + stats = ConnectionStats(stats.sampling); +} + +/** + * Set our event processing priority. + */ +void ConnectionMulti::set_priority(int pri) { + if (bufferevent_priority_set(bev1, pri)) { + DIE("bufferevent_set_priority(bev, %d) failed", pri); + } +} + + + +/** + * Get/Set or Set Style + * If a GET command: Issue a get first, if not found then set + * If trace file (or prob. write) says to set, then set it + */ +int ConnectionMulti::issue_getsetorset(double now) { + + + string line; + string rT; + string rApp; + string rOp; + string rKey; + string rKeySize; + string rvaluelen; + + int ret = 0; + int nissued = 0; + while (nissued < options.depth) { + + if (trace_queue->size() > 0) { + pthread_mutex_lock(lock); + line = trace_queue->front(); + trace_queue->pop(); + pthread_mutex_unlock(lock); + if (line.compare("EOF") == 0) { + eof = 1; + return 1; + } + + stringstream ss(line); + int Op = 0; + int vl = 0; + + if (options.twitter_trace == 1) { + getline( ss, rT, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rKeySize, ',' ); + getline( ss, rvaluelen, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + vl = stoi(rvaluelen); + if (vl < 1) continue; + if (vl > 524000) vl = 524000; + if (rOp.compare("get") == 0) { + Op = 1; + } else if (rOp.compare("set") == 0) { + Op = 2; + } else { + Op = 0; + } + + + } else if (options.twitter_trace == 2) { + getline( ss, rT, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + Op = stoi(rOp); + vl = stoi(rvaluelen); + } else { + getline( ss, rT, ',' ); + getline( ss, rApp, ',' ); + getline( ss, rOp, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + vl = stoi(rvaluelen); + if (rOp.compare("read") == 0) + Op = 1; + if (rOp.compare("write") == 0) + Op = 2; + } + + + char key[256]; + memset(key,0,256); + strncpy(key, rKey.c_str(),255); + int issued = 0; + switch(Op) + { + case 0: + //fprintf(stderr,"invalid line: %s, vl: %d @T: %d\n", + // key,vl,stoi(rT)); + break; + case 1: + if (nissued < options.depth-1) { + issued = issue_get_with_len(key, vl, now, true, 1); + last_quiet1 = true; + } else { + issued = issue_get_with_len(key, vl, now, false, 1); + last_quiet1 = false; + } + break; + case 2: + if (last_quiet1) { + issue_noop(now,1); + } + int index = lrand48() % (1024 * 1024); + issued = issue_set(key, &random_char[index], vl, now, true,1); + last_quiet = false; + break; + + } + if (issued) { + nissued++; + total++; + } else { + if (Op != 0) { + fprintf(stderr,"failed to issue line: %s, vl: %d @T: %d\n", + key,vl,stoi(rT)); + } + break; + } + } else { + //we should protect this with a condition variable + //since trace queue size is 0 and not EOF. + return 0; + } + } + if (last_quiet1) { + issue_noop(now,1); + last_quiet = false; + } +#ifdef DEBUGMC + fprintf(stderr,"getsetorset issuing %d reqs last quiet %d\n",issue_buf_n,last_quiet); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); + fprintf(stderr,"-------------------------------------\n"); + memcpy(output,issue_buf,issue_buf_size); + write(2,output,issue_buf_size); + fprintf(stderr,"\n-------------------------------------\n"); + free(output); +#endif + //buffer is ready to go! + bufferevent_write(bev1, issue_buf1, issue_buf_size1); + + memset(issue_buf1,0,issue_buf_size1); + issue_buf_pos1 = issue_buf1; + issue_buf_size1 = 0; + issue_buf_n1 = 0; + + return ret; + +} + +/** + * Issue a get request to the server. + */ +int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, int level) { + Operation op; + int l; + +#if HAVE_CLOCK_GETTIME + op.start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base1, &now_tv); + op.start_time = tv_to_double(&now_tv); +#else + op.start_time = get_time(); +#endif + } else { + op.start_time = now; + } +#endif + + op.key = string(key); + op.valuelen = valuelen; + op.type = Operation::GET; + op.opaque = opaque[level]++; + op.level = level; + op_queue[level][op.opaque] = op; + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 0; + } + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_GET, htons(keylen), + 0x00, 0x00, {htons(0)}, + htonl(keylen) }; + if (quiet) { + h.opcode = CMD_GETQ; + } + h.opaque = htonl(op.opaque); + + memcpy(issue_buf_pos[level],&h,24); + issue_buf_pos[level] += 24; + issue_buf_size[level] += 24; + memcpy(issue_buf_pos[level],key,keylen); + issue_buf_pos[level] += keylen; + issue_buf_size[level] += keylen; + issue_buf_n[level]++; + + if (read_state != LOADING) { + stats.tx_bytes += 24 + keylen; + } + + stats.log_access(op); + return 1; +} + +void ConnectionMulti::issue_noop(double now, int level) { + Operation op; + + if (now == 0.0) op.start_time = get_time(); + else op.start_time = now; + + binary_header_t h = { 0x80, CMD_NOOP, 0x0000, + 0x00, 0x00, {htons(0)}, + 0x00 }; + + memcpy(issue_buf_pos[level],&h,24); + issue_buf_pos[level] += 24; + issue_buf_size[level] += 24; + issue_buf_n[level]++; +} + +/** + * Issue a set request to the server. + */ +int ConnectionMulti::issue_set(const char* key, const char* value, int length, + double now, bool real_set, int level) { + Operation op; + int l; + +#if HAVE_CLOCK_GETTIME + op.start_time = get_time_accurate(); +#else + if (now == 0.0) op.start_time = get_time(); + else op.start_time = now; +#endif + + + op.key = string(key); + op.valuelen = length; + op.type = Operation::SET; + op.opaque = opaque[level]++; + op.level = level; + op_queue[level][op.opaque] = op; + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 0; + } + + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_SET, htons(keylen), + 0x08, 0x00, {htons(0)}, + htonl(keylen + 8 + length) }; + h.opaque = htonl(op.opaque); + + memcpy(issue_buf_pos[level],&h,24); + issue_buf_pos[level] += 24; + issue_buf_size[level] += 24; + + //miss through protocol assumes that every non miss driven set + //makes the data dirty -- assumes data is hit + if (options.miss_through && real_set && level == 1) { + uint32_t flags = htonl(16384); + memcpy(issue_buf_pos[level],&flags,4); + issue_buf_pos[level] += 4; + issue_buf_size[level] += 4; + uint32_t exp = 0; + memcpy(issue_buf_pos[level],&exp,4); + issue_buf_pos[level] += 4; + issue_buf_size[level] += 4; + + } else { + uint32_t flags = 0; + memcpy(issue_buf_pos[level],&flags,4); + issue_buf_pos[level] += 4; + issue_buf_size[level] += 4; + uint32_t exp = 0; + memcpy(issue_buf_pos[level],&exp,4); + issue_buf_pos[level] += 4; + issue_buf_size[level] += 4; + } + memcpy(issue_buf_pos[level],key,keylen); + issue_buf_pos[level] += keylen; + issue_buf_size[level] += keylen; + memcpy(issue_buf_pos[level],value,length); + issue_buf_pos[level] += length; + issue_buf_size[level] += length; + issue_buf_n[level]++; + + + if (read_state != LOADING) { + stats.tx_bytes += length + 32 + keylen; + } + + stats.log_access(op); + return 1; +} + +/** + * Return the oldest live operation in progress. + */ +void ConnectionMulti::pop_op(Operation *op) { + + uint32_t opopq = op->opaque; + uint8_t level = op->level; + op_queue[level].erase(opopq); + op_queue_size[level]--; + + + if (read_state == LOADING) return; + read_state = IDLE; + + // Advance the read state machine. + //if (op_queue.size() > 0) { + // Operation& op = op_queue.front(); + // switch (op.type) { + // case Operation::GET: read_state = WAITING_FOR_GET; break; + // case Operation::SET: read_state = WAITING_FOR_SET; break; + // case Operation::DELETE: read_state = WAITING_FOR_DELETE; break; + // default: DIE("Not implemented."); + // } + //} +} + +/** + * Finish up (record stats) an operation that just returned from the + * server. + */ +void ConnectionMulti::finish_op(Operation *op, int was_hit) { + double now; +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); +#else + now = get_time(); +#endif +#if HAVE_CLOCK_GETTIME + op->end_time = get_time_accurate(); +#else + op->end_time = now; +#endif + + if (options.successful_queries && was_hit) { + switch (op->type) { + case Operation::GET: stats.log_get(*op); break; + case Operation::SET: stats.log_set(*op); break; + case Operation::DELETE: break; + case Operation::TOUCH: break; + default: DIE("Not implemented."); + } + } else { + switch (op->type) { + case Operation::GET: stats.log_get(*op); break; + case Operation::SET: stats.log_set(*op); break; + case Operation::DELETE: break; + case Operation::TOUCH: break; + default: DIE("Not implemented."); + } + } + + last_rx = now; + uint32_t opopq = op->opaque; + uint8_t level = op->level; + op_queue[level].erase(opopq); + op_queue_size[level]--; + read_state = IDLE; + + //lets check if we should output stats for the window + //Do the binning for percentile outputs + //crude at start + if ((options.misswindow != 0) && ( ((stats.window_accesses) % options.misswindow) == 0)) + { + if (stats.window_gets != 0) + { + //printf("%lu,%.4f\n",(stats.accesses), + // ((double)stats.window_get_misses/(double)stats.window_accesses)); + stats.window_gets = 0; + stats.window_get_misses = 0; + stats.window_sets = 0; + stats.window_accesses = 0; + } + } + +} + + + +/** + * Check if our testing is done and we should exit. + */ +bool ConnectionMulti::check_exit_condition(double now) { + if (read_state == INIT_READ) return false; + if (now == 0.0) now = get_time(); + + if (options.read_file) { + if (eof) { + return true; + } + else if ((options.queries == 1) && + (now > start_time + options.time)) + { + return true; + } + else { + return false; + } + + } else { + if (options.queries != 0 && + (((long unsigned)options.queries) == (stats.accesses))) + { + return true; + } + if ((options.queries == 0) && + (now > start_time + options.time)) + { + return true; + } + if (options.loadonly && read_state == IDLE) return true; + } + + return false; +} + +/** + * Handle new connection and error events. + */ +void ConnectionMulti::event_callback(short events) { + if (events & BEV_EVENT_CONNECTED) { + D("Connected to %s:%s.", hostname.c_str(), port.c_str()); + int fd = bufferevent_getfd(bev); + if (fd < 0) DIE("bufferevent_getfd"); + + if (!options.no_nodelay && !options.unix_socket) { + int one = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (void *) &one, sizeof(one)) < 0) + DIE("setsockopt()"); + } + + read_state = CONN_SETUP; + if (prot->setup_connection_w()) { + read_state = IDLE; + } + drive_write_machine(); + + } else if (events & BEV_EVENT_ERROR) { + int err = bufferevent_socket_get_dns_error(bev); + //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); + if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); + fprintf(stderr,"Got an error: %s\n", + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); + + DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + + } else if (events & BEV_EVENT_EOF) { + fprintf(stderr,"Unexpected EOF from server."); + return; + } +} + +/** + * Request generation loop. Determines whether or not to issue a new command, + * based on timer events. + * + * Note that this function loops. Be wary of break vs. return. + */ +void ConnectionMulti::drive_write_machine(double now) { + if (now == 0.0) now = get_time(); + + double delay; + struct timeval tv; + + if (check_exit_condition(now)) { + return; + } + + while (1) { + switch (write_state) { + case INIT_WRITE: + delay = iagen->generate(); + next_time = now + delay; + double_to_tv(delay, &tv); + evtimer_add(timer, &tv); + write_state = WAITING_FOR_TIME; + write_state = ISSUING; + break; + + case ISSUING: + if (op_queue_size >= (size_t) options.depth) { + write_state = WAITING_FOR_OPQ; + return; + } + if (op_queue.size() >= (size_t) options.depth) { + write_state = WAITING_FOR_OPQ; + return; + } else if (now < next_time) { + write_state = WAITING_FOR_TIME; + break; // We want to run through the state machine one more time + // to make sure the timer is armed. + } else if (options.moderate && now < last_rx + 0.00025) { + write_state = WAITING_FOR_TIME; + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + delay = last_rx + 0.00025 - now; + double_to_tv(delay, &tv); + evtimer_add(timer, &tv); + } + return; + } + + if (options.getsetorset) { + int ret = issue_getsetorset(now); + if (ret) return; //if at EOF + } + + last_tx = now; + for (int i = 1; i <= 2; i++) { + stats.log_op(op_queue_size[i]); + } + next_time += iagen->generate(); + + if (options.skip && options.lambda > 0.0 && + now - next_time > 0.005000 && + op_queue.size() >= (size_t) options.depth) { + + while (next_time < now - 0.004000) { + stats.skips++; + next_time += iagen->generate(); + } + } + break; + + case WAITING_FOR_TIME: + if (now < next_time) { + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + delay = next_time - now; + double_to_tv(delay, &tv); + evtimer_add(timer, &tv); + } + return; + } + write_state = ISSUING; + break; + + case WAITING_FOR_OPQ: + if (op_queue_size[1] >= (size_t) options.depth) return; + write_state = ISSUING; + break; + + default: DIE("Not implemented"); + } + } +} + +/** + * Handle incoming data (responses). + */ +void Connection::read_callback() { + struct evbuffer *input = bufferevent_get_input(bev); + + Operation *op = NULL; + bool done, found; + + //initially assume found (for sets that may come through here) + //is this correct? do we want to assume true in case that + //GET was found, but wrong value size (i.e. update value) + // + found = true; + //bool full_read = true; + //fprintf(stderr,"read_cb start with current queue of ops: %lu and issue_buf_n: %d\n",op_queue.size(),issue_buf_n); + + //if (op_queue.size() == 0) V("Spurious read callback."); + bool full_read = true; + while (full_read) { + + if (read_state == CONN_SETUP) { + assert(options.binary); + if (!prot->setup_connection_r(input)) return; + read_state = IDLE; + break; + } + + int opcode; + uint32_t opaque; + full_read = prot->handle_response(input, done, found, opcode, opaque); + if (full_read) { + if (opcode == CMD_NOOP) { + //char out[128]; + //sprintf(out,"conn: %u, reading noop\n",cid); + //write(2,out,strlen(out)); + continue; + } + op = &op_queue[opaque]; + //char out[128]; + //sprintf(out,"conn: %u, reading opaque: %u\n",cid,opaque); + //write(2,out,strlen(out)); + //output_op(op,2,found); + if (op->key.length() < 1) { + //char out2[128]; + //sprintf(out2,"conn: %u, bad op: %s\n",cid,op->key.c_str()); + //write(2,out2,strlen(out2)); + continue; + } + } else { + break; + } + + + switch (op->type) { + case Operation::GET: + if (done) { + if ( !found && (options.getset || options.getsetorset) ) {// && + //(options.twitter_trace != 1)) { + char key[256]; + string keystr = op->key; + strcpy(key, keystr.c_str()); + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + finish_op(op,0); // sets read_state = IDLE + if (last_quiet) { + issue_noop(); + } + //issue_set_miss(key, &random_char[index], valuelen); + issue_set(key, &random_char[index], valuelen, false); + last_quiet = false; + + } else { + if (found) { + finish_op(op,1); + } else { + finish_op(op,0); + } + } + } else { + char out[128]; + sprintf(out,"conn: %u, not done reading, should do something",cid); + write(2,out,strlen(out)); + } + break; + case Operation::SET: + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + DIE("not implemented"); + } + + } + + double now = get_time(); + if (check_exit_condition(now)) { + return; + } + //fprintf(stderr,"read_cb done with current queue of ops: %d and issue_buf_n: %d\n",op_queue_size,issue_buf_n); + //for (auto x : op_queue) { + // cerr << x.first << ": " << x.second.key << endl; + //} + //buffer is ready to go! + //if (issue_buf_n >= options.depth) { + if (issue_buf_n > 0) { + if (last_quiet) { + issue_noop(); + last_quiet = false; + } + //fprintf(stderr,"read_cb writing %d reqs, last quiet %d\n",issue_buf_n,last_quiet); + //char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); + //fprintf(stderr,"-------------------------------------\n"); + //memcpy(output,issue_buf,issue_buf_size); + //write(2,output,issue_buf_size); + //fprintf(stderr,"\n-------------------------------------\n"); + //free(output); + + bufferevent_write(bev, issue_buf, issue_buf_size); + memset(issue_buf,0,issue_buf_size); + issue_buf_pos = issue_buf; + issue_buf_size = 0; + issue_buf_n = 0; + } + + //if (op_queue_size > (uint32_t) options.depth) { + // fprintf(stderr,"read_cb opqueue too big %d\n",op_queue_size); + // return; + //} else { + // fprintf(stderr,"read_cb issing %d\n",op_queue_size); + // issue_getsetorset(now); + //} + last_tx = now; + stats.log_op(op_queue_size); + drive_write_machine(); + + // update events + //if (bev != NULL) { + // // no pending response (nothing to read) and output buffer empty (nothing to write) + // if ((op_queue.size() == 0) && (evbuffer_get_length(bufferevent_get_output(bev)) == 0)) { + // bufferevent_disable(bev, EV_WRITE|EV_READ); + // } + //} +} + +/** + * Callback called when write requests finish. + */ +void Connection::write_callback() { + + //fprintf(stderr,"loaded evbuffer with ops: %u\n",op_queue.size()); +} + +/** + * Callback for timer timeouts. + */ +void Connection::timer_callback() { + drive_write_machine(); +} +// //fprintf(stderr,"timer callback issuing requests!\n"); +// if (op_queue_size >= (size_t) options.depth) { +// return; +// } else { +// double now = get_time(); +// issue_getsetorset(now); +// } +//} + + +/* The follow are C trampolines for libevent callbacks. */ +void bev_event_cb(struct bufferevent *bev, short events, void *ptr) { + + Connection* conn = (Connection*) ptr; + conn->event_callback(events); +} + +void bev_read_cb(struct bufferevent *bev, void *ptr) { + Connection* conn = (Connection*) ptr; + conn->read_callback(); +} + +void bev_write_cb(struct bufferevent *bev, void *ptr) { + Connection* conn = (Connection*) ptr; + conn->write_callback(); +} + +void timer_cb(evutil_socket_t fd, short what, void *ptr) { + Connection* conn = (Connection*) ptr; + conn->timer_callback(); +} + diff --git a/Operation.h b/Operation.h index 3e119b1..6959223 100644 --- a/Operation.h +++ b/Operation.h @@ -11,7 +11,7 @@ class Operation { double start_time, end_time; enum type_enum { - GET, SET, DELETE, SASL, NOOP + GET, SET, DELETE, SASL, NOOP, TOUCH }; type_enum type; @@ -19,6 +19,7 @@ class Operation { string key; int valuelen; uint32_t opaque; + uint8_t level; double time() const { return (end_time - start_time) * 1000000; } }; diff --git a/Protocol.cc b/Protocol.cc index 545ef4f..5507a6c 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -623,9 +623,10 @@ bool ProtocolBinary::handle_response(evbuffer *input, bool &done, bool &found, i if (bl > 0 && opcode == 1) { //fprintf(stderr,"set resp len: %u\n",bl); - void *data = malloc(bl); - data = evbuffer_pullup(input, bl); - free(data); + //void *data = malloc(bl); + //data = evbuffer_pullup(input, bl); + //free(data); + evbuffer_drain(input, targetLen); } else { evbuffer_drain(input, targetLen); } diff --git a/mutilate.cc b/mutilate.cc index 3c86a5c..ed78dba 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -53,6 +53,11 @@ using namespace moodycamel; ifstream kvfile; pthread_mutex_t flock = PTHREAD_MUTEX_INITIALIZER; + +pthread_mutex_t reader_l; +pthread_cond_t reader_ready; +int reader_not_ready = 1; + pthread_mutex_t *item_locks; int item_lock_hashpower = 13; @@ -701,6 +706,9 @@ void go(const vector& servers, options_t& options, *lock = PTHREAD_MUTEX_INITIALIZER; mutexes.push_back(lock); } + pthread_mutex_init(&reader_l, NULL); + pthread_cond_init(&reader_ready, NULL); + //ConcurrentQueue *trace_queue = new ConcurrentQueue(20000000); struct reader_data *rdata = (struct reader_data*)malloc(sizeof(struct reader_data)); rdata->trace_queue = trace_queue; @@ -713,7 +721,10 @@ void go(const vector& servers, options_t& options, if ((error = pthread_create(&rtid, NULL,reader_thread,rdata)) != 0) { printf("reader thread failed to be created with error code %d\n", error); } - usleep(10); + pthread_mutex_lock(&reader_l); + while (reader_not_ready) + pthread_cond_wait(&reader_ready,&reader_l); + pthread_mutex_unlock(&reader_l); } @@ -1002,12 +1013,25 @@ void* reader_thread(void *arg) { //} n++; if (n % 1000000 == 0) fprintf(stderr,"decompressed requests: %lu, waits: %lu\n",n,nwrites); + //if (n > 100000000) { + // pthread_mutex_lock(&reader_l); + // reader_not_ready = 0; + // pthread_mutex_unlock(&reader_l); + // pthread_cond_signal(&reader_ready); + //} } free(line_p); free(ftrace); trace = get_stream(dctx, fin, buffInSize, buffIn, buffOutSize, buffOut); } + pthread_mutex_lock(&reader_l); + if (reader_not_ready) { + reader_not_ready = 0; + } + pthread_mutex_unlock(&reader_l); + pthread_cond_signal(&reader_ready); + string eof = "EOF"; for (int i = 0; i < 1000; i++) { for (int j = 0; j < trace_queue.size(); j++) { From 6d67edb4efc426cd09634a44b47c60ac5044ea3b Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Tue, 7 Sep 2021 14:06:15 -0400 Subject: [PATCH 36/57] compiles with multi connection --- Connection.cc | 5 +- Connection.h | 33 ++-- ConnectionMulti.cc | 453 ++++++++++++++++++++++++++++++++------------- Protocol.cc | 60 +++--- SConstruct | 2 +- mutilate.cc | 4 +- 6 files changed, 374 insertions(+), 183 deletions(-) diff --git a/Connection.cc b/Connection.cc index 33cdd36..64722c7 100644 --- a/Connection.cc +++ b/Connection.cc @@ -658,7 +658,6 @@ int Connection::issue_getsetorset(double now) { int Connection::issue_get_with_len(const char* key, int valuelen, double now, bool quiet) { //Operation *op = new Operation; Operation op; // = new Operation; - int l; #if HAVE_CLOCK_GETTIME op.start_time = get_time_accurate(); @@ -729,7 +728,6 @@ int Connection::issue_get_with_len(const char* key, int valuelen, double now, bo */ void Connection::issue_get(const char* key, double now) { Operation op; - int l; #if HAVE_CLOCK_GETTIME op.start_time = get_time_accurate(); @@ -763,7 +761,7 @@ void Connection::issue_get(const char* key, double now) { op_queue_size++; if (read_state == IDLE) read_state = WAITING_FOR_GET; - l = prot->get_request(key,op.opaque); + int l = prot->get_request(key,op.opaque); if (read_state != LOADING) stats.tx_bytes += l; stats.log_access(op); @@ -893,7 +891,6 @@ int Connection::issue_set(const char* key, const char* value, int length, double now, bool is_access) { //Operation *op = new Operation; Operation op; // = new Operation; - int l; #if HAVE_CLOCK_GETTIME op.start_time = get_time_accurate(); diff --git a/Connection.h b/Connection.h index 3b314a4..ed6b153 100644 --- a/Connection.h +++ b/Connection.h @@ -34,8 +34,13 @@ using namespace moodycamel; void bev_event_cb(struct bufferevent *bev, short events, void *ptr); void bev_read_cb(struct bufferevent *bev, void *ptr); +void bev_event_cb1(struct bufferevent *bev, short events, void *ptr); +void bev_read_cb1(struct bufferevent *bev, void *ptr); +void bev_event_cb2(struct bufferevent *bev, short events, void *ptr); +void bev_read_cb2(struct bufferevent *bev, void *ptr); void bev_write_cb(struct bufferevent *bev, void *ptr); void timer_cb(evutil_socket_t fd, short what, void *ptr); +void timer_cb_m(evutil_socket_t fd, short what, void *ptr); class Protocol; @@ -171,11 +176,11 @@ class Connection { class ConnectionMulti { public: - Connection(struct event_base* _base1, struct event_base* _base2, struct evdns_base* _evdns, + ConnectionMulti(struct event_base* _base1, struct event_base* _base2, struct evdns_base* _evdns, string _hostname1, string _hostname2, string _port, options_t options, bool sampling = true); - ~Connection(); + ~ConnectionMulti(); int do_connect(); @@ -192,9 +197,11 @@ class ConnectionMulti { void reset(); bool check_exit_condition(double now = 0.0); + void event_callback1(short events); + void event_callback2(short events); + void read_callback1(); + void read_callback2(); // event callbacks - void event_callback(short events); - void read_callback(); void write_callback(); void timer_callback(); @@ -204,7 +211,8 @@ class ConnectionMulti { void set_lock(pthread_mutex_t* a_lock); private: - string hostname; + string hostname1; + string hostname2; string port; struct event_base *base1; @@ -243,7 +251,7 @@ class ConnectionMulti { // Parameters to track progress of the data loader. int loader_issued, loader_completed; - uint32_t **opaque; + uint32_t *opaque; int *issue_buf_size; int *issue_buf_n; unsigned char **issue_buf_pos; @@ -253,15 +261,15 @@ class ConnectionMulti { uint32_t total; uint32_t cid; int eof; + + std::vector> op_queue; + uint32_t *op_queue_size; - Protocol *prot; Generator *valuesize; Generator *keysize; KeyGenerator *keygen; Generator *iagen; - std::vector> op_queue; - uint32_t op_queue_size; pthread_mutex_t* lock; //ConcurrentQueue *trace_queue; queue *trace_queue; @@ -276,11 +284,10 @@ class ConnectionMulti { // request functions void issue_sasl(); - void issue_noop(double now = 0.0); - void issue_get(const char* key, double now = 0.0); - int issue_get_with_len(const char* key, int valuelen, double now = 0.0, bool quiet = false); + void issue_noop(double now = 0.0, int level = 1); + int issue_get_with_len(const char* key, int valuelen, double now = 0.0, bool quiet = false, int level = 1); int issue_set(const char* key, const char* value, int length, - double now = 0.0, bool is_access = false); + double now = 0.0, bool real_set = false, int level = 1); // protocol fucntions int set_request_ascii(const char* key, const char* value, int length); diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 0fba149..dc3094d 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -32,15 +32,9 @@ //#define DEBUGMC using namespace moodycamel; -std::hash hashstr; -extern ifstream kvfile; -extern pthread_mutex_t flock; -extern int item_lock_hashpower; - - -pthread_mutex_t cid_lock = PTHREAD_MUTEX_INITIALIZER; -static uint32_t connids = 1; +pthread_mutex_t cid_lock_m = PTHREAD_MUTEX_INITIALIZER; +static uint32_t connids_m = 1; void ConnectionMulti::output_op(Operation *op, int type, bool found) { @@ -129,23 +123,25 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base1, struct event_base* _ last_tx = last_rx = 0.0; - pthread_mutex_lock(&cid_lock); - cid = connids++; - pthread_mutex_unlock(&cid_lock); + pthread_mutex_lock(&cid_lock_m); + cid = connids_m++; + pthread_mutex_unlock(&cid_lock_m); - op_queue_size = malloc(sizeof(int)*LEVELS+1); - opaque = malloc(sizeof(int)*LEVELS+1); + op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); - issue_buf_n = malloc(sizeof(int)*LEVELS+1); - issue_buf = malloc(sizeof(unsigned char*)*LEVELS+1); - issue_buf_pos = malloc(sizeof(unsigned char*)*LEVELS+1); + issue_buf_n = (int*)malloc(sizeof(int)*(LEVELS+1)); + issue_buf = (unsigned char**)malloc(sizeof(unsigned char*)*(LEVELS+1)); + issue_buf_pos = (unsigned char**)malloc(sizeof(unsigned char*)*(LEVELS+1)); for (int i = 1; i <= LEVELS; i++) { op_queue_size[i] = 0; opaque[i] = 0; issue_buf[i] = (unsigned char*)malloc(sizeof(unsigned char)*MAX_BUFFER_SIZE); - issue_buf_pos[i] = issue_buf; + std::unordered_map op_q; + op_queue.push_back(op_q); + issue_buf_pos[i] = issue_buf[i]; issue_buf_size[i] = 0; } @@ -173,11 +169,11 @@ int ConnectionMulti::do_connect() { if (options.unix_socket) { bev1 = bufferevent_socket_new(base1, -1, BEV_OPT_CLOSE_ON_FREE); - bufferevent_setcb(bev1, bev1_read_cb, bev1_write_cb, bev1_event_cb, this); + bufferevent_setcb(bev1, bev_read_cb1, bev_write_cb, bev_event_cb1, this); bufferevent_enable(bev1, EV_READ | EV_WRITE); bev2 = bufferevent_socket_new(base2, -1, BEV_OPT_CLOSE_ON_FREE); - bufferevent_setcb(bev2, bev2_read_cb, bev2_write_cb, bev2_event_cb, this); + bufferevent_setcb(bev2, bev_read_cb2, bev_write_cb, bev_event_cb2, this); bufferevent_enable(bev2, EV_READ | EV_WRITE); struct sockaddr_un sin1; @@ -190,13 +186,6 @@ int ConnectionMulti::do_connect() { int err = bufferevent_socket_connect(bev1, (struct sockaddr*)&sin1, addrlen); if (err == 0) { connected = 1; - if (options.binary) { - prot = new ProtocolBinary(options, this, bev1); - } else if (options.redis) { - prot = new ProtocolRESP(options, this, bev1); - } else { - prot = new ProtocolAscii(options, this, bev1); - } } else { connected = 0; err = errno; @@ -210,16 +199,9 @@ int ConnectionMulti::do_connect() { strcpy(sin2.sun_path, hostname2.c_str()); addrlen = sizeof(sin2); - int err = bufferevent_socket_connect(bev2, (struct sockaddr*)&sin2, addrlen); + err = bufferevent_socket_connect(bev2, (struct sockaddr*)&sin2, addrlen); if (err == 0) { connected = 1; - if (options.binary) { - prot = new ProtocolBinary(options, this, bev2); - } else if (options.redis) { - prot = new ProtocolRESP(options, this, bev2); - } else { - prot = new ProtocolAscii(options, this, bev2); - } } else { connected = 0; err = errno; @@ -370,7 +352,7 @@ int ConnectionMulti::issue_getsetorset(double now) { } int index = lrand48() % (1024 * 1024); issued = issue_set(key, &random_char[index], vl, now, true,1); - last_quiet = false; + last_quiet1 = false; break; } @@ -392,7 +374,7 @@ int ConnectionMulti::issue_getsetorset(double now) { } if (last_quiet1) { issue_noop(now,1); - last_quiet = false; + last_quiet1 = false; } #ifdef DEBUGMC fprintf(stderr,"getsetorset issuing %d reqs last quiet %d\n",issue_buf_n,last_quiet); @@ -404,12 +386,12 @@ int ConnectionMulti::issue_getsetorset(double now) { free(output); #endif //buffer is ready to go! - bufferevent_write(bev1, issue_buf1, issue_buf_size1); + bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); - memset(issue_buf1,0,issue_buf_size1); - issue_buf_pos1 = issue_buf1; - issue_buf_size1 = 0; - issue_buf_n1 = 0; + memset(issue_buf[1],0,issue_buf_size[1]); + issue_buf_pos[1] = issue_buf[1]; + issue_buf_size[1] = 0; + issue_buf_n[1] = 0; return ret; @@ -420,7 +402,6 @@ int ConnectionMulti::issue_getsetorset(double now) { */ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, int level) { Operation op; - int l; #if HAVE_CLOCK_GETTIME op.start_time = get_time_accurate(); @@ -500,7 +481,6 @@ void ConnectionMulti::issue_noop(double now, int level) { int ConnectionMulti::issue_set(const char* key, const char* value, int length, double now, bool real_set, int level) { Operation op; - int l; #if HAVE_CLOCK_GETTIME op.start_time = get_time_accurate(); @@ -703,10 +683,10 @@ bool ConnectionMulti::check_exit_condition(double now) { /** * Handle new connection and error events. */ -void ConnectionMulti::event_callback(short events) { +void ConnectionMulti::event_callback1(short events) { if (events & BEV_EVENT_CONNECTED) { - D("Connected to %s:%s.", hostname.c_str(), port.c_str()); - int fd = bufferevent_getfd(bev); + D("Connected to %s:%s.", hostname1.c_str(), port.c_str()); + int fd = bufferevent_getfd(bev1); if (fd < 0) DIE("bufferevent_getfd"); if (!options.no_nodelay && !options.unix_socket) { @@ -716,14 +696,42 @@ void ConnectionMulti::event_callback(short events) { DIE("setsockopt()"); } - read_state = CONN_SETUP; - if (prot->setup_connection_w()) { - read_state = IDLE; - } drive_write_machine(); } else if (events & BEV_EVENT_ERROR) { - int err = bufferevent_socket_get_dns_error(bev); + int err = bufferevent_socket_get_dns_error(bev1); + //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); + if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); + fprintf(stderr,"Got an error: %s\n", + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); + + DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + + } else if (events & BEV_EVENT_EOF) { + fprintf(stderr,"Unexpected EOF from server."); + return; + } +} + +/** + * Handle new connection and error events. + */ +void ConnectionMulti::event_callback2(short events) { + if (events & BEV_EVENT_CONNECTED) { + D("Connected to %s:%s.", hostname2.c_str(), port.c_str()); + int fd = bufferevent_getfd(bev2); + if (fd < 0) DIE("bufferevent_getfd"); + + if (!options.no_nodelay && !options.unix_socket) { + int one = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (void *) &one, sizeof(one)) < 0) + DIE("setsockopt()"); + } + + + } else if (events & BEV_EVENT_ERROR) { + int err = bufferevent_socket_get_dns_error(bev2); //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); fprintf(stderr,"Got an error: %s\n", @@ -765,11 +773,7 @@ void ConnectionMulti::drive_write_machine(double now) { break; case ISSUING: - if (op_queue_size >= (size_t) options.depth) { - write_state = WAITING_FOR_OPQ; - return; - } - if (op_queue.size() >= (size_t) options.depth) { + if (op_queue_size[1] >= (size_t) options.depth) { write_state = WAITING_FOR_OPQ; return; } else if (now < next_time) { @@ -830,11 +834,60 @@ void ConnectionMulti::drive_write_machine(double now) { } } + + +/** + * Tries to consume a binary response (in its entirety) from an evbuffer. + * + * @param input evBuffer to read response from + * @return true if consumed, false if not enough data in buffer. + */ +static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, bool &found, int &opcode, uint32_t &opaque) { + // Read the first 24 bytes as a header + int length = evbuffer_get_length(input); + if (length < 24) return false; + binary_header_t* h = + reinterpret_cast(evbuffer_pullup(input, 24)); + assert(h); + + int bl = ntohl(h->body_len); + // Not whole response + int targetLen = 24 + bl; + if (length < targetLen) return false; + //fprintf(stderr,"handle resp - opcode: %u opaque: %u len: %u status: %u\n", + // h->opcode,ntohl(h->opaque), + // ntohl(h->body_len),ntohl(h->status)); + + opcode = h->opcode; + opaque = ntohl(h->opaque); + // If something other than success, count it as a miss + if (opcode == CMD_GET && h->status) { + conn->stats.get_misses++; + conn->stats.window_get_misses++; + found = false; + } + + + if (bl > 0 && opcode == 1) { + //fprintf(stderr,"set resp len: %u\n",bl); + //void *data = malloc(bl); + //data = evbuffer_pullup(input, bl); + //free(data); + evbuffer_drain(input, targetLen); + } else { + evbuffer_drain(input, targetLen); + } + + conn->stats.rx_bytes += targetLen; + done = true; + return true; +} + /** * Handle incoming data (responses). */ -void Connection::read_callback() { - struct evbuffer *input = bufferevent_get_input(bev); +void ConnectionMulti::read_callback1() { + struct evbuffer *input = bufferevent_get_input(bev1); Operation *op = NULL; bool done, found; @@ -842,8 +895,8 @@ void Connection::read_callback() { //initially assume found (for sets that may come through here) //is this correct? do we want to assume true in case that //GET was found, but wrong value size (i.e. update value) - // found = true; + //bool full_read = true; //fprintf(stderr,"read_cb start with current queue of ops: %lu and issue_buf_n: %d\n",op_queue.size(),issue_buf_n); @@ -851,32 +904,32 @@ void Connection::read_callback() { bool full_read = true; while (full_read) { - if (read_state == CONN_SETUP) { - assert(options.binary); - if (!prot->setup_connection_r(input)) return; - read_state = IDLE; - break; - } int opcode; uint32_t opaque; - full_read = prot->handle_response(input, done, found, opcode, opaque); + full_read = handle_response(this,input, done, found, opcode, opaque); if (full_read) { if (opcode == CMD_NOOP) { - //char out[128]; - //sprintf(out,"conn: %u, reading noop\n",cid); - //write(2,out,strlen(out)); +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif continue; } - op = &op_queue[opaque]; - //char out[128]; - //sprintf(out,"conn: %u, reading opaque: %u\n",cid,opaque); - //write(2,out,strlen(out)); - //output_op(op,2,found); + op = &op_queue[1][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif if (op->key.length() < 1) { - //char out2[128]; - //sprintf(out2,"conn: %u, bad op: %s\n",cid,op->key.c_str()); - //write(2,out2,strlen(out2)); +#ifdef DEBUGMC + char out2[128]; + sprintf(out2,"conn l1: %u, bad op: %s\n",cid,op->key.c_str()); + write(2,out2,strlen(out2)); +#endif continue; } } else { @@ -895,12 +948,11 @@ void Connection::read_callback() { int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); finish_op(op,0); // sets read_state = IDLE - if (last_quiet) { - issue_noop(); + if (last_quiet1) { + issue_noop(1); } - //issue_set_miss(key, &random_char[index], valuelen); - issue_set(key, &random_char[index], valuelen, false); - last_quiet = false; + issue_set(key, &random_char[index], valuelen, false, 1); + last_quiet1 = false; } else { if (found) { @@ -911,7 +963,7 @@ void Connection::read_callback() { } } else { char out[128]; - sprintf(out,"conn: %u, not done reading, should do something",cid); + sprintf(out,"conn l1: %u, not done reading, should do something",cid); write(2,out,strlen(out)); } break; @@ -929,41 +981,177 @@ void Connection::read_callback() { if (check_exit_condition(now)) { return; } - //fprintf(stderr,"read_cb done with current queue of ops: %d and issue_buf_n: %d\n",op_queue_size,issue_buf_n); - //for (auto x : op_queue) { - // cerr << x.first << ": " << x.second.key << endl; - //} +#ifdef DEBUGMC + fprintf(stderr,"read_cb1 done with current queue of ops: %d and issue_buf_n: %d\n",op_queue_size,issue_buf_n); + for (auto x : op_queue) { + cerr << x.first << ": " << x.second.key << endl; + } +#endif //buffer is ready to go! - //if (issue_buf_n >= options.depth) { - if (issue_buf_n > 0) { - if (last_quiet) { - issue_noop(); - last_quiet = false; + if (issue_buf_n[1] > 0) { + if (last_quiet1) { + issue_noop(now,1); + last_quiet1 = false; } - //fprintf(stderr,"read_cb writing %d reqs, last quiet %d\n",issue_buf_n,last_quiet); - //char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); - //fprintf(stderr,"-------------------------------------\n"); - //memcpy(output,issue_buf,issue_buf_size); - //write(2,output,issue_buf_size); - //fprintf(stderr,"\n-------------------------------------\n"); - //free(output); - - bufferevent_write(bev, issue_buf, issue_buf_size); - memset(issue_buf,0,issue_buf_size); - issue_buf_pos = issue_buf; - issue_buf_size = 0; - issue_buf_n = 0; +#ifdef DEBUGMC + fprintf(stderr,"read_cb1 writing %d reqs, last quiet %d\n",issue_buf_n,last_quiet); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); + fprintf(stderr,"-------------------------------------\n"); + memcpy(output,issue_buf,issue_buf_size); + write(2,output,issue_buf_size); + fprintf(stderr,"\n-------------------------------------\n"); + free(output); +#endif + + bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); + memset(issue_buf[1],0,issue_buf_size[1]); + issue_buf_pos[1] = issue_buf[1]; + issue_buf_size[1] = 0; + issue_buf_n[1] = 0; } - //if (op_queue_size > (uint32_t) options.depth) { - // fprintf(stderr,"read_cb opqueue too big %d\n",op_queue_size); - // return; - //} else { - // fprintf(stderr,"read_cb issing %d\n",op_queue_size); - // issue_getsetorset(now); + last_tx = now; + stats.log_op(op_queue_size[1]); + drive_write_machine(); + + // update events + //if (bev != NULL) { + // // no pending response (nothing to read) and output buffer empty (nothing to write) + // if ((op_queue.size() == 0) && (evbuffer_get_length(bufferevent_get_output(bev)) == 0)) { + // bufferevent_disable(bev, EV_WRITE|EV_READ); + // } //} +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMulti::read_callback2() { + struct evbuffer *input = bufferevent_get_input(bev2); + + Operation *op = NULL; + bool done, found; + + //initially assume found (for sets that may come through here) + //is this correct? do we want to assume true in case that + //GET was found, but wrong value size (i.e. update value) + found = true; + + //bool full_read = true; + //fprintf(stderr,"read_cb start with current queue of ops: %lu and issue_buf_n: %d\n",op_queue.size(),issue_buf_n); + + //if (op_queue.size() == 0) V("Spurious read callback."); + bool full_read = true; + while (full_read) { + + + int opcode; + uint32_t opaque; + full_read = handle_response(this,input, done, found, opcode, opaque); + if (full_read) { + if (opcode == CMD_NOOP) { +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif + continue; + } + op = &op_queue[2][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + if (op->key.length() < 1) { +#ifdef DEBUGMC + char out2[128]; + sprintf(out2,"conn l2: %u, bad op: %s\n",cid,op->key.c_str()); + write(2,out2,strlen(out2)); +#endif + continue; + } + } else { + break; + } + + + switch (op->type) { + case Operation::GET: + if (done) { + if ( !found && (options.getset || options.getsetorset) ) {// && + //(options.twitter_trace != 1)) { + char key[256]; + string keystr = op->key; + strcpy(key, keystr.c_str()); + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + finish_op(op,0); // sets read_state = IDLE + if (last_quiet2) { + issue_noop(0,2); + } + issue_set(key, &random_char[index], valuelen, false, 2); + last_quiet2 = false; + + } else { + if (found) { + finish_op(op,1); + } else { + finish_op(op,0); + } + } + } else { + char out[128]; + sprintf(out,"conn l2: %u, not done reading, should do something",cid); + write(2,out,strlen(out)); + } + break; + case Operation::SET: + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + DIE("not implemented"); + } + + } + + double now = get_time(); + if (check_exit_condition(now)) { + return; + } +#ifdef DEBUGMC + fprintf(stderr,"read_cb2 done with current queue of ops: %d and issue_buf_n: %d\n",op_queue_size,issue_buf_n); + for (auto x : op_queue) { + cerr << x.first << ": " << x.second.key << endl; + } +#endif + //buffer is ready to go! + if (issue_buf_n[2] > 0) { + if (last_quiet2) { + issue_noop(now,2); + last_quiet2 = false; + } +#ifdef DEBUGMC + fprintf(stderr,"read_cb2 writing %d reqs, last quiet %d\n",issue_buf_n,last_quiet); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); + fprintf(stderr,"-------------------------------------\n"); + memcpy(output,issue_buf,issue_buf_size); + write(2,output,issue_buf_size); + fprintf(stderr,"\n-------------------------------------\n"); + free(output); +#endif + + bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); + memset(issue_buf[2],0,issue_buf_size[2]); + issue_buf_pos[2] = issue_buf[2]; + issue_buf_size[2] = 0; + issue_buf_n[2] = 0; + } + last_tx = now; - stats.log_op(op_queue_size); + stats.log_op(op_queue_size[2]); drive_write_machine(); // update events @@ -978,7 +1166,7 @@ void Connection::read_callback() { /** * Callback called when write requests finish. */ -void Connection::write_callback() { +void ConnectionMulti::write_callback() { //fprintf(stderr,"loaded evbuffer with ops: %u\n",op_queue.size()); } @@ -986,38 +1174,37 @@ void Connection::write_callback() { /** * Callback for timer timeouts. */ -void Connection::timer_callback() { +void ConnectionMulti::timer_callback() { drive_write_machine(); } -// //fprintf(stderr,"timer callback issuing requests!\n"); -// if (op_queue_size >= (size_t) options.depth) { -// return; -// } else { -// double now = get_time(); -// issue_getsetorset(now); -// } -//} /* The follow are C trampolines for libevent callbacks. */ -void bev_event_cb(struct bufferevent *bev, short events, void *ptr) { +void bev_event_cb1(struct bufferevent *bev, short events, void *ptr) { + + ConnectionMulti* conn = (ConnectionMulti*) ptr; + conn->event_callback1(events); +} + +/* The follow are C trampolines for libevent callbacks. */ +void bev_event_cb2(struct bufferevent *bev, short events, void *ptr) { - Connection* conn = (Connection*) ptr; - conn->event_callback(events); + ConnectionMulti* conn = (ConnectionMulti*) ptr; + conn->event_callback2(events); } -void bev_read_cb(struct bufferevent *bev, void *ptr) { - Connection* conn = (Connection*) ptr; - conn->read_callback(); +void bev_read_cb1(struct bufferevent *bev, void *ptr) { + ConnectionMulti* conn = (ConnectionMulti*) ptr; + conn->read_callback1(); } -void bev_write_cb(struct bufferevent *bev, void *ptr) { - Connection* conn = (Connection*) ptr; - conn->write_callback(); +void bev_read_cb2(struct bufferevent *bev, void *ptr) { + ConnectionMulti* conn = (ConnectionMulti*) ptr; + conn->read_callback2(); } -void timer_cb(evutil_socket_t fd, short what, void *ptr) { - Connection* conn = (Connection*) ptr; +void timer_cb_m(evutil_socket_t fd, short what, void *ptr) { + ConnectionMulti* conn = (ConnectionMulti*) ptr; conn->timer_callback(); } diff --git a/Protocol.cc b/Protocol.cc index 5507a6c..89d52de 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -110,24 +110,24 @@ int ProtocolRESP::get_request(const char* key, uint32_t opaque) { int ProtocolRESP::hset_request(const char* key, const char* value, int len) { - int l; + int l = 0; //hash is first n-assoc bytes //field is last assoc bytes //value is value - int assoc = opts.assoc; - char* hash = (char*)malloc(sizeof(char)*((strlen(key)-assoc)+1)); - char* field = (char*)malloc(sizeof(char)*(assoc+1)); - strncpy(hash, key, strlen(key)-assoc); - strncpy(field,key+strlen(key)-assoc,assoc); - hash[strlen(key)-assoc] = '\0'; - field[assoc] = '\0'; - l = evbuffer_add_printf(bufferevent_get_output(bev), - "*4\r\n$4\r\nHSET\r\n$%lu\r\n%s\r\n$%lu\r\n%s\r\n$%d\r\n%s\r\n", - strlen(hash),hash,strlen(field),field,len,value); - l += len + 2; - if (read_state == IDLE) read_state = WAITING_FOR_END; - free(hash); - free(field); + //int assoc = opts.assoc; + //char* hash = (char*)malloc(sizeof(char)*((strlen(key)-assoc)+1)); + //char* field = (char*)malloc(sizeof(char)*(assoc+1)); + //strncpy(hash, key, strlen(key)-assoc); + //strncpy(field,key+strlen(key)-assoc,assoc); + //hash[strlen(key)-assoc] = '\0'; + //field[assoc] = '\0'; + //l = evbuffer_add_printf(bufferevent_get_output(bev), + // "*4\r\n$4\r\nHSET\r\n$%lu\r\n%s\r\n$%lu\r\n%s\r\n$%d\r\n%s\r\n", + // strlen(hash),hash,strlen(field),field,len,value); + //l += len + 2; + //if (read_state == IDLE) read_state = WAITING_FOR_END; + //free(hash); + //free(field); return l; } @@ -140,23 +140,23 @@ int ProtocolRESP::hset_request(const char* key, const char* value, int len) { * the vast vast majority are going to be 20 bytes. */ int ProtocolRESP::hget_request(const char* key) { - int l; + int l = 0; //hash is first n-assoc bytes //field is last assoc bytes - int assoc = opts.assoc; - char* hash = (char*)malloc(sizeof(char)*((strlen(key)-assoc)+1)); - char* field = (char*)malloc(sizeof(char)*(assoc+1)); - strncpy(hash, key, strlen(key)-assoc); - strncpy(field,key+strlen(key)-assoc,assoc); - hash[strlen(key)-assoc] = '\0'; - field[assoc] = '\0'; - l = evbuffer_add_printf(bufferevent_get_output(bev), - "*3\r\n$4\r\nHGET\r\n$%lu\r\n%s\r\n$%lu\r\n%s\r\n", - strlen(hash),hash,strlen(field),field); - - if (read_state == IDLE) read_state = WAITING_FOR_GET; - free(hash); - free(field); + //int assoc = opts.assoc; + //char* hash = (char*)malloc(sizeof(char)*((strlen(key)-assoc)+1)); + //char* field = (char*)malloc(sizeof(char)*(assoc+1)); + //strncpy(hash, key, strlen(key)-assoc); + //strncpy(field,key+strlen(key)-assoc,assoc); + //hash[strlen(key)-assoc] = '\0'; + //field[assoc] = '\0'; + //l = evbuffer_add_printf(bufferevent_get_output(bev), + // "*3\r\n$4\r\nHGET\r\n$%lu\r\n%s\r\n$%lu\r\n%s\r\n", + // strlen(hash),hash,strlen(field),field); + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + //free(hash); + //free(field); return l; } diff --git a/SConstruct b/SConstruct index 6964c46..cce1aab 100644 --- a/SConstruct +++ b/SConstruct @@ -50,7 +50,7 @@ env.Append(CPPFLAGS = ' -O2 -Wall -g') env.Command(['cmdline.cc', 'cmdline.h'], 'cmdline.ggo', 'gengetopt < $SOURCE') src = Split("""mutilate.cc cmdline.cc log.cc distributions.cc util.cc - Connection.cc Protocol.cc Generator.cc""") + Connection.cc ConnectionMulti.cc Protocol.cc Generator.cc""") if not env['HAVE_POSIX_BARRIER']: # USE_POSIX_BARRIER: src += ['barrier.cc'] diff --git a/mutilate.cc b/mutilate.cc index ed78dba..3464892 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -994,7 +994,7 @@ void* reader_thread(void *arg) { continue; } } - if (appid < trace_queue.size()) { + if (appid < (int)trace_queue.size()) { pthread_mutex_lock(mutexes[appid]); trace_queue[appid]->push(full_line); pthread_mutex_unlock(mutexes[appid]); @@ -1034,7 +1034,7 @@ void* reader_thread(void *arg) { string eof = "EOF"; for (int i = 0; i < 1000; i++) { - for (int j = 0; j < trace_queue.size(); j++) { + for (int j = 0; j < (int)trace_queue.size(); j++) { //trace_queue[j]->enqueue(eof); trace_queue[j]->push(eof); } From e25be07f4982549d70ae73ee2b5635000a67e685 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 8 Sep 2021 08:43:57 -0400 Subject: [PATCH 37/57] progress our goal --- Connection.h | 2 + ConnectionMulti.cc | 286 ++++++++++++++++++++---- Operation.h | 4 +- binary_protocol.h | 2 + mutilate.cc | 542 +++++++++++++++++++++++++++++++-------------- 5 files changed, 626 insertions(+), 210 deletions(-) diff --git a/Connection.h b/Connection.h index ed6b153..a28156b 100644 --- a/Connection.h +++ b/Connection.h @@ -285,6 +285,8 @@ class ConnectionMulti { // request functions void issue_sasl(); void issue_noop(double now = 0.0, int level = 1); + int issue_touch(const char* key, int valuelen, double now = 0.0, int level = 1); + int issue_delete(const char* key, double now = 0.0, int level = 1); int issue_get_with_len(const char* key, int valuelen, double now = 0.0, bool quiet = false, int level = 1); int issue_set(const char* key, const char* value, int length, double now = 0.0, bool real_set = false, int level = 1); diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index dc3094d..5b3dfb0 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -27,15 +27,43 @@ #include #include "blockingconcurrentqueue.h" -#define LEVELS 2 +#define ITEM_DIRTY 16384 +#define ITEM_INCL 4096 +#define ITEM_EXCL 8192 +#define LEVELS 2 +#define SET_INCL(incl,flags) \ + switch (incl) { \ + case 1: \ + flags |= ITEM_INCL; \ + case 2: \ + flags |= ITEM_EXCL; \ + } + +#define GET_INCL(incl,flags) \ + if ((flags & ITEM_INCL) == 1) incl = 1; \ + else if ((flags & ITEM_EXCL) == 1) incl = 2; \ //#define DEBUGMC using namespace moodycamel; pthread_mutex_t cid_lock_m = PTHREAD_MUTEX_INITIALIZER; static uint32_t connids_m = 1; + +typedef struct _evicted_type { + bool evicted; + uint32_t evictedFlags; + int evictedKeyLen; + int evictedLen; + char *evictedKey; + char *evictedData; +} evicted_t; + +static int get_incl(int vl) { +} +static int get_class(int vl, uint32_t kl) { +} void ConnectionMulti::output_op(Operation *op, int type, bool found) { char output[1024]; @@ -131,6 +159,7 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base1, struct event_base* _ opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); issue_buf_n = (int*)malloc(sizeof(int)*(LEVELS+1)); + issue_buf_size = (int*)malloc(sizeof(int)*(LEVELS+1)); issue_buf = (unsigned char**)malloc(sizeof(unsigned char*)*(LEVELS+1)); issue_buf_pos = (unsigned char**)malloc(sizeof(unsigned char*)*(LEVELS+1)); @@ -351,7 +380,13 @@ int ConnectionMulti::issue_getsetorset(double now) { issue_noop(now,1); } int index = lrand48() % (1024 * 1024); - issued = issue_set(key, &random_char[index], vl, now, true,1); + int classid = 0; + int incl = get_incl(vl); + int flags = 0; + SET_INCL(incl,flags); + flags |= ITEM_DIRTY; + + issued = issue_set(key, &random_char[index], vl, now, flags,1); last_quiet1 = false; break; @@ -459,6 +494,123 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no return 1; } +/** + * Issue a get request to the server. + */ +int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int level) { + Operation op; + +#if HAVE_CLOCK_GETTIME + op.start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base1, &now_tv); + op.start_time = tv_to_double(&now_tv); +#else + op.start_time = get_time(); +#endif + } else { + op.start_time = now; + } +#endif + + op.key = string(key); + op.valuelen = valuelen; + op.type = Operation::TOUCH; + op.opaque = opaque[level]++; + op.level = level; + op_queue[level][op.opaque] = op; + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 0; + } + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_TOUCH, htons(keylen), + 0x00, 0x00, {htons(0)}, + htonl(keylen) }; + h.opaque = htonl(op.opaque); + + memcpy(issue_buf_pos[level],&h,24); + issue_buf_pos[level] += 24; + issue_buf_size[level] += 24; + memcpy(issue_buf_pos[level],key,keylen); + issue_buf_pos[level] += keylen; + issue_buf_size[level] += keylen; + issue_buf_n[level]++; + + if (read_state != LOADING) { + stats.tx_bytes += 24 + keylen; + } + + stats.log_access(op); + return 1; +} + +/** + * Issue a delete request to the server. + */ +int ConnectionMulti::issue_delete(const char* key, double now, int level) { + Operation op; + +#if HAVE_CLOCK_GETTIME + op.start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base1, &now_tv); + op.start_time = tv_to_double(&now_tv); +#else + op.start_time = get_time(); +#endif + } else { + op.start_time = now; + } +#endif + + op.key = string(key); + op.type = Operation::DELETE; + op.opaque = opaque[level]++; + op.level = level; + op_queue[level][op.opaque] = op; + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 0; + } + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_DELETE, htons(keylen), + 0x00, 0x00, {htons(0)}, + htonl(keylen) }; + h.opaque = htonl(op.opaque); + + memcpy(issue_buf_pos[level],&h,24); + issue_buf_pos[level] += 24; + issue_buf_size[level] += 24; + memcpy(issue_buf_pos[level],key,keylen); + issue_buf_pos[level] += keylen; + issue_buf_size[level] += keylen; + issue_buf_n[level]++; + + if (read_state != LOADING) { + stats.tx_bytes += 24 + keylen; + } + + stats.log_access(op); + return 1; +} + void ConnectionMulti::issue_noop(double now, int level) { Operation op; @@ -479,7 +631,7 @@ void ConnectionMulti::issue_noop(double now, int level) { * Issue a set request to the server. */ int ConnectionMulti::issue_set(const char* key, const char* value, int length, - double now, bool real_set, int level) { + double now, int flags, int level) { Operation op; #if HAVE_CLOCK_GETTIME @@ -495,6 +647,8 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, op.type = Operation::SET; op.opaque = opaque[level]++; op.level = level; + GET_INCL(op.incl,flags); + op.clsid = get_class(length,strlen(key)); op_queue[level][op.opaque] = op; op_queue_size[level]++; @@ -514,31 +668,20 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, issue_buf_pos[level] += 24; issue_buf_size[level] += 24; - //miss through protocol assumes that every non miss driven set - //makes the data dirty -- assumes data is hit - if (options.miss_through && real_set && level == 1) { - uint32_t flags = htonl(16384); - memcpy(issue_buf_pos[level],&flags,4); - issue_buf_pos[level] += 4; - issue_buf_size[level] += 4; - uint32_t exp = 0; - memcpy(issue_buf_pos[level],&exp,4); - issue_buf_pos[level] += 4; - issue_buf_size[level] += 4; + uint32_t f = htonl(flags); + memcpy(issue_buf_pos[level],&f,4); + issue_buf_pos[level] += 4; + issue_buf_size[level] += 4; + + uint32_t exp = 0; + memcpy(issue_buf_pos[level],&exp,4); + issue_buf_pos[level] += 4; + issue_buf_size[level] += 4; - } else { - uint32_t flags = 0; - memcpy(issue_buf_pos[level],&flags,4); - issue_buf_pos[level] += 4; - issue_buf_size[level] += 4; - uint32_t exp = 0; - memcpy(issue_buf_pos[level],&exp,4); - issue_buf_pos[level] += 4; - issue_buf_size[level] += 4; - } memcpy(issue_buf_pos[level],key,keylen); issue_buf_pos[level] += keylen; issue_buf_size[level] += keylen; + memcpy(issue_buf_pos[level],value,length); issue_buf_pos[level] += length; issue_buf_size[level] += length; @@ -907,7 +1050,9 @@ void ConnectionMulti::read_callback1() { int opcode; uint32_t opaque; - full_read = handle_response(this,input, done, found, opcode, opaque); + evicted_t evict; + + full_read = handle_response(this,input, done, found, opcode, opaque, evict); if (full_read) { if (opcode == CMD_NOOP) { #ifdef DEBUGMC @@ -940,22 +1085,19 @@ void ConnectionMulti::read_callback1() { switch (op->type) { case Operation::GET: if (done) { - if ( !found && (options.getset || options.getsetorset) ) {// && - //(options.twitter_trace != 1)) { - char key[256]; - string keystr = op->key; - strcpy(key, keystr.c_str()); - int valuelen = op->valuelen; - int index = lrand48() % (1024 * 1024); - finish_op(op,0); // sets read_state = IDLE - if (last_quiet1) { - issue_noop(1); - } - issue_set(key, &random_char[index], valuelen, false, 1); - last_quiet1 = false; - + if ( !found && (options.getset || options.getsetorset) ) { + /* issue a get a l2 */ + string key = op->key; + int vl = op->valuelen; + issue_get_with_len(key.c_str(),vl,0,false,2); + //probably want to finish this op somehow + //think about the stats + } else { if (found) { + if (op->incl == 1) { + issue_touch(op->key.c_str(),0,2); + } finish_op(op,1); } else { finish_op(op,0); @@ -968,6 +1110,16 @@ void ConnectionMulti::read_callback1() { } break; case Operation::SET: + if (op->incl == 1) { + issue_touch(op->key.c_str(),0,2); + } + if (evict->evicted) { + if ((evict->evictedFlags & ITEM_INCL) == 1 && (evicted->evictedFlags & ITEM_DIRTY)) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, 0, ITEM_INCL | ITEM_DIRTY, 2); + } else if ((evict->evictedFlags & ITEM_EXCL) == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, 0, ITEM_EXCL, 2); + } + } finish_op(op,1); break; default: @@ -1087,16 +1239,41 @@ void ConnectionMulti::read_callback2() { strcpy(key, keystr.c_str()); int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); + int incl = op->incl; + int flags = 0; + SET_INCL(incl,flags); finish_op(op,0); // sets read_state = IDLE - if (last_quiet2) { - issue_noop(0,2); + if (last_quiet1) { + issue_noop(0,1); + } + issue_set(key, &random_char[index], valuelen, 0, flags, 1); + last_quiet1 = false; + if (incl == 1) { + if (last_quiet2) { + issue_noop(0,2); + } + issue_set(key, &random_char[index], valuelen, 0, flags, 2); + last_quiet2 = false; } - issue_set(key, &random_char[index], valuelen, false, 2); - last_quiet2 = false; } else { if (found) { + char key[256]; + string keystr = op->key; + strcpy(key, keystr.c_str()); + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int incl = op->incl; + int flags = 0; + SET_INCL(incl,flags); + //found in l2, set in l1 + issue_set(key, &random_char[index],valuelen, 0, flags, 1); + if (incl == 2) { + //if exclusive, remove from l2 + issue_delete(key,0,1); + } finish_op(op,1); + } else { finish_op(op,0); } @@ -1110,6 +1287,19 @@ void ConnectionMulti::read_callback2() { case Operation::SET: finish_op(op,1); break; + case Operation::TOUCH: + if (!found) { + char key[256]; + string keystr = op->key; + strcpy(key, keystr.c_str()); + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int incl = op->incl; + int flags = 0; + SET_INCL(incl,flags); + // not found in l2, set in l2 + issue_set(key, &random_char[index],valuelen, 0, flags, 2); + } default: fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); DIE("not implemented"); @@ -1134,11 +1324,11 @@ void ConnectionMulti::read_callback2() { last_quiet2 = false; } #ifdef DEBUGMC - fprintf(stderr,"read_cb2 writing %d reqs, last quiet %d\n",issue_buf_n,last_quiet); - char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); + fprintf(stderr,"read_cb2 writing %d reqs, last quiet %d\n",issue_buf_n[2],last_quiet2); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size[2]+512)); fprintf(stderr,"-------------------------------------\n"); - memcpy(output,issue_buf,issue_buf_size); - write(2,output,issue_buf_size); + memcpy(output,issue_buf[2],issue_buf_size[2]); + write(2,output,issue_buf_size[2]); fprintf(stderr,"\n-------------------------------------\n"); free(output); #endif @@ -1152,7 +1342,7 @@ void ConnectionMulti::read_callback2() { last_tx = now; stats.log_op(op_queue_size[2]); - drive_write_machine(); + //drive_write_machine(); // update events //if (bev != NULL) { diff --git a/Operation.h b/Operation.h index 6959223..d6bf9d6 100644 --- a/Operation.h +++ b/Operation.h @@ -16,10 +16,12 @@ class Operation { type_enum type; - string key; int valuelen; uint32_t opaque; uint8_t level; + uint8_t incl; + uint16_t clsid; + string key; double time() const { return (end_time - start_time) * 1000000; } }; diff --git a/binary_protocol.h b/binary_protocol.h index 26f95cd..bd89d7d 100644 --- a/binary_protocol.h +++ b/binary_protocol.h @@ -3,6 +3,8 @@ #define CMD_GET 0x00 #define CMD_GETQ 0x09 +#define CMD_TOUCH 0x1c +#define CMD_DELETE 0x04 #define CMD_SET 0x01 #define CMD_NOOP 0x0a #define CMD_SETQ 0x11 diff --git a/mutilate.cc b/mutilate.cc index 3464892..0513c6b 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -517,6 +517,7 @@ int main(int argc, char **argv) { servers.push_back(name_to_ipaddr(string(args.server_arg[s]))); } } + ConnectionStats stats; @@ -1087,8 +1088,6 @@ void* thread_main(void *arg) { void do_mutilate(const vector& servers, options_t& options, ConnectionStats& stats, vector*> trace_queue, vector mutexes, bool master -//void do_mutilate(const vector& servers, options_t& options, -// ConnectionStats& stats, vector*> trace_queue, bool master #ifdef HAVE_LIBZMQ , zmq::socket_t* socket #endif @@ -1106,7 +1105,7 @@ void do_mutilate(const vector& servers, options_t& options, #ifdef HAVE_DECL_EVENT_BASE_FLAG_PRECISE_TIMER if (event_config_set_flag(config, EVENT_BASE_FLAG_PRECISE_TIMER)) - DIE("event_config_set_flag(EVENT_BASE_FLAG_PRECISE_TIMER) fail"); + DIE("event_config_set_flag(EVENT_BASE_FLAG_PRECISE_TIMER) fail"); #endif if ((base = event_base_new_with_config(config)) == NULL) @@ -1122,120 +1121,206 @@ void do_mutilate(const vector& servers, options_t& options, double start = get_time(); double now = start; - vector connections; - vector server_lead; - for (auto s: servers) { - // Split args.server_arg[s] into host:port using strtok(). - char *s_copy = new char[s.length() + 1]; - strcpy(s_copy, s.c_str()); + if (servers.size() == 1) { + vector connections; + vector server_lead; + for (auto s: servers) { + // Split args.server_arg[s] into host:port using strtok(). + char *s_copy = new char[s.length() + 1]; + strcpy(s_copy, s.c_str()); + + char *h_ptr = strtok_r(s_copy, ":", &saveptr); + char *p_ptr = strtok_r(NULL, ":", &saveptr); + + if (h_ptr == NULL) DIE("strtok(.., \":\") failed to parse %s", s.c_str()); + + string hostname = h_ptr; + string port = "11211"; + if (p_ptr) port = p_ptr; + + delete[] s_copy; + + int conns = args.measure_connections_given ? args.measure_connections_arg : + options.connections; + + srand(time(NULL)); + for (int c = 0; c < conns; c++) { + Connection* conn = new Connection(base, evdns, hostname, port, options, + //NULL,//trace_queue, + args.agentmode_given ? false : + true); + int tries = 120; + int connected = 0; + int s = 2; + for (int i = 0; i < tries; i++) { + pthread_mutex_lock(&flock); + int ret = conn->do_connect(); + pthread_mutex_unlock(&flock); + if (ret) { + connected = 1; + fprintf(stderr,"thread %lu, conn: %d, connected!\n",pthread_self(),c+1); + break; + } + int d = s + rand() % 100; + //s = s + d; + + //fprintf(stderr,"conn: %d, sleeping %d\n",c,d); + sleep(d); + } + if (connected) { + conn->set_queue(trace_queue[conn->get_cid()]); + conn->set_lock(mutexes[conn->get_cid()]); + connections.push_back(conn); + } else { + fprintf(stderr,"conn: %d, not connected!!\n",c); - char *h_ptr = strtok_r(s_copy, ":", &saveptr); - char *p_ptr = strtok_r(NULL, ":", &saveptr); + } + if (c == 0) server_lead.push_back(conn); + } + } - if (h_ptr == NULL) DIE("strtok(.., \":\") failed to parse %s", s.c_str()); + // Wait for all Connections to become IDLE. + while (1) { + // FIXME: If all connections become ready before event_base_loop + // is called, this will deadlock. + event_base_loop(base, EVLOOP_ONCE); - string hostname = h_ptr; - string port = "11211"; - if (p_ptr) port = p_ptr; + bool restart = false; + for (Connection *conn: connections) + if (!conn->is_ready()) restart = true; - delete[] s_copy; + if (restart) continue; + else break; + } - int conns = args.measure_connections_given ? args.measure_connections_arg : - options.connections; + // Load database on lead connection for each server. + if (!options.noload) { + V("Loading database."); - srand(time(NULL)); - for (int c = 0; c < conns; c++) { - Connection* conn = new Connection(base, evdns, hostname, port, options, - //NULL,//trace_queue, - args.agentmode_given ? false : - true); - int tries = 120; - int connected = 0; - int s = 2; - for (int i = 0; i < tries; i++) { - pthread_mutex_lock(&flock); - int ret = conn->do_connect(); - pthread_mutex_unlock(&flock); - if (ret) { - connected = 1; - fprintf(stderr,"thread %lu, conn: %d, connected!\n",pthread_self(),c+1); - break; - } - int d = s + rand() % 100; - //s = s + d; - - //fprintf(stderr,"conn: %d, sleeping %d\n",c,d); - sleep(d); - } - if (connected) { - conn->set_queue(trace_queue[conn->get_cid()]); - conn->set_lock(mutexes[conn->get_cid()]); - connections.push_back(conn); - } else { - fprintf(stderr,"conn: %d, not connected!!\n",c); + for (auto c: server_lead) c->start_loading(); + + // Wait for all Connections to become IDLE. + while (1) { + // FIXME: If all connections become ready before event_base_loop + // is called, this will deadlock. + event_base_loop(base, EVLOOP_ONCE); + + bool restart = false; + for (Connection *conn: connections) + if (!conn->is_ready()) restart = true; + if (restart) continue; + else break; } - if (c == 0) server_lead.push_back(conn); } - } - // Wait for all Connections to become IDLE. - while (1) { - // FIXME: If all connections become ready before event_base_loop - // is called, this will deadlock. - event_base_loop(base, EVLOOP_ONCE); + if (options.loadonly) { + evdns_base_free(evdns, 0); + event_base_free(base); + return; + } - bool restart = false; - for (Connection *conn: connections) - if (!conn->is_ready()) restart = true; + // FIXME: Remove. Not needed, testing only. + // // FIXME: Synchronize start_time here across threads/nodes. + // pthread_barrier_wait(&barrier); - if (restart) continue; - else break; - } + // Warmup connection. + if (options.warmup > 0) { + if (master) V("Warmup start."); + +#ifdef HAVE_LIBZMQ + if (args.agent_given || args.agentmode_given) { + if (master) V("Synchronizing."); - // Load database on lead connection for each server. - if (!options.noload) { - V("Loading database."); + // 1. thread barrier: make sure our threads ready before syncing agents + // 2. sync agents: all threads across all agents are now ready + // 3. thread barrier: don't release our threads until all agents ready + pthread_barrier_wait(&barrier); + if (master) sync_agent(socket); + pthread_barrier_wait(&barrier); - for (auto c: server_lead) c->start_loading(); + if (master) V("Synchronized."); + } +#endif - // Wait for all Connections to become IDLE. - while (1) { - // FIXME: If all connections become ready before event_base_loop - // is called, this will deadlock. - event_base_loop(base, EVLOOP_ONCE); + int old_time = options.time; + // options.time = 1; + + start = get_time(); + for (Connection *conn: connections) { + conn->start_time = start; + conn->options.time = options.warmup; + conn->start(); // Kick the Connection into motion. + } + + while (1) { + event_base_loop(base, loop_flag); + + //#ifdef USE_CLOCK_GETTIME + // now = get_time(); + //#else + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); + //#endif + + bool restart = false; + for (Connection *conn: connections) + if (!conn->check_exit_condition(now)) + restart = true; + + if (restart) continue; + else break; + } bool restart = false; for (Connection *conn: connections) if (!conn->is_ready()) restart = true; - if (restart) continue; - else break; + if (restart) { + + // Wait for all Connections to become IDLE. + while (1) { + // FIXME: If there were to use EVLOOP_ONCE and all connections + // become ready before event_base_loop is called, this will + // deadlock. We should check for IDLE before calling + // event_base_loop. + event_base_loop(base, EVLOOP_ONCE); // EVLOOP_NONBLOCK); + + bool restart = false; + for (Connection *conn: connections) + if (!conn->is_ready()) restart = true; + + if (restart) continue; + else break; + } + } + + for (Connection *conn: connections) { + conn->reset(); + conn->options.time = old_time; + } + + if (master) V("Warmup stop."); } - } - if (options.loadonly) { - evdns_base_free(evdns, 0); - event_base_free(base); - return; - } - // FIXME: Remove. Not needed, testing only. - // // FIXME: Synchronize start_time here across threads/nodes. - // pthread_barrier_wait(&barrier); + // FIXME: Synchronize start_time here across threads/nodes. + pthread_barrier_wait(&barrier); - // Warmup connection. - if (options.warmup > 0) { - if (master) V("Warmup start."); + if (master && args.wait_given) { + if (get_time() < boot_time + args.wait_arg) { + double t = (boot_time + args.wait_arg)-get_time(); + V("Sleeping %.1fs for -W.", t); + sleep_time(t); + } + } #ifdef HAVE_LIBZMQ if (args.agent_given || args.agentmode_given) { if (master) V("Synchronizing."); - // 1. thread barrier: make sure our threads ready before syncing agents - // 2. sync agents: all threads across all agents are now ready - // 3. thread barrier: don't release our threads until all agents ready pthread_barrier_wait(&barrier); if (master) sync_agent(socket); pthread_barrier_wait(&barrier); @@ -1244,21 +1329,23 @@ void do_mutilate(const vector& servers, options_t& options, } #endif - int old_time = options.time; - // options.time = 1; + if (master && !args.scan_given && !args.search_given) + V("started at %f", get_time()); start = get_time(); for (Connection *conn: connections) { conn->start_time = start; - conn->options.time = options.warmup; conn->start(); // Kick the Connection into motion. } + // V("Start = %f", start); + + // Main event loop. while (1) { event_base_loop(base, loop_flag); - //#ifdef USE_CLOCK_GETTIME - // now = get_time(); + //#if USE_CLOCK_GETTIME + // now = get_time(); //#else struct timeval now_tv; event_base_gettimeofday_cached(base, &now_tv); @@ -1274,108 +1361,241 @@ void do_mutilate(const vector& servers, options_t& options, else break; } - bool restart = false; - for (Connection *conn: connections) - if (!conn->is_ready()) restart = true; + if (master && !args.scan_given && !args.search_given) + V("stopped at %f options.time = %d", get_time(), options.time); + + // Tear-down and accumulate stats. + for (Connection *conn: connections) { + stats.accumulate(conn->stats); + delete conn; + } + + stats.start = start; + stats.stop = now; + + event_config_free(config); + evdns_base_free(evdns, 0); + event_base_free(base); + } else if (servers.size() == 2) { + vector connections; + vector server_lead; + struct event_base *base2; + + if ((base2 = event_base_new_with_config(config)) == NULL) + DIE("event_base_new() fail"); + + string hostname1 = servers[0]; + string hostname2 = servers[1]; + string port = "11211"; + + int conns = args.measure_connections_given ? args.measure_connections_arg : + options.connections; + + srand(time(NULL)); + for (int c = 0; c < conns; c++) { + ConnectionMulti* conn = new ConnectionMulti(base, base2, evdns, + hostname1, hostname2, port, options,args.agentmode_given ? false : true); + int tries = 120; + int connected = 0; + int s = 2; + for (int i = 0; i < tries; i++) { + pthread_mutex_lock(&flock); + int ret = conn->do_connect(); + pthread_mutex_unlock(&flock); + if (ret) { + connected = 1; + fprintf(stderr,"thread %lu, multi conn: %d, connected!\n",pthread_self(),c+1); + break; + } + int d = s + rand() % 100; + sleep(d); + } + if (connected) { + conn->set_queue(trace_queue[conn->get_cid()]); + conn->set_lock(mutexes[conn->get_cid()]); + connections.push_back(conn); + } else { + fprintf(stderr,"conn multi: %d, not connected!!\n",c); - if (restart) { + } + if (c == 0) server_lead.push_back(conn); + } // Wait for all Connections to become IDLE. while (1) { - // FIXME: If there were to use EVLOOP_ONCE and all connections - // become ready before event_base_loop is called, this will - // deadlock. We should check for IDLE before calling - // event_base_loop. - event_base_loop(base, EVLOOP_ONCE); // EVLOOP_NONBLOCK); + // FIXME: If all connections become ready before event_base_loop + // is called, this will deadlock. + event_base_loop(base, EVLOOP_ONCE); bool restart = false; - for (Connection *conn: connections) + for (ConnectionMulti *conn: connections) if (!conn->is_ready()) restart = true; if (restart) continue; else break; } - } - for (Connection *conn: connections) { - conn->reset(); - conn->options.time = old_time; - } - if (master) V("Warmup stop."); - } + // FIXME: Remove. Not needed, testing only. + // // FIXME: Synchronize start_time here across threads/nodes. + // pthread_barrier_wait(&barrier); + + // Warmup connection. + if (options.warmup > 0) { + if (master) V("Warmup start."); + +#ifdef HAVE_LIBZMQ + if (args.agent_given || args.agentmode_given) { + if (master) V("Synchronizing."); + + // 1. thread barrier: make sure our threads ready before syncing agents + // 2. sync agents: all threads across all agents are now ready + // 3. thread barrier: don't release our threads until all agents ready + pthread_barrier_wait(&barrier); + if (master) sync_agent(socket); + pthread_barrier_wait(&barrier); + + if (master) V("Synchronized."); + } +#endif + + int old_time = options.time; + // options.time = 1; + + start = get_time(); + for (ConnectionMulti *conn: connections) { + conn->start_time = start; + conn->options.time = options.warmup; + conn->start(); // Kick the Connection into motion. + } + + while (1) { + event_base_loop(base, loop_flag); + event_base_loop(base2, loop_flag); + + //#ifdef USE_CLOCK_GETTIME + // now = get_time(); + //#else + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); + //#endif + + bool restart = false; + for (ConnectionMulti *conn: connections) + if (!conn->check_exit_condition(now)) + restart = true; + + if (restart) continue; + else break; + } + + bool restart = false; + for (ConnectionMulti *conn: connections) + if (!conn->is_ready()) restart = true; + if (restart) { - // FIXME: Synchronize start_time here across threads/nodes. - pthread_barrier_wait(&barrier); + // Wait for all Connections to become IDLE. + while (1) { + // FIXME: If there were to use EVLOOP_ONCE and all connections + // become ready before event_base_loop is called, this will + // deadlock. We should check for IDLE before calling + // event_base_loop. + event_base_loop(base, EVLOOP_ONCE); // EVLOOP_NONBLOCK); + event_base_loop(base2, EVLOOP_ONCE); // EVLOOP_NONBLOCK); - if (master && args.wait_given) { - if (get_time() < boot_time + args.wait_arg) { - double t = (boot_time + args.wait_arg)-get_time(); - V("Sleeping %.1fs for -W.", t); - sleep_time(t); + bool restart = false; + for (ConnectionMulti *conn: connections) + if (!conn->is_ready()) restart = true; + + if (restart) continue; + else break; + } + } + + for (ConnectionMulti *conn: connections) { + conn->reset(); + conn->options.time = old_time; + } + + if (master) V("Warmup stop."); } - } -#ifdef HAVE_LIBZMQ - if (args.agent_given || args.agentmode_given) { - if (master) V("Synchronizing."); - pthread_barrier_wait(&barrier); - if (master) sync_agent(socket); + // FIXME: Synchronize start_time here across threads/nodes. pthread_barrier_wait(&barrier); - if (master) V("Synchronized."); - } + if (master && args.wait_given) { + if (get_time() < boot_time + args.wait_arg) { + double t = (boot_time + args.wait_arg)-get_time(); + V("Sleeping %.1fs for -W.", t); + sleep_time(t); + } + } + +#ifdef HAVE_LIBZMQ + if (args.agent_given || args.agentmode_given) { + if (master) V("Synchronizing."); + + pthread_barrier_wait(&barrier); + if (master) sync_agent(socket); + pthread_barrier_wait(&barrier); + + if (master) V("Synchronized."); + } #endif - if (master && !args.scan_given && !args.search_given) - V("started at %f", get_time()); + if (master && !args.scan_given && !args.search_given) + V("started at %f", get_time()); - start = get_time(); - for (Connection *conn: connections) { - conn->start_time = start; - conn->start(); // Kick the Connection into motion. - } + start = get_time(); + for (ConnectionMulti *conn: connections) { + conn->start_time = start; + conn->start(); // Kick the Connection into motion. + } - // V("Start = %f", start); + // V("Start = %f", start); - // Main event loop. - while (1) { - event_base_loop(base, loop_flag); + // Main event loop. + while (1) { + event_base_loop(base, loop_flag); + event_base_loop(base2, loop_flag); - //#if USE_CLOCK_GETTIME - // now = get_time(); - //#else - struct timeval now_tv; - event_base_gettimeofday_cached(base, &now_tv); - now = tv_to_double(&now_tv); - //#endif + //#if USE_CLOCK_GETTIME + // now = get_time(); + //#else + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); + //#endif - bool restart = false; - for (Connection *conn: connections) - if (!conn->check_exit_condition(now)) - restart = true; + bool restart = false; + for (ConnectionMulti *conn: connections) + if (!conn->check_exit_condition(now)) + restart = true; - if (restart) continue; - else break; - } + if (restart) continue; + else break; + } - if (master && !args.scan_given && !args.search_given) - V("stopped at %f options.time = %d", get_time(), options.time); + if (master && !args.scan_given && !args.search_given) + V("stopped at %f options.time = %d", get_time(), options.time); - // Tear-down and accumulate stats. - for (Connection *conn: connections) { - stats.accumulate(conn->stats); - delete conn; - } + // Tear-down and accumulate stats. + for (ConnectionMulti *conn: connections) { + stats.accumulate(conn->stats); + delete conn; + } - stats.start = start; - stats.stop = now; + stats.start = start; + stats.stop = now; - event_config_free(config); - evdns_base_free(evdns, 0); - event_base_free(base); + event_config_free(config); + evdns_base_free(evdns, 0); + event_base_free(base); + event_base_free(base2); + } } void args_to_options(options_t* options) { From 06e79a35120eb650371cbb07903163047642457a Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 8 Sep 2021 09:06:37 -0400 Subject: [PATCH 38/57] class logic --- ConnectionMulti.cc | 53 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 5b3dfb0..49300e9 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -49,7 +49,12 @@ using namespace moodycamel; pthread_mutex_t cid_lock_m = PTHREAD_MUTEX_INITIALIZER; static uint32_t connids_m = 1; - + +#define NCLASSES 40 +static int classes = 0; +static int sizes[NCLASSES+1]; +static int inclusives[NCLASSES+1]; + typedef struct _evicted_type { bool evicted; uint32_t evictedFlags; @@ -59,10 +64,54 @@ typedef struct _evicted_type { char *evictedData; } evicted_t; -static int get_incl(int vl) { +static int get_incl(int vl, int kl) { + int clsid = get_class(vl,kl); + return inclusives[clsid]; +} + +static int init_inclusives(string inclusive_str) { + int j = 1; + for (int i = 0; i < inclusive_str.length(); i++) { + if (inclusive_str[i] == '-') { + continue; + } else { + inclusives[j] = inclusive_str[i] - '0'; + j++; + } + } +} + +static int init_classes() { + + double factor = 1.25; + unsigned int chunk_size = 48; + unsigned int item_size = 24; + unsigned int size = item_size + chunk_size; + unsigned int i = 0; + unsigned int chunk_size_max = 1048576/2; + while (++i < DEFAULT_NCLASSES-1) { + if (size >= chunk_size_max / factor) { + break; + } + if (size % CHUNK_ALIGN_BYTES) + size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); + sizes[i] = size; + size *= factor; + } + sizes[i] = chunk_size_max; + classes = i; + } static int get_class(int vl, uint32_t kl) { + int vsize = vl+kl+24+1+2; + int res = 1; + while (vsize > sizes[res]) + if (res++ == classes) { + fprintf(stderr,"item larger than max class size. vsize: %d, class size: %d\n",vsize,sizes[res]); + return -1; + } + return res; } void ConnectionMulti::output_op(Operation *op, int type, bool found) { From 56d98c8c1dab5ff4d905e2e83d6291d9aca4683a Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Mon, 13 Sep 2021 16:37:51 -0400 Subject: [PATCH 39/57] before changes to pointer array --- AgentStats.h | 4 + Connection.cc | 2 +- Connection.h | 14 +- ConnectionMulti.cc | 572 ++++++++++++++++++++++++++++++++------------ ConnectionOptions.h | 2 + ConnectionStats.h | 90 ++++++- Operation.h | 4 +- SConstruct | 32 ++- binary_protocol.h | 14 +- cmdline.ggo | 1 + mutilate.cc | 123 ++++++---- 11 files changed, 621 insertions(+), 237 deletions(-) diff --git a/AgentStats.h b/AgentStats.h index 69a5435..051dc4e 100644 --- a/AgentStats.h +++ b/AgentStats.h @@ -6,6 +6,10 @@ class AgentStats { public: uint64_t rx_bytes, tx_bytes; uint64_t gets, sets, accesses, get_misses; + uint64_t gets_l1, gets_l2, sets_l1, sets_l2; + uint64_t get_misses_l1, get_misses_l2; + uint64_t excl_wbs, incl_wbs; + uint64_t copies_to_l1; uint64_t skips; double start, stop; diff --git a/Connection.cc b/Connection.cc index 64722c7..e3b942f 100644 --- a/Connection.cc +++ b/Connection.cc @@ -39,7 +39,7 @@ extern int item_lock_hashpower; pthread_mutex_t cid_lock = PTHREAD_MUTEX_INITIALIZER; -uint32_t connids = 1; +uint32_t connids = 0; //pthread_mutex_t opaque_lock = PTHREAD_MUTEX_INITIALIZER; //uint32_t g_opaque = 0; diff --git a/Connection.h b/Connection.h index a28156b..1d17d1b 100644 --- a/Connection.h +++ b/Connection.h @@ -133,7 +133,9 @@ class Connection { Generator *keysize; KeyGenerator *keygen; Generator *iagen; + //std::vector> op_queue; std::unordered_map op_queue; + uint32_t op_queue_size; pthread_mutex_t* lock; //ConcurrentQueue *trace_queue; @@ -176,7 +178,7 @@ class Connection { class ConnectionMulti { public: - ConnectionMulti(struct event_base* _base1, struct event_base* _base2, struct evdns_base* _evdns, + ConnectionMulti(struct event_base* _base, struct evdns_base* _evdns, string _hostname1, string _hostname2, string _port, options_t options, bool sampling = true); @@ -215,8 +217,7 @@ class ConnectionMulti { string hostname2; string port; - struct event_base *base1; - struct event_base *base2; + struct event_base *base; struct evdns_base *evdns; struct bufferevent *bev1; struct bufferevent *bev2; @@ -262,7 +263,8 @@ class ConnectionMulti { uint32_t cid; int eof; - std::vector> op_queue; + //std::vector> op_queue; + Operation ***op_queue; uint32_t *op_queue_size; @@ -287,9 +289,9 @@ class ConnectionMulti { void issue_noop(double now = 0.0, int level = 1); int issue_touch(const char* key, int valuelen, double now = 0.0, int level = 1); int issue_delete(const char* key, double now = 0.0, int level = 1); - int issue_get_with_len(const char* key, int valuelen, double now = 0.0, bool quiet = false, int level = 1); + int issue_get_with_len(const char* key, int valuelen, double now = 0.0, bool quiet = false, int level = 1, int flags = 0, uint32_t l1opaque = 0, uint8_t log = 1); int issue_set(const char* key, const char* value, int length, - double now = 0.0, bool real_set = false, int level = 1); + double now = 0.0, int level = 1, int flags = 0, uint32_t l1opaque = 0, uint8_t log = 1); // protocol fucntions int set_request_ascii(const char* key, const char* value, int length); diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 49300e9..97f0321 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -36,14 +36,19 @@ switch (incl) { \ case 1: \ flags |= ITEM_INCL; \ + break; \ case 2: \ flags |= ITEM_EXCL; \ - } + break; \ + \ + } \ #define GET_INCL(incl,flags) \ - if ((flags & ITEM_INCL) == 1) incl = 1; \ - else if ((flags & ITEM_EXCL) == 1) incl = 2; \ -//#define DEBUGMC + if (flags & ITEM_INCL) incl = 1; \ + else if (flags & ITEM_EXCL) incl = 2; \ + +#define DEBUGMC +#define DEBUGS using namespace moodycamel; @@ -51,6 +56,7 @@ pthread_mutex_t cid_lock_m = PTHREAD_MUTEX_INITIALIZER; static uint32_t connids_m = 1; #define NCLASSES 40 +#define CHUNK_ALIGN_BYTES 8 static int classes = 0; static int sizes[NCLASSES+1]; static int inclusives[NCLASSES+1]; @@ -58,20 +64,19 @@ static int inclusives[NCLASSES+1]; typedef struct _evicted_type { bool evicted; uint32_t evictedFlags; - int evictedKeyLen; - int evictedLen; + uint32_t serverFlags; + uint32_t clsid; + uint32_t evictedKeyLen; + uint32_t evictedLen; char *evictedKey; char *evictedData; } evicted_t; -static int get_incl(int vl, int kl) { - int clsid = get_class(vl,kl); - return inclusives[clsid]; -} +extern int max_n[3]; -static int init_inclusives(string inclusive_str) { +static void init_inclusives(char *inclusive_str) { int j = 1; - for (int i = 0; i < inclusive_str.length(); i++) { + for (int i = 0; i < (int)strlen(inclusive_str); i++) { if (inclusive_str[i] == '-') { continue; } else { @@ -81,7 +86,7 @@ static int init_inclusives(string inclusive_str) { } } -static int init_classes() { +static void init_classes() { double factor = 1.25; unsigned int chunk_size = 48; @@ -89,7 +94,7 @@ static int init_classes() { unsigned int size = item_size + chunk_size; unsigned int i = 0; unsigned int chunk_size_max = 1048576/2; - while (++i < DEFAULT_NCLASSES-1) { + while (++i < NCLASSES-1) { if (size >= chunk_size_max / factor) { break; } @@ -114,6 +119,11 @@ static int get_class(int vl, uint32_t kl) { return res; } +static int get_incl(int vl, int kl) { + int clsid = get_class(vl,kl); + return inclusives[clsid]; +} + void ConnectionMulti::output_op(Operation *op, int type, bool found) { char output[1024]; char k[256]; @@ -171,12 +181,14 @@ void ConnectionMulti::output_op(Operation *op, int type, bool found) { /** * Create a new connection to a server endpoint. */ -ConnectionMulti::ConnectionMulti(struct event_base* _base1, struct event_base* _base2, struct evdns_base* _evdns, +ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _evdns, string _hostname1, string _hostname2, string _port, options_t _options, bool sampling ) : start_time(0), stats(sampling), options(_options), - hostname1(_hostname1), hostname2(_hostname2), port(_port), base1(_base1), base2(_base2), evdns(_evdns) + hostname1(_hostname1), hostname2(_hostname2), port(_port), base(_base), evdns(_evdns) { + init_classes(); + init_inclusives(options.inclusives); valuesize = createGenerator(options.valuesize); keysize = createGenerator(options.keysize); @@ -193,7 +205,7 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base1, struct event_base* _ iagen->set_lambda(options.lambda); } - read_state = INIT_READ; + read_state = IDLE; write_state = INIT_WRITE; last_quiet1 = false; last_quiet2 = false; @@ -206,25 +218,27 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base1, struct event_base* _ op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); - + op_queue = (Operation***)malloc(sizeof(Operation**)*(LEVELS+1)); issue_buf_n = (int*)malloc(sizeof(int)*(LEVELS+1)); issue_buf_size = (int*)malloc(sizeof(int)*(LEVELS+1)); issue_buf = (unsigned char**)malloc(sizeof(unsigned char*)*(LEVELS+1)); issue_buf_pos = (unsigned char**)malloc(sizeof(unsigned char*)*(LEVELS+1)); - for (int i = 1; i <= LEVELS; i++) { + for (int i = 0; i <= LEVELS; i++) { op_queue_size[i] = 0; - opaque[i] = 0; + opaque[i] = 1; issue_buf[i] = (unsigned char*)malloc(sizeof(unsigned char)*MAX_BUFFER_SIZE); - std::unordered_map op_q; - op_queue.push_back(op_q); + memset(issue_buf[i],0,MAX_BUFFER_SIZE); + //op_queue[i] = (Operation*)malloc(sizeof(int)*OPAQUE_MAX); + op_queue[i] = (Operation**)malloc(sizeof(Operation*)*OPAQUE_MAX); issue_buf_pos[i] = issue_buf[i]; issue_buf_size[i] = 0; + issue_buf_n[i] = 0; } - timer = evtimer_new(base1, timer_cb, this); + timer = evtimer_new(base, timer_cb_m, this); } @@ -246,11 +260,11 @@ int ConnectionMulti::do_connect() { int connected = 0; if (options.unix_socket) { - bev1 = bufferevent_socket_new(base1, -1, BEV_OPT_CLOSE_ON_FREE); + bev1 = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev1, bev_read_cb1, bev_write_cb, bev_event_cb1, this); bufferevent_enable(bev1, EV_READ | EV_WRITE); - bev2 = bufferevent_socket_new(base2, -1, BEV_OPT_CLOSE_ON_FREE); + bev2 = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev2, bev_read_cb2, bev_write_cb, bev_event_cb2, this); bufferevent_enable(bev2, EV_READ | EV_WRITE); @@ -350,17 +364,17 @@ int ConnectionMulti::issue_getsetorset(double now) { int nissued = 0; while (nissued < options.depth) { - if (trace_queue->size() > 0) { - pthread_mutex_lock(lock); + + //pthread_mutex_lock(lock); + if (!trace_queue->empty()) { line = trace_queue->front(); trace_queue->pop(); - pthread_mutex_unlock(lock); if (line.compare("EOF") == 0) { eof = 1; return 1; } - stringstream ss(line); + //pthread_mutex_unlock(lock); int Op = 0; int vl = 0; @@ -409,6 +423,9 @@ int ConnectionMulti::issue_getsetorset(double now) { memset(key,0,256); strncpy(key, rKey.c_str(),255); int issued = 0; + int incl = get_incl(vl,strlen(key)); + int flags = 0; + SET_INCL(incl,flags); switch(Op) { case 0: @@ -417,26 +434,24 @@ int ConnectionMulti::issue_getsetorset(double now) { break; case 1: if (nissued < options.depth-1) { - issued = issue_get_with_len(key, vl, now, true, 1); - last_quiet1 = true; + issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); + last_quiet1 = false; } else { - issued = issue_get_with_len(key, vl, now, false, 1); + issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); last_quiet1 = false; } + this->stats.gets++; break; case 2: if (last_quiet1) { issue_noop(now,1); } int index = lrand48() % (1024 * 1024); - int classid = 0; - int incl = get_incl(vl); - int flags = 0; - SET_INCL(incl,flags); flags |= ITEM_DIRTY; - issued = issue_set(key, &random_char[index], vl, now, flags,1); + issued = issue_set(key, &random_char[index], vl, now, 1, flags, 0, 1); last_quiet1 = false; + this->stats.sets++; break; } @@ -451,6 +466,7 @@ int ConnectionMulti::issue_getsetorset(double now) { break; } } else { + //pthread_mutex_unlock(lock); //we should protect this with a condition variable //since trace queue size is 0 and not EOF. return 0; @@ -461,12 +477,12 @@ int ConnectionMulti::issue_getsetorset(double now) { last_quiet1 = false; } #ifdef DEBUGMC - fprintf(stderr,"getsetorset issuing %d reqs last quiet %d\n",issue_buf_n,last_quiet); - char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); - fprintf(stderr,"-------------------------------------\n"); - memcpy(output,issue_buf,issue_buf_size); - write(2,output,issue_buf_size); - fprintf(stderr,"\n-------------------------------------\n"); + fprintf(stderr,"getsetorset issuing to l1 %d reqs last quiet %d\n",issue_buf_n[1],last_quiet1); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size[1]+512)); + //fprintf(stderr,"-------------------------------------\n"); + //memcpy(output,issue_buf[1],issue_buf_size[1]); + //write(2,output,issue_buf_size[1]); + //fprintf(stderr,"\n-------------------------------------\n"); free(output); #endif //buffer is ready to go! @@ -476,6 +492,29 @@ int ConnectionMulti::issue_getsetorset(double now) { issue_buf_pos[1] = issue_buf[1]; issue_buf_size[1] = 0; issue_buf_n[1] = 0; + + if (issue_buf_n[2] >= options.depth-2) { + if (last_quiet2) { + issue_noop(now,2); + last_quiet2 = false; + } +#ifdef DEBUGMC + fprintf(stderr,"getsetorset issuing to l2 %d reqs last quiet %d\n",issue_buf_n[2],last_quiet2); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size[2]+522)); + //fprintf(stderr,"-------------------------------------\n"); + //memcpy(output,issue_buf[2],issue_buf_size[2]); + //write(2,output,issue_buf_size[2]); + //fprintf(stderr,"\n-------------------------------------\n"); + free(output); +#endif + //buffer is ready to go! + bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); + + memset(issue_buf[2],0,issue_buf_size[2]); + issue_buf_pos[2] = issue_buf[2]; + issue_buf_size[2] = 0; + issue_buf_n[2] = 0; + } return ret; @@ -484,8 +523,11 @@ int ConnectionMulti::issue_getsetorset(double now) { /** * Issue a get request to the server. */ -int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, int level) { - Operation op; +int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, + int level, int flags, uint32_t l1opaque, uint8_t log) { + //Operation op; + Operation *pop = new Operation(); + Operation op = *pop; #if HAVE_CLOCK_GETTIME op.start_time = get_time_accurate(); @@ -493,7 +535,7 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no if (now == 0.0) { #if USE_CACHED_TIME struct timeval now_tv; - event_base_gettimeofday_cached(base1, &now_tv); + event_base_gettimeofday_cached(base, &now_tv); op.start_time = tv_to_double(&now_tv); #else op.start_time = get_time(); @@ -508,11 +550,16 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no op.type = Operation::GET; op.opaque = opaque[level]++; op.level = level; - op_queue[level][op.opaque] = op; + op.l1opaque = l1opaque; + op.log = log; + GET_INCL(op.incl,flags); + op.clsid = get_class(valuelen,strlen(key)); + op_queue[level][op.opaque] = pop; + //op_queue[level].push(op); op_queue_size[level]++; if (opaque[level] > OPAQUE_MAX) { - opaque[level] = 0; + opaque[level] = 1; } //if (read_state == IDLE) read_state = WAITING_FOR_GET; @@ -534,12 +581,17 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no issue_buf_pos[level] += keylen; issue_buf_size[level] += keylen; issue_buf_n[level]++; +#ifdef DEBUGS + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } +#endif if (read_state != LOADING) { stats.tx_bytes += 24 + keylen; } - stats.log_access(op); + //stats.log_access(op); return 1; } @@ -547,7 +599,8 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no * Issue a get request to the server. */ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int level) { - Operation op; + Operation *pop = new Operation(); + Operation op = *pop; #if HAVE_CLOCK_GETTIME op.start_time = get_time_accurate(); @@ -555,7 +608,7 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int if (now == 0.0) { #if USE_CACHED_TIME struct timeval now_tv; - event_base_gettimeofday_cached(base1, &now_tv); + event_base_gettimeofday_cached(base, &now_tv); op.start_time = tv_to_double(&now_tv); #else op.start_time = get_time(); @@ -570,11 +623,12 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int op.type = Operation::TOUCH; op.opaque = opaque[level]++; op.level = level; - op_queue[level][op.opaque] = op; + op_queue[level][op.opaque] = pop; + //op_queue[level].push(op); op_queue_size[level]++; if (opaque[level] > OPAQUE_MAX) { - opaque[level] = 0; + opaque[level] = 1; } //if (read_state == IDLE) read_state = WAITING_FOR_GET; @@ -582,13 +636,19 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int // each line is 4-bytes binary_header_t h = { 0x80, CMD_TOUCH, htons(keylen), - 0x00, 0x00, {htons(0)}, - htonl(keylen) }; + 0x04, 0x00, {htons(0)}, + htonl(keylen + 4) }; h.opaque = htonl(op.opaque); memcpy(issue_buf_pos[level],&h,24); issue_buf_pos[level] += 24; issue_buf_size[level] += 24; + + uint32_t exp = 0; + memcpy(issue_buf_pos[level],&exp,4); + issue_buf_pos[level] += 4; + issue_buf_size[level] += 4; + memcpy(issue_buf_pos[level],key,keylen); issue_buf_pos[level] += keylen; issue_buf_size[level] += keylen; @@ -598,7 +658,7 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int stats.tx_bytes += 24 + keylen; } - stats.log_access(op); + //stats.log_access(op); return 1; } @@ -606,7 +666,9 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int * Issue a delete request to the server. */ int ConnectionMulti::issue_delete(const char* key, double now, int level) { - Operation op; + //Operation op; + Operation *pop = new Operation(); + Operation op = *pop; #if HAVE_CLOCK_GETTIME op.start_time = get_time_accurate(); @@ -614,7 +676,7 @@ int ConnectionMulti::issue_delete(const char* key, double now, int level) { if (now == 0.0) { #if USE_CACHED_TIME struct timeval now_tv; - event_base_gettimeofday_cached(base1, &now_tv); + event_base_gettimeofday_cached(base, &now_tv); op.start_time = tv_to_double(&now_tv); #else op.start_time = get_time(); @@ -628,11 +690,12 @@ int ConnectionMulti::issue_delete(const char* key, double now, int level) { op.type = Operation::DELETE; op.opaque = opaque[level]++; op.level = level; - op_queue[level][op.opaque] = op; + op_queue[level][op.opaque] = pop; + //op_queue[level].push(op); op_queue_size[level]++; if (opaque[level] > OPAQUE_MAX) { - opaque[level] = 0; + opaque[level] = 1; } //if (read_state == IDLE) read_state = WAITING_FOR_GET; @@ -680,8 +743,10 @@ void ConnectionMulti::issue_noop(double now, int level) { * Issue a set request to the server. */ int ConnectionMulti::issue_set(const char* key, const char* value, int length, - double now, int flags, int level) { - Operation op; + double now, int level, int flags, uint32_t l1opaque, uint8_t log) { + //Operation op; + Operation *pop = new Operation(); + Operation op = *pop; #if HAVE_CLOCK_GETTIME op.start_time = get_time_accurate(); @@ -696,13 +761,20 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, op.type = Operation::SET; op.opaque = opaque[level]++; op.level = level; + op.incl = 0; + op.log = log; + op.l1opaque = l1opaque; GET_INCL(op.incl,flags); op.clsid = get_class(length,strlen(key)); - op_queue[level][op.opaque] = op; + op_queue[level][op.opaque] = pop; + //op_queue[level].push(op); op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"issing set: %s, size: %u, level %d, flags: %d\n",key,length,level,flags); +#endif if (opaque[level] > OPAQUE_MAX) { - opaque[level] = 0; + opaque[level] = 1; } uint16_t keylen = strlen(key); @@ -730,18 +802,38 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, memcpy(issue_buf_pos[level],key,keylen); issue_buf_pos[level] += keylen; issue_buf_size[level] += keylen; - + +//if (issue_buf_pos[level]+length >= issue_buf[level]+MAX_BUFFER_SIZE-1 || +// issue_buf_size[level]+length >= MAX_BUFFER_SIZE-1) { +// +// fprintf(stderr,"issing set: %s, size: %u, level %d, flags: %d\n",key,length,level,flags); +// for (int i = 1; i <= 2; i++) { +// fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); +// } +// for (int i = 1; i <= 2; i++) { +// fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); +// } +// for (int i = 1; i <= 2; i++) { +// fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); +// } +// +//} memcpy(issue_buf_pos[level],value,length); issue_buf_pos[level] += length; issue_buf_size[level] += length; issue_buf_n[level]++; - + +#ifdef DEBUGS + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } +#endif if (read_state != LOADING) { stats.tx_bytes += length + 32 + keylen; } - stats.log_access(op); + //stats.log_access(op); return 1; } @@ -750,9 +842,8 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, */ void ConnectionMulti::pop_op(Operation *op) { - uint32_t opopq = op->opaque; uint8_t level = op->level; - op_queue[level].erase(opopq); + //op_queue[level].erase(op); op_queue_size[level]--; @@ -792,16 +883,56 @@ void ConnectionMulti::finish_op(Operation *op, int was_hit) { if (options.successful_queries && was_hit) { switch (op->type) { - case Operation::GET: stats.log_get(*op); break; - case Operation::SET: stats.log_set(*op); break; + case Operation::GET: + switch (op->level) { + case 1: + stats.log_get_l1(*op); + break; + case 2: + stats.log_get_l2(*op); + break; + } + break; + case Operation::SET: + switch (op->level) { + case 1: + stats.log_set_l1(*op); + break; + case 2: + stats.log_set_l2(*op); + break; + } + break; case Operation::DELETE: break; case Operation::TOUCH: break; default: DIE("Not implemented."); } } else { switch (op->type) { - case Operation::GET: stats.log_get(*op); break; - case Operation::SET: stats.log_set(*op); break; + case Operation::GET: + if (op->log) { + switch (op->level) { + case 1: + stats.log_get_l1(*op); + break; + case 2: + stats.log_get_l2(*op); + break; + } + } + break; + case Operation::SET: + if (op->log) { + switch (op->level) { + case 1: + stats.log_set_l1(*op); + break; + case 2: + stats.log_set_l2(*op); + break; + } + } + break; case Operation::DELETE: break; case Operation::TOUCH: break; default: DIE("Not implemented."); @@ -809,9 +940,10 @@ void ConnectionMulti::finish_op(Operation *op, int was_hit) { } last_rx = now; - uint32_t opopq = op->opaque; uint8_t level = op->level; - op_queue[level].erase(opopq); + //op_queue[level].erase(op_queue[level].begin()+opopq); + op_queue[level][op->opaque] = 0; + delete op; op_queue_size[level]--; read_state = IDLE; @@ -887,6 +1019,9 @@ void ConnectionMulti::event_callback1(short events) { (void *) &one, sizeof(one)) < 0) DIE("setsockopt()"); } +#ifdef DEBUGMC + fprintf(stderr,"libevent connected %s, fd: %u\n",hostname1.c_str(),bufferevent_getfd(bev1)); +#endif drive_write_machine(); @@ -920,6 +1055,9 @@ void ConnectionMulti::event_callback2(short events) { (void *) &one, sizeof(one)) < 0) DIE("setsockopt()"); } +#ifdef DEBUGMC + fprintf(stderr,"libevent connected %s, fd: %u\n",hostname2.c_str(),bufferevent_getfd(bev2)); +#endif } else if (events & BEV_EVENT_ERROR) { @@ -965,7 +1103,8 @@ void ConnectionMulti::drive_write_machine(double now) { break; case ISSUING: - if (op_queue_size[1] >= (size_t) options.depth) { + if ( (op_queue_size[1] >= (size_t) options.depth) || + (op_queue_size[2] >= (size_t) options.depth) ) { write_state = WAITING_FOR_OPQ; return; } else if (now < next_time) { @@ -994,8 +1133,8 @@ void ConnectionMulti::drive_write_machine(double now) { next_time += iagen->generate(); if (options.skip && options.lambda > 0.0 && - now - next_time > 0.005000 && - op_queue.size() >= (size_t) options.depth) { + now - next_time > 0.005000 ) { + //op_queue.size() >= (size_t) options.depth) { while (next_time < now - 0.004000) { stats.skips++; @@ -1017,7 +1156,10 @@ void ConnectionMulti::drive_write_machine(double now) { break; case WAITING_FOR_OPQ: - if (op_queue_size[1] >= (size_t) options.depth) return; + if ( (op_queue_size[1] >= (size_t) options.depth) || + (op_queue_size[2] >= (size_t) options.depth) ) { + return; + } write_state = ISSUING; break; @@ -1034,37 +1176,84 @@ void ConnectionMulti::drive_write_machine(double now) { * @param input evBuffer to read response from * @return true if consumed, false if not enough data in buffer. */ -static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, bool &found, int &opcode, uint32_t &opaque) { +static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, bool &found, int &opcode, uint32_t &opaque, evicted_t *evict, int level) { // Read the first 24 bytes as a header int length = evbuffer_get_length(input); if (length < 24) return false; binary_header_t* h = reinterpret_cast(evbuffer_pullup(input, 24)); - assert(h); + //assert(h); +#ifdef DEBUGMC + fprintf(stderr,"handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",level, + h->opcode,ntohl(h->opaque),ntohl(h->key_len),h->extra_len, + ntohl(h->body_len),ntohs(h->status)); +#endif - int bl = ntohl(h->body_len); + uint32_t bl = ntohl(h->body_len); + uint16_t kl = ntohs(h->key_len); + uint8_t el = h->extra_len; // Not whole response int targetLen = 24 + bl; - if (length < targetLen) return false; - //fprintf(stderr,"handle resp - opcode: %u opaque: %u len: %u status: %u\n", - // h->opcode,ntohl(h->opaque), - // ntohl(h->body_len),ntohl(h->status)); + if (length < targetLen) { + return false; + } opcode = h->opcode; opaque = ntohl(h->opaque); + uint16_t status = ntohs(h->status); + + // If something other than success, count it as a miss - if (opcode == CMD_GET && h->status) { - conn->stats.get_misses++; - conn->stats.window_get_misses++; + if (opcode == CMD_GET && status == RESP_NOT_FOUND) { + switch(level) { + case 1: + conn->stats.get_misses_l1++; + break; + case 2: + conn->stats.get_misses_l2++; + conn->stats.get_misses++; + conn->stats.window_get_misses++; + break; + + } found = false; - } + evbuffer_drain(input, targetLen); - - if (bl > 0 && opcode == 1) { - //fprintf(stderr,"set resp len: %u\n",bl); - //void *data = malloc(bl); - //data = evbuffer_pullup(input, bl); - //free(data); + } else if (opcode == CMD_SET && kl > 0) { + //first data is extras: clsid, flags, eflags + if (evict) { + evbuffer_drain(input,24); + unsigned char *buf = evbuffer_pullup(input,bl); + + evict->clsid = *((uint32_t*)buf); + evict->clsid = ntohl(evict->clsid); + buf += 4; + + evict->serverFlags = *((uint32_t*)buf); + evict->serverFlags = ntohl(evict->serverFlags); + buf += 4; + + evict->evictedFlags = *((uint32_t*)buf); + evict->evictedFlags = ntohl(evict->evictedFlags); + buf += 4; + + + evict->evictedKeyLen = kl; + evict->evictedKey = (char*)malloc(kl+1); + memset(evict->evictedKey,0,kl+1); + memcpy(evict->evictedKey,buf,kl); + buf += kl; + + evict->evictedLen = bl - kl - el; + evict->evictedData = (char*)malloc(evict->evictedLen); + memcpy(evict->evictedData,buf,evict->evictedLen); + evict->evicted = true; + evbuffer_drain(input,bl); + } else { + evbuffer_drain(input, targetLen); + } + } else if (opcode == CMD_TOUCH && status == RESP_NOT_FOUND) { + found = false; evbuffer_drain(input, targetLen); } else { evbuffer_drain(input, targetLen); @@ -1099,9 +1288,10 @@ void ConnectionMulti::read_callback1() { int opcode; uint32_t opaque; - evicted_t evict; + evicted_t *evict = (evicted_t*)malloc(sizeof(evicted_t)); + memset(evict,0,sizeof(evicted_t)); - full_read = handle_response(this,input, done, found, opcode, opaque, evict); + full_read = handle_response(this,input, done, found, opcode, opaque, evict,1); if (full_read) { if (opcode == CMD_NOOP) { #ifdef DEBUGMC @@ -1109,9 +1299,12 @@ void ConnectionMulti::read_callback1() { sprintf(out,"conn l1: %u, reading noop\n",cid); write(2,out,strlen(out)); #endif + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); continue; } - op = &op_queue[1][opaque]; + op = op_queue[1][opaque]; #ifdef DEBUGMC char out[128]; sprintf(out,"conn l1: %u, reading opaque: %u\n",cid,opaque); @@ -1124,6 +1317,9 @@ void ConnectionMulti::read_callback1() { sprintf(out2,"conn l1: %u, bad op: %s\n",cid,op->key.c_str()); write(2,out2,strlen(out2)); #endif + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); continue; } } else { @@ -1131,6 +1327,7 @@ void ConnectionMulti::read_callback1() { } + double now = get_time(); switch (op->type) { case Operation::GET: if (done) { @@ -1138,14 +1335,15 @@ void ConnectionMulti::read_callback1() { /* issue a get a l2 */ string key = op->key; int vl = op->valuelen; - issue_get_with_len(key.c_str(),vl,0,false,2); - //probably want to finish this op somehow - //think about the stats + int flags = 0; + SET_INCL(op->incl,flags); + issue_get_with_len(key.c_str(),vl,now,false,2,flags,0,1); + finish_op(op,0); } else { if (found) { if (op->incl == 1) { - issue_touch(op->key.c_str(),0,2); + issue_touch(op->key.c_str(),op->valuelen,now,2); } finish_op(op,1); } else { @@ -1160,13 +1358,15 @@ void ConnectionMulti::read_callback1() { break; case Operation::SET: if (op->incl == 1) { - issue_touch(op->key.c_str(),0,2); + issue_touch(op->key.c_str(),op->valuelen,0,2); } if (evict->evicted) { - if ((evict->evictedFlags & ITEM_INCL) == 1 && (evicted->evictedFlags & ITEM_DIRTY)) { - issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, 0, ITEM_INCL | ITEM_DIRTY, 2); - } else if ((evict->evictedFlags & ITEM_EXCL) == 1) { - issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, 0, ITEM_EXCL, 2); + if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, 2, ITEM_INCL | ITEM_DIRTY, 0, 1); + this->stats.incl_wbs++; + } else if (evict->evictedFlags & ITEM_EXCL) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, 2, ITEM_EXCL, 0, 1); + this->stats.excl_wbs++; } } finish_op(op,1); @@ -1176,6 +1376,12 @@ void ConnectionMulti::read_callback1() { DIE("not implemented"); } + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + } double now = get_time(); @@ -1183,10 +1389,18 @@ void ConnectionMulti::read_callback1() { return; } #ifdef DEBUGMC - fprintf(stderr,"read_cb1 done with current queue of ops: %d and issue_buf_n: %d\n",op_queue_size,issue_buf_n); - for (auto x : op_queue) { - cerr << x.first << ": " << x.second.key << endl; - } + fprintf(stderr,"read_cb1 done with current queue of ops in l1: %d and issue_buf_n in l1: %d\n",op_queue_size[1],issue_buf_n[1]); + //for (auto x : op_queue[1]) { + // if (x.opaque > 0) { + // cerr << x.opaque << ": " << x.key << endl; + // } + //} + //fprintf(stderr,"read_cb1 done with current queue of ops in l2: %d and issue_buf_n in l2: %d\n",op_queue_size[2],issue_buf_n[2]); + //for (auto x : op_queue[2]) { + // if (x.opaque > 0) { + // cerr << x.opaque << ": " << x.key << endl; + // } + //} #endif //buffer is ready to go! if (issue_buf_n[1] > 0) { @@ -1195,12 +1409,12 @@ void ConnectionMulti::read_callback1() { last_quiet1 = false; } #ifdef DEBUGMC - fprintf(stderr,"read_cb1 writing %d reqs, last quiet %d\n",issue_buf_n,last_quiet); - char *output = (char*)malloc(sizeof(char)*(issue_buf_size+512)); - fprintf(stderr,"-------------------------------------\n"); - memcpy(output,issue_buf,issue_buf_size); - write(2,output,issue_buf_size); - fprintf(stderr,"\n-------------------------------------\n"); + fprintf(stderr,"read_cb1 writing %d reqs to l1, last quiet %d\n",issue_buf_n[1],last_quiet1); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size[1]+512)); + //fprintf(stderr,"-------------------------------------\n"); + //memcpy(output,issue_buf[1],issue_buf_size[1]); + //write(2,output,issue_buf_size[1]); + //fprintf(stderr,"\n-------------------------------------\n"); free(output); #endif @@ -1210,9 +1424,35 @@ void ConnectionMulti::read_callback1() { issue_buf_size[1] = 0; issue_buf_n[1] = 0; } + + if (issue_buf_n[2] > 0) { + if (last_quiet2) { + issue_noop(now,2); + last_quiet2 = false; + } +#ifdef DEBUGMC + fprintf(stderr,"read_cb1 writing %d reqs to l2, last quiet %d\n",issue_buf_n[2],last_quiet2); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size[2]+512)); + //fprintf(stderr,"-------------------------------------\n"); + //memcpy(output,issue_buf[2],issue_buf_size[2]); + //write(2,output,issue_buf_size[2]); + //fprintf(stderr,"\n-------------------------------------\n"); + free(output); +#endif + + bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); + memset(issue_buf[2],0,issue_buf_size[2]); + issue_buf_pos[2] = issue_buf[2]; + issue_buf_size[2] = 0; + issue_buf_n[2] = 0; + } last_tx = now; stats.log_op(op_queue_size[1]); + stats.log_op(op_queue_size[2]); + //for (int i = 1; i <= 2; i++) { + // fprintf(stderr,"max issue buf n[%d]: %u\n",i,max_n[i]); + //} drive_write_machine(); // update events @@ -1248,7 +1488,7 @@ void ConnectionMulti::read_callback2() { int opcode; uint32_t opaque; - full_read = handle_response(this,input, done, found, opcode, opaque); + full_read = handle_response(this,input, done, found, opcode, opaque, NULL,2); if (full_read) { if (opcode == CMD_NOOP) { #ifdef DEBUGMC @@ -1258,7 +1498,7 @@ void ConnectionMulti::read_callback2() { #endif continue; } - op = &op_queue[2][opaque]; + op = op_queue[2][opaque]; #ifdef DEBUGMC char out[128]; sprintf(out,"conn l2: %u, reading opaque: %u\n",cid,opaque); @@ -1278,6 +1518,7 @@ void ConnectionMulti::read_callback2() { } + double now = get_time(); switch (op->type) { case Operation::GET: if (done) { @@ -1292,16 +1533,10 @@ void ConnectionMulti::read_callback2() { int flags = 0; SET_INCL(incl,flags); finish_op(op,0); // sets read_state = IDLE - if (last_quiet1) { - issue_noop(0,1); - } - issue_set(key, &random_char[index], valuelen, 0, flags, 1); + issue_set(key, &random_char[index], valuelen, now, 1, flags, 0, 1); last_quiet1 = false; if (incl == 1) { - if (last_quiet2) { - issue_noop(0,2); - } - issue_set(key, &random_char[index], valuelen, 0, flags, 2); + issue_set(key, &random_char[index], valuelen, now, 2, flags, 0, 1); last_quiet2 = false; } @@ -1316,11 +1551,12 @@ void ConnectionMulti::read_callback2() { int flags = 0; SET_INCL(incl,flags); //found in l2, set in l1 - issue_set(key, &random_char[index],valuelen, 0, flags, 1); + issue_set(key, &random_char[index],valuelen, now, 1, flags, 0, 0); if (incl == 2) { //if exclusive, remove from l2 - issue_delete(key,0,1); + issue_delete(key,now,2); } + this->stats.copies_to_l1++; finish_op(op,1); } else { @@ -1338,17 +1574,24 @@ void ConnectionMulti::read_callback2() { break; case Operation::TOUCH: if (!found) { - char key[256]; - string keystr = op->key; - strcpy(key, keystr.c_str()); - int valuelen = op->valuelen; - int index = lrand48() % (1024 * 1024); - int incl = op->incl; - int flags = 0; - SET_INCL(incl,flags); - // not found in l2, set in l2 - issue_set(key, &random_char[index],valuelen, 0, flags, 2); + //char key[256]; + //string keystr = op->key; + //strcpy(key, keystr.c_str()); + //int valuelen = op->valuelen; + //int index = lrand48() % (1024 * 1024); + //int incl = op->incl; + //int flags = 0; + //SET_INCL(incl,flags); + //// not found in l2, set in l2 + //issue_set(key, &random_char[index],valuelen, 0, flags, 2, 0, 1); + finish_op(op,0); + } else { + finish_op(op,1); } + break; + case Operation::DELETE: + finish_op(op,1); + break; default: fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); DIE("not implemented"); @@ -1361,10 +1604,18 @@ void ConnectionMulti::read_callback2() { return; } #ifdef DEBUGMC - fprintf(stderr,"read_cb2 done with current queue of ops: %d and issue_buf_n: %d\n",op_queue_size,issue_buf_n); - for (auto x : op_queue) { - cerr << x.first << ": " << x.second.key << endl; - } + fprintf(stderr,"read_cb2 done with current queue of ops l1: %d and issue_buf_n: %d\n",op_queue_size[1],issue_buf_n[1]); + //for (auto x : op_queue[1]) { + // if (x.opaque > 0) { + // cerr << x.opaque << ": " << x.key << endl; + // } + //} + //fprintf(stderr,"read_cb2 done with current queue of ops l2: %d and issue_buf_n: %d\n",op_queue_size[2],issue_buf_n[2]); + //for (auto x : op_queue[2]) { + // if (x.opaque > 0) { + // cerr << x.opaque << ": " << x.key << endl; + // } + //} #endif //buffer is ready to go! if (issue_buf_n[2] > 0) { @@ -1375,10 +1626,10 @@ void ConnectionMulti::read_callback2() { #ifdef DEBUGMC fprintf(stderr,"read_cb2 writing %d reqs, last quiet %d\n",issue_buf_n[2],last_quiet2); char *output = (char*)malloc(sizeof(char)*(issue_buf_size[2]+512)); - fprintf(stderr,"-------------------------------------\n"); - memcpy(output,issue_buf[2],issue_buf_size[2]); - write(2,output,issue_buf_size[2]); - fprintf(stderr,"\n-------------------------------------\n"); + //fprintf(stderr,"-------------------------------------\n"); + //memcpy(output,issue_buf[2],issue_buf_size[2]); + //write(2,output,issue_buf_size[2]); + //fprintf(stderr,"\n-------------------------------------\n"); free(output); #endif @@ -1388,10 +1639,33 @@ void ConnectionMulti::read_callback2() { issue_buf_size[2] = 0; issue_buf_n[2] = 0; } + //buffer is ready to go! + if (issue_buf_n[1] > 0) { + if (last_quiet1) { + issue_noop(now,1); + last_quiet1 = false; + } +#ifdef DEBUGMC + fprintf(stderr,"read_cb1 writing %d reqs to l1, last quiet %d\n",issue_buf_n[1],last_quiet1); + char *output = (char*)malloc(sizeof(char)*(issue_buf_size[1]+512)); + //fprintf(stderr,"-------------------------------------\n"); + //memcpy(output,issue_buf[1],issue_buf_size[1]); + //write(2,output,issue_buf_size[1]); + //fprintf(stderr,"\n-------------------------------------\n"); + free(output); +#endif + + bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); + memset(issue_buf[1],0,issue_buf_size[1]); + issue_buf_pos[1] = issue_buf[1]; + issue_buf_size[1] = 0; + issue_buf_n[1] = 0; + } last_tx = now; stats.log_op(op_queue_size[2]); - //drive_write_machine(); + stats.log_op(op_queue_size[1]); + drive_write_machine(); // update events //if (bev != NULL) { diff --git a/ConnectionOptions.h b/ConnectionOptions.h index 59bf6a1..a9874e4 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -57,6 +57,8 @@ typedef struct { bool oob_thread; bool moderate; + char inclusives[256]; + } options_t; #endif // CONNECTIONOPTIONS_H diff --git a/ConnectionStats.h b/ConnectionStats.h index c2d1d42..a9151cd 100644 --- a/ConnectionStats.h +++ b/ConnectionStats.h @@ -22,35 +22,69 @@ class ConnectionStats { public: ConnectionStats(bool _sampling = true) : #ifdef USE_ADAPTIVE_SAMPLER - get_sampler(100000), set_sampler(100000), access_sampler(100000), op_sampler(100000), + get_sampler(100000), set_sampler(100000), + get_l1_sampler(100000), set_l1_sampler(100000), + get_l2_sampler(100000), set_l2_sampler(100000), + access_sampler(100000), op_sampler(100000), #elif defined(USE_HISTOGRAM_SAMPLER) - get_sampler(10000,1), set_sampler(10000,1), access_sampler(10000,1), op_sampler(1000,1), + get_sampler(10000,1), set_sampler(10000,1), + get_l1_sampler(10000,1), set_l1_sampler(10000,1), + get_l2_sampler(10000,1), set_l2_sampler(10000,1), + access_sampler(10000,1), op_sampler(1000,1), #else - get_sampler(200), set_sampler(200), access_sampler(200), op_sampler(100), + get_sampler(200), set_sampler(200), + get_l1_sampler(200), set_l1_sampler(200), + get_l2_sampler(200), set_l2_sampler(200), + access_sampler(200), op_sampler(100), #endif - rx_bytes(0), tx_bytes(0), gets(0), sets(0), accesses(0), - get_misses(0), window_gets(0), window_sets(0), window_accesses(0), + rx_bytes(0), tx_bytes(0), + gets(0), sets(0), + gets_l1(0), sets_l1(0), + gets_l2(0), sets_l2(0), + accesses(0), + get_misses(0), + get_misses_l1(0), get_misses_l2(0), + excl_wbs(0), incl_wbs(0), + copies_to_l1(0), + window_gets(0), window_sets(0), window_accesses(0), window_get_misses(0), skips(0), sampling(_sampling) {} #ifdef USE_ADAPTIVE_SAMPLER AdaptiveSampler get_sampler; AdaptiveSampler set_sampler; + AdaptiveSampler get_l1_sampler; + AdaptiveSampler set_l1_sampler; + AdaptiveSampler get_l2_sampler; + AdaptiveSampler set_l2_sampler; AdaptiveSampler access_sampler; AdaptiveSampler op_sampler; #elif defined(USE_HISTOGRAM_SAMPLER) HistogramSampler get_sampler; HistogramSampler set_sampler; + HistogramSampler get_l1_sampler; + HistogramSampler get_l2_sampler; + HistogramSampler set_l1_sampler; + HistogramSampler set_l2_sampler; HistogramSampler access_sampler; HistogramSampler op_sampler; #else LogHistogramSampler get_sampler; LogHistogramSampler set_sampler; + LogHistogramSampler get_l1_sampler; + LogHistogramSampler set_l1_sampler; + LogHistogramSampler get_l2_sampler; + LogHistogramSampler set_l2_sampler; LogHistogramSampler access_sampler; LogHistogramSampler op_sampler; #endif uint64_t rx_bytes, tx_bytes; - uint64_t gets, sets, accesses, get_misses; + uint64_t gets, sets; + uint64_t gets_l1, sets_l1, gets_l2, sets_l2; + uint64_t accesses, get_misses; + uint64_t get_misses_l1, get_misses_l2; + uint64_t excl_wbs, incl_wbs; + uint64_t copies_to_l1; uint64_t window_gets, window_sets, window_accesses, window_get_misses; uint64_t skips; @@ -60,12 +94,18 @@ class ConnectionStats { void log_get(Operation& op) { if (sampling) get_sampler.sample(op); window_gets++; gets++; } void log_set(Operation& op) { if (sampling) set_sampler.sample(op); window_sets++; sets++; } + + void log_get_l1(Operation& op) { if (sampling) get_l1_sampler.sample(op); window_gets++; gets_l1++; } + void log_set_l1(Operation& op) { if (sampling) set_l1_sampler.sample(op); window_sets++; sets_l1++; } + + void log_get_l2(Operation& op) { if (sampling) get_l2_sampler.sample(op); window_gets++; gets_l2++; } + void log_set_l2(Operation& op) { if (sampling) set_l2_sampler.sample(op); window_sets++; sets_l2++; } void log_access(Operation& op) { //if (sampling) access_sampler.sample(op); - window_accesses++; accesses++; } + window_accesses++; } //accesses++; } void log_op (double op) { if (sampling) op_sampler.sample(op); } double get_qps() { - return (gets + sets) / (stop - start); + return (gets_l1 + gets_l2 + sets_l1 + sets_l2) / (stop - start); } #ifdef USE_ADAPTIVE_SAMPLER @@ -76,8 +116,16 @@ class ConnectionStats { for (auto s: get_sampler.samples) samples.push_back(s.time()); // (s.end_time - s.start_time) * 1000000); + for (auto s: get_l1_sampler.samples) + samples.push_back(s.time()); // (s.end_time - s.start_time) * 1000000); + for (auto s: get_l2_sampler.samples) + samples.push_back(s.time()); // (s.end_time - s.start_time) * 1000000); for (auto s: set_sampler.samples) samples.push_back(s.time()); // (s.end_time - s.start_time) * 1000000); + for (auto s: set_l1_sampler.samples) + samples.push_back(s.time()); // (s.end_time - s.start_time) * 1000000); + for (auto s: set_l2_sampler.samples) + samples.push_back(s.time()); // (s.end_time - s.start_time) * 1000000); for (auto s: access_sampler.samples) samples.push_back(s.time()); // (s.end_time - s.start_time) * 1000000); @@ -100,12 +148,20 @@ class ConnectionStats { void accumulate(const ConnectionStats &cs) { #ifdef USE_ADAPTIVE_SAMPLER for (auto i: cs.get_sampler.samples) get_sampler.sample(i); //log_get(i); + for (auto i: cs.get_l1_sampler.samples) get_l1_sampler.sample(i); //log_get(i); + for (auto i: cs.get_l2_sampler.samples) get_l2_sampler.sample(i); //log_get(i); for (auto i: cs.set_sampler.samples) set_sampler.sample(i); //log_set(i); + for (auto i: cs.set_l1_sampler.samples) set_l1_sampler.sample(i); //log_set(i); + for (auto i: cs.set_l2_sampler.samples) set_l2_sampler.sample(i); //log_set(i); for (auto i: cs.access_sampler.samples) access_sampler.sample(i); //log_access(i); for (auto i: cs.op_sampler.samples) op_sampler.sample(i); //log_op(i); #else get_sampler.accumulate(cs.get_sampler); + get_l1_sampler.accumulate(cs.get_l1_sampler); + get_l2_sampler.accumulate(cs.get_l2_sampler); set_sampler.accumulate(cs.set_sampler); + set_l1_sampler.accumulate(cs.set_l1_sampler); + set_l2_sampler.accumulate(cs.set_l2_sampler); access_sampler.accumulate(cs.access_sampler); op_sampler.accumulate(cs.op_sampler); #endif @@ -114,8 +170,17 @@ class ConnectionStats { tx_bytes += cs.tx_bytes; gets += cs.gets; sets += cs.sets; + gets_l1 += cs.gets_l1; + gets_l2 += cs.gets_l2; + sets_l1 += cs.sets_l1; + sets_l2 += cs.sets_l2; accesses += cs.accesses; get_misses += cs.get_misses; + get_misses_l1 += cs.get_misses_l1; + get_misses_l2 += cs.get_misses_l2; + excl_wbs += cs.excl_wbs; + incl_wbs += cs.incl_wbs; + copies_to_l1 += cs.copies_to_l1; skips += cs.skips; start = cs.start; @@ -127,8 +192,17 @@ class ConnectionStats { tx_bytes += as.tx_bytes; gets += as.gets; sets += as.sets; + gets_l1 += as.gets_l1; + gets_l2 += as.gets_l2; + sets_l1 += as.sets_l1; + sets_l2 += as.sets_l2; accesses += as.accesses; get_misses += as.get_misses; + get_misses_l1 += as.get_misses_l1; + get_misses_l2 += as.get_misses_l2; + excl_wbs += as.excl_wbs; + incl_wbs += as.incl_wbs; + copies_to_l1 += as.copies_to_l1; skips += as.skips; start = as.start; diff --git a/Operation.h b/Operation.h index d6bf9d6..5f9c51c 100644 --- a/Operation.h +++ b/Operation.h @@ -18,9 +18,11 @@ class Operation { int valuelen; uint32_t opaque; + uint32_t l1opaque; + uint16_t clsid; uint8_t level; + uint8_t log; uint8_t incl; - uint16_t clsid; string key; double time() const { return (end_time - start_time) * 1000000; } diff --git a/SConstruct b/SConstruct index cce1aab..3a3ded2 100644 --- a/SConstruct +++ b/SConstruct @@ -6,11 +6,15 @@ env = Environment(ENV = os.environ) env['HAVE_POSIX_BARRIER'] = True -#env.Append(CPPPATH = ['/u/dbyrne99/local/include']) -#env.Append(CPATH = ['/u/dbyrne99/local/include']) -#env.Append(LIBPATH = ['/u/dbyrne99/local/lib']) -#env.Append(CFLAGS = '-std=c++11 -D_GNU_SOURCE -static-libsan -fsanitize=thread -I/u/dbyrne99/local/include' ) -#env.Append(CCFLAGS = '-std=c++11 -D_GNU_SOURCE -static-libsan -fsanitize=thread -I/u/dbyrne99/local/include' ) +#env['CC'] = 'clang' +#env['CXX'] = 'clang++' + +#env.Append(CPPPATH = ['/u/dbyrne99/local/include', '/usr/include']) +#env.Append(CPATH = ['/u/dbyrne99/local/include', '/usr/include']) +#env.Append(LIBPATH = ['/u/dbyrne99/local/lib', '/lib64/']) + +#env.Append(CFLAGS = '-std=c++11 -D_GNU_SOURCE -static-libsan -fsanitize=address -I/u/dbyrne99/local/include' ) +#env.Append(CCFLAGS = '-std=c++11 -D_GNU_SOURCE -static-libsan -fsanitize=address -I/u/dbyrne99/local/include' ) #if sys.platform == 'darwin': #env['CC'] = 'clang' #env['CXX'] = 'clang++' @@ -40,12 +44,18 @@ if not conf.CheckFunc('pthread_barrier_init'): env = conf.Finish() -#env.Append(CFLAGS = ' -O0 -Wall -g') -#env.Append(CPPFLAGS = ' -O0 -Wall -g') -#env.Append(CFLAGS = ' -O3 -Wall -g') -#env.Append(CPPFLAGS = ' -O3 -Wall -g') -env.Append(CFLAGS = ' -O2 -Wall -g') -env.Append(CPPFLAGS = ' -O2 -Wall -g') +env.Append(CFLAGS = ' -O0 -Wall -g') +env.Append(CPPFLAGS = ' -O0 -Wall -g') +#env.Append(CFLAGS = ' -O2 -Wall') +#env.Append(CPPFLAGS = ' -O2 -Wall') +#env.Append(LDFLAGS = '-fsantize=address') +#env.Append(CFLAGS = ' -O2 -Wall -g -fsantize=address') +#env.Append(CPPFLAGS = ' -O2 -Wall -g -fsanitize=address') +#env.Append(CFLAGS = ' -O0 -Wall -g -fsantize=address') +#env.Append(CPPFLAGS = ' -O0 -Wall -g -fsanitize=address') + +#env.Append(CFLAGS = '-g -std=c++11 -D_GNU_SOURCE -static-libsan -fsanitize=address -I/u/dbyrne99/local/include' ) +#env.Append(CCFLAGS = '-g -std=c++11 -D_GNU_SOURCE -static-libsan -fsanitize=address -I/u/dbyrne99/local/include' ) env.Command(['cmdline.cc', 'cmdline.h'], 'cmdline.ggo', 'gengetopt < $SOURCE') diff --git a/binary_protocol.h b/binary_protocol.h index bd89d7d..1698149 100644 --- a/binary_protocol.h +++ b/binary_protocol.h @@ -11,26 +11,20 @@ #define CMD_SASL 0x21 #define RESP_OK 0x00 +#define RESP_NOT_FOUND 0x01 #define RESP_SASL_ERR 0x20 -typedef struct __attribute__ ((__packed__)) { +typedef struct { uint8_t magic; uint8_t opcode; uint16_t key_len; - uint8_t extra_len; uint8_t data_type; - union { - uint16_t vbucket; // request use - uint16_t status; // response use - }; - + uint16_t status; // response use uint32_t body_len; uint32_t opaque; - uint64_t version; + uint64_t cas; - // Used for set only. - uint64_t extras; } binary_header_t; #endif /* BINARY_PROTOCOL_H */ diff --git a/cmdline.ggo b/cmdline.ggo index b0b2499..2f5b44d 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -13,6 +13,7 @@ text "\nBasic options:" option "server" s "Memcached server hostname[:port]. \ Repeat to specify multiple servers." string multiple option "unix_socket" - "Use UNIX socket instead of TCP." +option "inclusives" - "give a list of 1 == inclusive, 2 == exclusives for each class" string default="" option "binary" - "Use binary memcached protocol instead of ASCII." option "redis" - "Use Redis RESP protocol instead of memchached." option "getset" - "Use getset mode, in getset mode we first issue \ diff --git a/mutilate.cc b/mutilate.cc index 0513c6b..d41be70 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -51,6 +51,7 @@ using namespace std; using namespace moodycamel; +int max_n[3] = {0,0,0}; ifstream kvfile; pthread_mutex_t flock = PTHREAD_MUTEX_INITIALIZER; @@ -62,7 +63,7 @@ pthread_mutex_t *item_locks; int item_lock_hashpower = 13; gengetopt_args_info args; -char random_char[2 * 1024 * 1024]; // Buffer used to generate random values. +char random_char[4 * 1024 * 1024]; // Buffer used to generate random values. #ifdef HAVE_LIBZMQ vector agent_sockets; @@ -78,15 +79,15 @@ struct thread_data { #endif int id; //std::vector*> trace_queue; - std::vector*> trace_queue; - std::vector mutexes; + std::vector*> *trace_queue; + std::vector *mutexes; }; struct reader_data { //std::vector*> trace_queue; - std::vector*> trace_queue; - std::vector mutexes; - string trace_filename; + std::vector*> *trace_queue; + std::vector *mutexes; + string *trace_filename; int twitter_trace; }; @@ -110,7 +111,7 @@ void go(const vector &servers, options_t &options, //void do_mutilate(const vector &servers, options_t &options, // ConnectionStats &stats,std::vector*> trace_queue, bool master = true void do_mutilate(const vector &servers, options_t &options, - ConnectionStats &stats,std::vector*> trace_queue, std::vector mutexes, bool master = true + ConnectionStats &stats,std::vector*> *trace_queue, std::vector *mutexes, bool master = true #ifdef HAVE_LIBZMQ , zmq::socket_t* socket = NULL #endif @@ -633,7 +634,7 @@ int main(int argc, char **argv) { stats.print_stats("update", stats.set_sampler); stats.print_stats("op_q", stats.op_sampler); - int total = stats.gets + stats.sets; + int total = stats.gets_l1 + stats.gets_l2 + stats.sets_l1 + stats.sets_l2; printf("\nTotal QPS = %.1f (%d / %.1fs)\n", total / (stats.stop - stats.start), @@ -645,7 +646,20 @@ int main(int argc, char **argv) { printf("\n"); printf("Misses = %" PRIu64 " (%.1f%%)\n", stats.get_misses, - (double) stats.get_misses/stats.gets*100); + (double) stats.get_misses/(stats.gets+stats.sets)*100); + if (servers.size() == 2) { + printf("Misses (L1) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l1, + (double) stats.get_misses_l1/(stats.gets + stats.sets)*100); + printf("Misses (L2) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l2, + (double) stats.get_misses_l2/(stats.gets + stats.sets)*100); + printf("L2 Writes = %" PRIu64 " (%.1f%%)\n", stats.sets_l2, + (double) stats.sets_l2/(stats.gets+stats.sets)*100); + + printf("Incl WBs = %" PRIu64 " (%.1f%%)\n", stats.incl_wbs, + (double) stats.incl_wbs/(stats.gets+stats.sets)*100); + printf("Excl WBs = %" PRIu64 " (%.1f%%)\n", stats.excl_wbs, + (double) stats.excl_wbs/(stats.gets+stats.sets)*100); + } printf("Skipped TXs = %" PRIu64 " (%.1f%%)\n\n", stats.skips, (double) stats.skips / total * 100); @@ -698,14 +712,15 @@ void go(const vector& servers, options_t& options, #endif //std::vector*> trace_queue; // = (ConcurrentQueue**)malloc(sizeof(ConcurrentQueue) - std::vector*> trace_queue; // = (ConcurrentQueue**)malloc(sizeof(ConcurrentQueue) - std::vector mutexes; + std::vector*> *trace_queue = new std::vector*>(); + // = (ConcurrentQueue**)malloc(sizeof(ConcurrentQueue) + std::vector *mutexes = new std::vector(); for (int i = 0; i <= options.apps; i++) { //trace_queue.push_back(new ConcurrentQueue(2000000)); - trace_queue.push_back(new std::queue()); + trace_queue->push_back(new std::queue()); pthread_mutex_t *lock = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); *lock = PTHREAD_MUTEX_INITIALIZER; - mutexes.push_back(lock); + mutexes->push_back(lock); } pthread_mutex_init(&reader_l, NULL); pthread_cond_init(&reader_ready, NULL); @@ -717,7 +732,7 @@ void go(const vector& servers, options_t& options, rdata->twitter_trace = options.twitter_trace; pthread_t rtid; if (options.read_file) { - rdata->trace_filename = options.file_name; + rdata->trace_filename = new string(options.file_name); int error = 0; if ((error = pthread_create(&rtid, NULL,reader_thread,rdata)) != 0) { printf("reader thread failed to be created with error code %d\n", error); @@ -807,8 +822,12 @@ void go(const vector& servers, options_t& options, ConnectionStats *cs; if (pthread_join(pt[t], (void**) &cs)) DIE("pthread_join() failed"); stats.accumulate(*cs); + delete cs; } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"max issue buf n[%d]: %u\n",i,max_n[i]); + } //delete trace_queue; } else if (options.threads == 1) { @@ -933,12 +952,13 @@ static char *get_stream(ZSTD_DCtx* dctx, FILE *fin, size_t const buffInSize, voi void* reader_thread(void *arg) { struct reader_data *rdata = (struct reader_data *) arg; //std::vector*> trace_queue = (std::vector*>) rdata->trace_queue; - std::vector*> trace_queue = (std::vector*>) rdata->trace_queue; - std::vector mutexes = (std::vector) rdata->mutexes; + std::vector*> *trace_queue = (std::vector*>*) rdata->trace_queue; + std::vector *mutexes = (std::vector*) rdata->mutexes; int twitter_trace = rdata->twitter_trace; - if (hasEnding(rdata->trace_filename,".zst")) { + string fn = *(rdata->trace_filename); + if (hasEnding(fn,".zst")) { //init - const char *filename = rdata->trace_filename.c_str(); + const char *filename = fn.c_str(); FILE* const fin = fopen_orDie(filename, "rb"); size_t const buffInSize = ZSTD_DStreamInSize()*1000; void* const buffIn = malloc_orDie(buffInSize); @@ -964,7 +984,7 @@ void* reader_thread(void *arg) { int appid = 0; if (full_line.length() > 10) { - if (trace_queue.size() > 1) { + if (trace_queue->size() > 1) { stringstream ss(full_line); string rT; string rApp; @@ -979,7 +999,7 @@ void* reader_thread(void *arg) { getline( ss, rKeySize, ',' ); getline( ss, rvaluelen, ',' ); getline( ss, rApp, ',' ); - appid = stoi(rApp) % trace_queue.size(); + appid = (stoi(rApp)) % trace_queue->size(); } else { continue; } @@ -990,20 +1010,20 @@ void* reader_thread(void *arg) { if (n == 4) { getline( ss, rT, ','); getline( ss, rApp, ','); - appid = stoi(rApp); + appid = (stoi(rApp)) % trace_queue->size(); } else { continue; } } - if (appid < (int)trace_queue.size()) { - pthread_mutex_lock(mutexes[appid]); - trace_queue[appid]->push(full_line); - pthread_mutex_unlock(mutexes[appid]); + if (appid < (int)trace_queue->size()) { + //pthread_mutex_lock(mutexes[appid]); + trace_queue->at(appid)->push(full_line); + //pthread_mutex_unlock(mutexes[appid]); } } else { - pthread_mutex_lock(mutexes[appid]); - trace_queue[appid]->push(full_line); - pthread_mutex_unlock(mutexes[appid]); + //pthread_mutex_lock(mutexes[appid]); + trace_queue->at(appid)->push(full_line); + //pthread_mutex_unlock(mutexes[appid]); } } //bool res = trace_queue[appid]->try_enqueue(full_line); @@ -1026,20 +1046,21 @@ void* reader_thread(void *arg) { free(ftrace); trace = get_stream(dctx, fin, buffInSize, buffIn, buffOutSize, buffOut); } + + string eof = "EOF"; + for (int i = 0; i < 1000; i++) { + for (int j = 0; j < (int)trace_queue->size(); j++) { + //trace_queue[j]->enqueue(eof); + trace_queue->at(j)->push(eof); + } + } + pthread_mutex_lock(&reader_l); if (reader_not_ready) { reader_not_ready = 0; } pthread_mutex_unlock(&reader_l); pthread_cond_signal(&reader_ready); - - string eof = "EOF"; - for (int i = 0; i < 1000; i++) { - for (int j = 0; j < (int)trace_queue.size(); j++) { - //trace_queue[j]->enqueue(eof); - trace_queue[j]->push(eof); - } - } if (trace) { free(trace); } @@ -1087,7 +1108,7 @@ void* thread_main(void *arg) { } void do_mutilate(const vector& servers, options_t& options, - ConnectionStats& stats, vector*> trace_queue, vector mutexes, bool master + ConnectionStats& stats, vector*> *trace_queue, vector *mutexes, bool master #ifdef HAVE_LIBZMQ , zmq::socket_t* socket #endif @@ -1145,7 +1166,7 @@ void do_mutilate(const vector& servers, options_t& options, options.connections; srand(time(NULL)); - for (int c = 0; c < conns; c++) { + for (int c = 0; c <= conns; c++) { Connection* conn = new Connection(base, evdns, hostname, port, options, //NULL,//trace_queue, args.agentmode_given ? false : @@ -1169,8 +1190,9 @@ void do_mutilate(const vector& servers, options_t& options, sleep(d); } if (connected) { - conn->set_queue(trace_queue[conn->get_cid()]); - conn->set_lock(mutexes[conn->get_cid()]); + fprintf(stderr,"cid %d gets trace_queue\nfirst: %s",conn->get_cid(),trace_queue->at(conn->get_cid())->front().c_str()); + conn->set_queue(trace_queue->at(conn->get_cid())); + conn->set_lock(mutexes->at(conn->get_cid())); connections.push_back(conn); } else { fprintf(stderr,"conn: %d, not connected!!\n",c); @@ -1379,10 +1401,6 @@ void do_mutilate(const vector& servers, options_t& options, } else if (servers.size() == 2) { vector connections; vector server_lead; - struct event_base *base2; - - if ((base2 = event_base_new_with_config(config)) == NULL) - DIE("event_base_new() fail"); string hostname1 = servers[0]; string hostname2 = servers[1]; @@ -1393,7 +1411,7 @@ void do_mutilate(const vector& servers, options_t& options, srand(time(NULL)); for (int c = 0; c < conns; c++) { - ConnectionMulti* conn = new ConnectionMulti(base, base2, evdns, + ConnectionMulti* conn = new ConnectionMulti(base, evdns, hostname1, hostname2, port, options,args.agentmode_given ? false : true); int tries = 120; int connected = 0; @@ -1410,9 +1428,11 @@ void do_mutilate(const vector& servers, options_t& options, int d = s + rand() % 100; sleep(d); } + int cid = conn->get_cid(); if (connected) { - conn->set_queue(trace_queue[conn->get_cid()]); - conn->set_lock(mutexes[conn->get_cid()]); + fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front().c_str()); + conn->set_queue(trace_queue->at(cid)); + conn->set_lock(mutexes->at(cid)); connections.push_back(conn); } else { fprintf(stderr,"conn multi: %d, not connected!!\n",c); @@ -1471,7 +1491,6 @@ void do_mutilate(const vector& servers, options_t& options, while (1) { event_base_loop(base, loop_flag); - event_base_loop(base2, loop_flag); //#ifdef USE_CLOCK_GETTIME // now = get_time(); @@ -1503,7 +1522,6 @@ void do_mutilate(const vector& servers, options_t& options, // deadlock. We should check for IDLE before calling // event_base_loop. event_base_loop(base, EVLOOP_ONCE); // EVLOOP_NONBLOCK); - event_base_loop(base2, EVLOOP_ONCE); // EVLOOP_NONBLOCK); bool restart = false; for (ConnectionMulti *conn: connections) @@ -1556,11 +1574,11 @@ void do_mutilate(const vector& servers, options_t& options, } // V("Start = %f", start); + fprintf(stderr,"Start = %f", start); // Main event loop. while (1) { event_base_loop(base, loop_flag); - event_base_loop(base2, loop_flag); //#if USE_CLOCK_GETTIME // now = get_time(); @@ -1594,7 +1612,6 @@ void do_mutilate(const vector& servers, options_t& options, event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); - event_base_free(base2); } } @@ -1607,6 +1624,10 @@ void args_to_options(options_t* options) { options->server_given = args.server_given; options->roundrobin = args.roundrobin_given; options->apps = args.apps_arg; + if (args.inclusives_given) { + memset(options->inclusives,0,256); + strncpy(options->inclusives,args.inclusives_arg,256); + } int connections = options->connections; if (options->roundrobin) { From ae497c9f03b917610f3caa803a248fab9e3c0b62 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Thu, 16 Sep 2021 12:54:18 -0400 Subject: [PATCH 40/57] before big update --- Connection.h | 2 +- ConnectionMulti.cc | 379 +++++++++++++++++++++++++++++++++++---------- SConstruct | 22 +-- mutilate.cc | 8 +- 4 files changed, 312 insertions(+), 99 deletions(-) diff --git a/Connection.h b/Connection.h index 1d17d1b..1e83eb4 100644 --- a/Connection.h +++ b/Connection.h @@ -27,7 +27,7 @@ #define hashsize(n) ((unsigned long int)1<<(n)) #define hashmask(n) (hashsize(n)-1) -#define MAX_BUFFER_SIZE 8*1024*1024 +#define MAX_BUFFER_SIZE 10*1024*1024 using namespace std; using namespace moodycamel; diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 97f0321..2750c13 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -47,8 +47,8 @@ if (flags & ITEM_INCL) incl = 1; \ else if (flags & ITEM_EXCL) incl = 2; \ -#define DEBUGMC -#define DEBUGS +//#define DEBUGMC +//#define DEBUGS using namespace moodycamel; @@ -231,7 +231,7 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _e issue_buf[i] = (unsigned char*)malloc(sizeof(unsigned char)*MAX_BUFFER_SIZE); memset(issue_buf[i],0,MAX_BUFFER_SIZE); //op_queue[i] = (Operation*)malloc(sizeof(int)*OPAQUE_MAX); - op_queue[i] = (Operation**)malloc(sizeof(Operation*)*OPAQUE_MAX); + op_queue[i] = (Operation**)malloc(sizeof(Operation*)*(OPAQUE_MAX*2)); issue_buf_pos[i] = issue_buf[i]; issue_buf_size[i] = 0; issue_buf_n[i] = 0; @@ -261,11 +261,11 @@ int ConnectionMulti::do_connect() { if (options.unix_socket) { bev1 = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); - bufferevent_setcb(bev1, bev_read_cb1, bev_write_cb, bev_event_cb1, this); + bufferevent_setcb(bev1, bev_read_cb1, NULL, bev_event_cb1, this); bufferevent_enable(bev1, EV_READ | EV_WRITE); bev2 = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); - bufferevent_setcb(bev2, bev_read_cb2, bev_write_cb, bev_event_cb2, this); + bufferevent_setcb(bev2, bev_read_cb2, NULL, bev_event_cb2, this); bufferevent_enable(bev2, EV_READ | EV_WRITE); struct sockaddr_un sin1; @@ -309,6 +309,20 @@ int ConnectionMulti::do_connect() { */ ConnectionMulti::~ConnectionMulti() { + + for (int i = 0; i <= LEVELS; i++) { + free(issue_buf[i]); + free(op_queue[i]); + + } + + free(op_queue_size); + free(opaque); + free(op_queue); + free(issue_buf_n); + free(issue_buf_size); + free(issue_buf); + free(issue_buf_pos); event_free(timer); timer = NULL; // FIXME: W("Drain op_q?"); @@ -405,6 +419,9 @@ int ConnectionMulti::issue_getsetorset(double now) { getline( ss, rvaluelen, ',' ); Op = stoi(rOp); vl = stoi(rvaluelen); + if (vl < 1 || vl > 524000) { + fprintf(stderr,"bad val for line: %s, %d\n",line.c_str(),vl); + } } else { getline( ss, rT, ',' ); getline( ss, rApp, ',' ); @@ -485,15 +502,52 @@ int ConnectionMulti::issue_getsetorset(double now) { //fprintf(stderr,"\n-------------------------------------\n"); free(output); #endif + //if (issue_buf_n[1] > 500) { + // fprintf(stderr,"issue_buf_n[%d] too big %d (getsetorset)\n",1,issue_buf_n[1]); + //for (int i = 1; i <= 2; i++) { + // fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + //} + //for (int i = 1; i <= 2; i++) { + // fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + //} + //for (int i = 1; i <= 2; i++) { + // fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + //} + // + //} //buffer is ready to go! - bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); + //bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); + struct evbuffer* out1 = bufferevent_get_output(bev1); + if (evbuffer_expand(out1,issue_buf_size[1])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + + } + if (evbuffer_add(out1, issue_buf[1], issue_buf_size[1])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + } memset(issue_buf[1],0,issue_buf_size[1]); issue_buf_pos[1] = issue_buf[1]; issue_buf_size[1] = 0; issue_buf_n[1] = 0; - if (issue_buf_n[2] >= options.depth-2) { + if (issue_buf_n[2] >= 4) { if (last_quiet2) { issue_noop(now,2); last_quiet2 = false; @@ -508,7 +562,32 @@ int ConnectionMulti::issue_getsetorset(double now) { free(output); #endif //buffer is ready to go! - bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); + //bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); + + struct evbuffer* out2 = bufferevent_get_output(bev2); + if (evbuffer_expand(out2,issue_buf_size[2])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + + } + if (evbuffer_add(out2, issue_buf[2], issue_buf_size[2])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + } memset(issue_buf[2],0,issue_buf_size[2]); issue_buf_pos[2] = issue_buf[2]; @@ -527,34 +606,33 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no int level, int flags, uint32_t l1opaque, uint8_t log) { //Operation op; Operation *pop = new Operation(); - Operation op = *pop; #if HAVE_CLOCK_GETTIME - op.start_time = get_time_accurate(); + pop->start_time = get_time_accurate(); #else if (now == 0.0) { #if USE_CACHED_TIME struct timeval now_tv; event_base_gettimeofday_cached(base, &now_tv); - op.start_time = tv_to_double(&now_tv); + pop->start_time = tv_to_double(&now_tv); #else - op.start_time = get_time(); + pop->start_time = get_time(); #endif } else { - op.start_time = now; + pop->start_time = now; } #endif - op.key = string(key); - op.valuelen = valuelen; - op.type = Operation::GET; - op.opaque = opaque[level]++; - op.level = level; - op.l1opaque = l1opaque; - op.log = log; - GET_INCL(op.incl,flags); - op.clsid = get_class(valuelen,strlen(key)); - op_queue[level][op.opaque] = pop; + pop->key = string(key); + pop->valuelen = valuelen; + pop->type = Operation::GET; + pop->opaque = opaque[level]++; + pop->level = level; + pop->l1opaque = l1opaque; + pop->log = log; + GET_INCL(pop->incl,flags); + pop->clsid = get_class(valuelen,strlen(key)); + op_queue[level][pop->opaque] = pop; //op_queue[level].push(op); op_queue_size[level]++; @@ -572,7 +650,7 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no if (quiet) { h.opcode = CMD_GETQ; } - h.opaque = htonl(op.opaque); + h.opaque = htonl(pop->opaque); memcpy(issue_buf_pos[level],&h,24); issue_buf_pos[level] += 24; @@ -581,6 +659,19 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no issue_buf_pos[level] += keylen; issue_buf_size[level] += keylen; issue_buf_n[level]++; + if (issue_buf_n[level] > 500) { + fprintf(stderr,"issue_buf_n[%d] too big %d (get_with_len)\n",level,issue_buf_n[level]); + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + } + #ifdef DEBUGS for (int i = 1; i <= 2; i++) { fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); @@ -600,30 +691,29 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no */ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int level) { Operation *pop = new Operation(); - Operation op = *pop; #if HAVE_CLOCK_GETTIME - op.start_time = get_time_accurate(); + pop->start_time = get_time_accurate(); #else if (now == 0.0) { #if USE_CACHED_TIME struct timeval now_tv; event_base_gettimeofday_cached(base, &now_tv); - op.start_time = tv_to_double(&now_tv); + pop->start_time = tv_to_double(&now_tv); #else - op.start_time = get_time(); + pop->start_time = get_time(); #endif } else { - op.start_time = now; + pop->start_time = now; } #endif - op.key = string(key); - op.valuelen = valuelen; - op.type = Operation::TOUCH; - op.opaque = opaque[level]++; - op.level = level; - op_queue[level][op.opaque] = pop; + pop->key = string(key); + pop->valuelen = valuelen; + pop->type = Operation::TOUCH; + pop->opaque = opaque[level]++; + pop->level = level; + op_queue[level][pop->opaque] = pop; //op_queue[level].push(op); op_queue_size[level]++; @@ -638,7 +728,7 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int binary_header_t h = { 0x80, CMD_TOUCH, htons(keylen), 0x04, 0x00, {htons(0)}, htonl(keylen + 4) }; - h.opaque = htonl(op.opaque); + h.opaque = htonl(pop->opaque); memcpy(issue_buf_pos[level],&h,24); issue_buf_pos[level] += 24; @@ -668,29 +758,28 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int int ConnectionMulti::issue_delete(const char* key, double now, int level) { //Operation op; Operation *pop = new Operation(); - Operation op = *pop; #if HAVE_CLOCK_GETTIME - op.start_time = get_time_accurate(); + pop->start_time = get_time_accurate(); #else if (now == 0.0) { #if USE_CACHED_TIME struct timeval now_tv; event_base_gettimeofday_cached(base, &now_tv); - op.start_time = tv_to_double(&now_tv); + pop->start_time = tv_to_double(&now_tv); #else - op.start_time = get_time(); + pop->start_time = get_time(); #endif } else { - op.start_time = now; + pop->start_time = now; } #endif - op.key = string(key); - op.type = Operation::DELETE; - op.opaque = opaque[level]++; - op.level = level; - op_queue[level][op.opaque] = pop; + pop->key = string(key); + pop->type = Operation::DELETE; + pop->opaque = opaque[level]++; + pop->level = level; + op_queue[level][pop->opaque] = pop; //op_queue[level].push(op); op_queue_size[level]++; @@ -705,7 +794,7 @@ int ConnectionMulti::issue_delete(const char* key, double now, int level) { binary_header_t h = { 0x80, CMD_DELETE, htons(keylen), 0x00, 0x00, {htons(0)}, htonl(keylen) }; - h.opaque = htonl(op.opaque); + h.opaque = htonl(pop->opaque); memcpy(issue_buf_pos[level],&h,24); issue_buf_pos[level] += 24; @@ -719,7 +808,7 @@ int ConnectionMulti::issue_delete(const char* key, double now, int level) { stats.tx_bytes += 24 + keylen; } - stats.log_access(op); + //stats.log_access(op); return 1; } @@ -746,27 +835,26 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, double now, int level, int flags, uint32_t l1opaque, uint8_t log) { //Operation op; Operation *pop = new Operation(); - Operation op = *pop; #if HAVE_CLOCK_GETTIME - op.start_time = get_time_accurate(); + pop->start_time = get_time_accurate(); #else - if (now == 0.0) op.start_time = get_time(); - else op.start_time = now; + if (now == 0.0) pop->start_time = get_time(); + else pop->start_time = now; #endif - op.key = string(key); - op.valuelen = length; - op.type = Operation::SET; - op.opaque = opaque[level]++; - op.level = level; - op.incl = 0; - op.log = log; - op.l1opaque = l1opaque; - GET_INCL(op.incl,flags); - op.clsid = get_class(length,strlen(key)); - op_queue[level][op.opaque] = pop; + pop->key = string(key); + pop->valuelen = length; + pop->type = Operation::SET; + pop->opaque = opaque[level]++; + pop->level = level; + pop->incl = 0; + pop->log = log; + pop->l1opaque = l1opaque; + GET_INCL(pop->incl,flags); + pop->clsid = get_class(length,strlen(key)); + op_queue[level][pop->opaque] = pop; //op_queue[level].push(op); op_queue_size[level]++; #ifdef DEBUGS @@ -783,7 +871,7 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, binary_header_t h = { 0x80, CMD_SET, htons(keylen), 0x08, 0x00, {htons(0)}, htonl(keylen + 8 + length) }; - h.opaque = htonl(op.opaque); + h.opaque = htonl(pop->opaque); memcpy(issue_buf_pos[level],&h,24); issue_buf_pos[level] += 24; @@ -823,6 +911,19 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, issue_buf_size[level] += length; issue_buf_n[level]++; + if (issue_buf_n[level] > 500) { + fprintf(stderr,"issue_buf_n[%d] too big %d (set: %d)\n",level,issue_buf_n[level],log); + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + } + #ifdef DEBUGS for (int i = 1; i <= 2; i++) { fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); @@ -1029,10 +1130,10 @@ void ConnectionMulti::event_callback1(short events) { int err = bufferevent_socket_get_dns_error(bev1); //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); - fprintf(stderr,"Got an error: %s\n", + fprintf(stderr,"CID: %d - Got an error: %s\n",this->cid, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); - DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + //DIE("BEV_EVENT_ERROR: %s", strerror(errno)); } else if (events & BEV_EVENT_EOF) { fprintf(stderr,"Unexpected EOF from server."); @@ -1064,10 +1165,11 @@ void ConnectionMulti::event_callback2(short events) { int err = bufferevent_socket_get_dns_error(bev2); //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); - fprintf(stderr,"Got an error: %s\n", + fprintf(stderr,"CID: %d - Got an error: %s\n",this->cid, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); - DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + //DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + } else if (events & BEV_EVENT_EOF) { fprintf(stderr,"Unexpected EOF from server."); @@ -1133,8 +1235,9 @@ void ConnectionMulti::drive_write_machine(double now) { next_time += iagen->generate(); if (options.skip && options.lambda > 0.0 && - now - next_time > 0.005000 ) { - //op_queue.size() >= (size_t) options.depth) { + now - next_time > 0.005000 && ( + op_queue_size[1] >= (size_t) options.depth || + op_queue_size[2] >= (size_t) options.depth)) { while (next_time < now - 0.004000) { stats.skips++; @@ -1323,6 +1426,11 @@ void ConnectionMulti::read_callback1() { continue; } } else { + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } break; } @@ -1342,9 +1450,9 @@ void ConnectionMulti::read_callback1() { } else { if (found) { - if (op->incl == 1) { - issue_touch(op->key.c_str(),op->valuelen,now,2); - } + //if (op->incl == 1) { + // issue_touch(op->key.c_str(),op->valuelen,now,2); + //} finish_op(op,1); } else { finish_op(op,0); @@ -1357,9 +1465,9 @@ void ConnectionMulti::read_callback1() { } break; case Operation::SET: - if (op->incl == 1) { - issue_touch(op->key.c_str(),op->valuelen,0,2); - } + //if (op->incl == 1) { + // issue_touch(op->key.c_str(),op->valuelen,0,2); + //} if (evict->evicted) { if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, 2, ITEM_INCL | ITEM_DIRTY, 0, 1); @@ -1383,6 +1491,7 @@ void ConnectionMulti::read_callback1() { } } + double now = get_time(); if (check_exit_condition(now)) { @@ -1403,7 +1512,7 @@ void ConnectionMulti::read_callback1() { //} #endif //buffer is ready to go! - if (issue_buf_n[1] > 0) { + if (issue_buf_n[1] >= 4) { if (last_quiet1) { issue_noop(now,1); last_quiet1 = false; @@ -1418,14 +1527,41 @@ void ConnectionMulti::read_callback1() { free(output); #endif - bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); + //bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); + + struct evbuffer* out1 = bufferevent_get_output(bev1); + if (evbuffer_expand(out1,issue_buf_size[1])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + + } + if (evbuffer_add(out1, issue_buf[1], issue_buf_size[1])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + } + + memset(issue_buf[1],0,issue_buf_size[1]); issue_buf_pos[1] = issue_buf[1]; issue_buf_size[1] = 0; issue_buf_n[1] = 0; } - if (issue_buf_n[2] > 0) { + if (issue_buf_n[2] >= 4) { if (last_quiet2) { issue_noop(now,2); last_quiet2 = false; @@ -1440,7 +1576,32 @@ void ConnectionMulti::read_callback1() { free(output); #endif - bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); + //bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); + + struct evbuffer* out2 = bufferevent_get_output(bev2); + if (evbuffer_expand(out2,issue_buf_size[2])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + + } + if (evbuffer_add(out2, issue_buf[2], issue_buf_size[2])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + } memset(issue_buf[2],0,issue_buf_size[2]); issue_buf_pos[2] = issue_buf[2]; issue_buf_size[2] = 0; @@ -1618,7 +1779,7 @@ void ConnectionMulti::read_callback2() { //} #endif //buffer is ready to go! - if (issue_buf_n[2] > 0) { + if (issue_buf_n[2] >= 4) { if (last_quiet2) { issue_noop(now,2); last_quiet2 = false; @@ -1633,14 +1794,39 @@ void ConnectionMulti::read_callback2() { free(output); #endif - bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); + //bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); + + struct evbuffer* out2 = bufferevent_get_output(bev2); + if (evbuffer_expand(out2,issue_buf_size[2])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + + } + if (evbuffer_add(out2, issue_buf[2], issue_buf_size[2])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + } memset(issue_buf[2],0,issue_buf_size[2]); issue_buf_pos[2] = issue_buf[2]; issue_buf_size[2] = 0; issue_buf_n[2] = 0; } //buffer is ready to go! - if (issue_buf_n[1] > 0) { + if (issue_buf_n[1] >= 4) { if (last_quiet1) { issue_noop(now,1); last_quiet1 = false; @@ -1655,7 +1841,32 @@ void ConnectionMulti::read_callback2() { free(output); #endif - bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); + //bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); + + struct evbuffer* out1 = bufferevent_get_output(bev1); + if (evbuffer_expand(out1,issue_buf_size[1])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + + } + if (evbuffer_add(out1, issue_buf[1], issue_buf_size[1])) { + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); + } + for (int i = 1; i <= 2; i++) { + fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); + } + } memset(issue_buf[1],0,issue_buf_size[1]); issue_buf_pos[1] = issue_buf[1]; issue_buf_size[1] = 0; diff --git a/SConstruct b/SConstruct index 3a3ded2..c6b5cef 100644 --- a/SConstruct +++ b/SConstruct @@ -29,10 +29,10 @@ if env.Execute("@which gengetopt &> /dev/null"): print "not found (required)" Exit(1) else: print "found" -if not conf.CheckLibWithHeader("event", "event2/event.h", "C++"): - print "libevent required" - Exit(1) -conf.CheckDeclaration("EVENT_BASE_FLAG_PRECISE_TIMER", '#include ', "C++") +#if not conf.CheckLibWithHeader("event", "event2/event.h", "C++"): +# print "libevent required" +# Exit(1) +#conf.CheckDeclaration("EVENT_BASE_FLAG_PRECISE_TIMER", '#include ', "C++") if not conf.CheckLibWithHeader("pthread", "pthread.h", "C++"): print "pthread required" Exit(1) @@ -44,13 +44,13 @@ if not conf.CheckFunc('pthread_barrier_init'): env = conf.Finish() -env.Append(CFLAGS = ' -O0 -Wall -g') -env.Append(CPPFLAGS = ' -O0 -Wall -g') -#env.Append(CFLAGS = ' -O2 -Wall') -#env.Append(CPPFLAGS = ' -O2 -Wall') +#env.Append(CFLAGS = ' -O0 -Wall -g') +#env.Append(CPPFLAGS = ' -O0 -Wall -g') +env.Append(CFLAGS = ' -O3 -Wall -g') +env.Append(CPPFLAGS = ' -O3 -Wall -g') #env.Append(LDFLAGS = '-fsantize=address') -#env.Append(CFLAGS = ' -O2 -Wall -g -fsantize=address') -#env.Append(CPPFLAGS = ' -O2 -Wall -g -fsanitize=address') +#env.Append(CFLAGS = ' -O3 -Wall -g -fsantize=address') +#env.Append(CPPFLAGS = ' -O3 -Wall -g -fsanitize=address') #env.Append(CFLAGS = ' -O0 -Wall -g -fsantize=address') #env.Append(CPPFLAGS = ' -O0 -Wall -g -fsanitize=address') @@ -65,7 +65,7 @@ src = Split("""mutilate.cc cmdline.cc log.cc distributions.cc util.cc if not env['HAVE_POSIX_BARRIER']: # USE_POSIX_BARRIER: src += ['barrier.cc'] -src += ['libzstd.a'] +src += ['libzstd.a', '/u/dbyrne99/local/lib/libevent.a'] env.Program(target='mutilate', source=src) #env.Program(target='gtest', source=['TestGenerator.cc', 'log.cc', 'util.cc', # 'Generator.cc']) diff --git a/mutilate.cc b/mutilate.cc index d41be70..c3c2b12 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -486,7 +486,7 @@ int main(int argc, char **argv) { // struct event_base *base; // if ((base = event_base_new()) == NULL) DIE("event_base_new() fail"); - // evthread_use_pthreads(); + //evthread_use_pthreads(); // if ((evdns = evdns_base_new(base, 1)) == 0) DIE("evdns"); @@ -1121,6 +1121,8 @@ void do_mutilate(const vector& servers, options_t& options, struct event_base *base; struct evdns_base *evdns; struct event_config *config; + //event_enable_debug_mode(); + if ((config = event_config_new()) == NULL) DIE("event_config_new() fail"); @@ -1132,7 +1134,7 @@ void do_mutilate(const vector& servers, options_t& options, if ((base = event_base_new_with_config(config)) == NULL) DIE("event_base_new() fail"); - // evthread_use_pthreads(); + //evthread_use_pthreads(); if ((evdns = evdns_base_new(base, 1)) == 0) DIE("evdns"); @@ -1425,7 +1427,7 @@ void do_mutilate(const vector& servers, options_t& options, fprintf(stderr,"thread %lu, multi conn: %d, connected!\n",pthread_self(),c+1); break; } - int d = s + rand() % 100; + int d = s + rand() % 10; sleep(d); } int cid = conn->get_cid(); From 717befafb958bda6a9597362afeff124ba8ad09f Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Fri, 17 Sep 2021 16:18:13 -0400 Subject: [PATCH 41/57] before big flags update --- AgentStats.h | 1 + Connection.h | 2 +- ConnectionMulti.cc | 631 ++++++++++----------------------------------- ConnectionStats.h | 6 + Operation.h | 4 +- mutilate.cc | 10 +- 6 files changed, 148 insertions(+), 506 deletions(-) diff --git a/AgentStats.h b/AgentStats.h index 051dc4e..2c23711 100644 --- a/AgentStats.h +++ b/AgentStats.h @@ -8,6 +8,7 @@ class AgentStats { uint64_t gets, sets, accesses, get_misses; uint64_t gets_l1, gets_l2, sets_l1, sets_l2; uint64_t get_misses_l1, get_misses_l2; + uint64_t set_misses_l1, set_misses_l2; uint64_t excl_wbs, incl_wbs; uint64_t copies_to_l1; uint64_t skips; diff --git a/Connection.h b/Connection.h index 1e83eb4..fdab077 100644 --- a/Connection.h +++ b/Connection.h @@ -288,7 +288,7 @@ class ConnectionMulti { void issue_sasl(); void issue_noop(double now = 0.0, int level = 1); int issue_touch(const char* key, int valuelen, double now = 0.0, int level = 1); - int issue_delete(const char* key, double now = 0.0, int level = 1); + int issue_delete(const char* key, double now = 0.0, int level = 1, int log = 1); int issue_get_with_len(const char* key, int valuelen, double now = 0.0, bool quiet = false, int level = 1, int flags = 0, uint32_t l1opaque = 0, uint8_t log = 1); int issue_set(const char* key, const char* value, int length, double now = 0.0, int level = 1, int flags = 0, uint32_t l1opaque = 0, uint8_t log = 1); diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 2750c13..bddba55 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -27,9 +27,19 @@ #include #include "blockingconcurrentqueue.h" -#define ITEM_DIRTY 16384 +#define ITEM_L1 1 +#define ITEM_L2 2 +#define LOG_OP 4 +#define SRC_L1_M 8 +#define SRC_L1_H 16 +#define SRC_L2_M 32 +#define SRC_L2_H 64 +#define SRC_DIRECT_SET 128 +#define SRC_L1_COPY 256 + #define ITEM_INCL 4096 #define ITEM_EXCL 8192 +#define ITEM_DIRTY 16384 #define LEVELS 2 #define SET_INCL(incl,flags) \ @@ -47,6 +57,18 @@ if (flags & ITEM_INCL) incl = 1; \ else if (flags & ITEM_EXCL) incl = 2; \ +//#define OP_level(op) ( ((op)->flags & ITEM_L1) ? ITEM_L1 : ITEM_L2 ) +#define OP_level(op) ( (op)->flags ~& (LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H \ + SRC_DIRECT_SET | SRC_L1_COPY ) ) +#define OP_src(op) ( (op)->flags ~& (ITEM_L1 | ITEM_L2 | LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY ) ) +#define OP_log(op) ((op)->flags & LOG_OP) +#define OP_incl(op) ((op)->flags & ITEM_INCL) +#define OP_excl(op) ((op)->flags & ITEM_EXCL) +#define OP_set_flag(op,flag) ((op))->flags |= flag; + //#define DEBUGMC //#define DEBUGS @@ -219,22 +241,12 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _e op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); op_queue = (Operation***)malloc(sizeof(Operation**)*(LEVELS+1)); - issue_buf_n = (int*)malloc(sizeof(int)*(LEVELS+1)); - issue_buf_size = (int*)malloc(sizeof(int)*(LEVELS+1)); - issue_buf = (unsigned char**)malloc(sizeof(unsigned char*)*(LEVELS+1)); - issue_buf_pos = (unsigned char**)malloc(sizeof(unsigned char*)*(LEVELS+1)); for (int i = 0; i <= LEVELS; i++) { op_queue_size[i] = 0; opaque[i] = 1; - - issue_buf[i] = (unsigned char*)malloc(sizeof(unsigned char)*MAX_BUFFER_SIZE); - memset(issue_buf[i],0,MAX_BUFFER_SIZE); //op_queue[i] = (Operation*)malloc(sizeof(int)*OPAQUE_MAX); op_queue[i] = (Operation**)malloc(sizeof(Operation*)*(OPAQUE_MAX*2)); - issue_buf_pos[i] = issue_buf[i]; - issue_buf_size[i] = 0; - issue_buf_n[i] = 0; } @@ -311,7 +323,6 @@ ConnectionMulti::~ConnectionMulti() { for (int i = 0; i <= LEVELS; i++) { - free(issue_buf[i]); free(op_queue[i]); } @@ -319,10 +330,6 @@ ConnectionMulti::~ConnectionMulti() { free(op_queue_size); free(opaque); free(op_queue); - free(issue_buf_n); - free(issue_buf_size); - free(issue_buf); - free(issue_buf_pos); event_free(timer); timer = NULL; // FIXME: W("Drain op_q?"); @@ -493,107 +500,6 @@ int ConnectionMulti::issue_getsetorset(double now) { issue_noop(now,1); last_quiet1 = false; } -#ifdef DEBUGMC - fprintf(stderr,"getsetorset issuing to l1 %d reqs last quiet %d\n",issue_buf_n[1],last_quiet1); - char *output = (char*)malloc(sizeof(char)*(issue_buf_size[1]+512)); - //fprintf(stderr,"-------------------------------------\n"); - //memcpy(output,issue_buf[1],issue_buf_size[1]); - //write(2,output,issue_buf_size[1]); - //fprintf(stderr,"\n-------------------------------------\n"); - free(output); -#endif - //if (issue_buf_n[1] > 500) { - // fprintf(stderr,"issue_buf_n[%d] too big %d (getsetorset)\n",1,issue_buf_n[1]); - //for (int i = 1; i <= 2; i++) { - // fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - //} - //for (int i = 1; i <= 2; i++) { - // fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - //} - //for (int i = 1; i <= 2; i++) { - // fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - //} - // - //} - //buffer is ready to go! - //bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); - struct evbuffer* out1 = bufferevent_get_output(bev1); - if (evbuffer_expand(out1,issue_buf_size[1])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - - } - if (evbuffer_add(out1, issue_buf[1], issue_buf_size[1])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - } - - memset(issue_buf[1],0,issue_buf_size[1]); - issue_buf_pos[1] = issue_buf[1]; - issue_buf_size[1] = 0; - issue_buf_n[1] = 0; - - if (issue_buf_n[2] >= 4) { - if (last_quiet2) { - issue_noop(now,2); - last_quiet2 = false; - } -#ifdef DEBUGMC - fprintf(stderr,"getsetorset issuing to l2 %d reqs last quiet %d\n",issue_buf_n[2],last_quiet2); - char *output = (char*)malloc(sizeof(char)*(issue_buf_size[2]+522)); - //fprintf(stderr,"-------------------------------------\n"); - //memcpy(output,issue_buf[2],issue_buf_size[2]); - //write(2,output,issue_buf_size[2]); - //fprintf(stderr,"\n-------------------------------------\n"); - free(output); -#endif - //buffer is ready to go! - //bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); - - struct evbuffer* out2 = bufferevent_get_output(bev2); - if (evbuffer_expand(out2,issue_buf_size[2])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - - } - if (evbuffer_add(out2, issue_buf[2], issue_buf_size[2])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - } - - memset(issue_buf[2],0,issue_buf_size[2]); - issue_buf_pos[2] = issue_buf[2]; - issue_buf_size[2] = 0; - issue_buf_n[2] = 0; - } return ret; @@ -604,6 +510,15 @@ int ConnectionMulti::issue_getsetorset(double now) { */ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, int level, int flags, uint32_t l1opaque, uint8_t log) { + struct evbuffer *output = NULL; + switch (level) { + case 1: + output = bufferevent_get_output(bev1); + break; + case 2: + output = bufferevent_get_output(bev2); + break; + } //Operation op; Operation *pop = new Operation(); @@ -651,38 +566,11 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no h.opcode = CMD_GETQ; } h.opaque = htonl(pop->opaque); - - memcpy(issue_buf_pos[level],&h,24); - issue_buf_pos[level] += 24; - issue_buf_size[level] += 24; - memcpy(issue_buf_pos[level],key,keylen); - issue_buf_pos[level] += keylen; - issue_buf_size[level] += keylen; - issue_buf_n[level]++; - if (issue_buf_n[level] > 500) { - fprintf(stderr,"issue_buf_n[%d] too big %d (get_with_len)\n",level,issue_buf_n[level]); - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - } - -#ifdef DEBUGS - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } -#endif - - if (read_state != LOADING) { - stats.tx_bytes += 24 + keylen; - } - //stats.log_access(op); + evbuffer_add(output, &h, 24); + evbuffer_add(output, key, keylen); + + stats.tx_bytes += 24 + keylen; return 1; } @@ -690,6 +578,15 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no * Issue a get request to the server. */ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int level) { + struct evbuffer *output = NULL; + switch (level) { + case 1: + output = bufferevent_get_output(bev1); + break; + case 2: + output = bufferevent_get_output(bev2); + break; + } Operation *pop = new Operation(); #if HAVE_CLOCK_GETTIME @@ -726,27 +623,17 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int // each line is 4-bytes binary_header_t h = { 0x80, CMD_TOUCH, htons(keylen), - 0x04, 0x00, {htons(0)}, + 0x04, 0x00, htons(0), htonl(keylen + 4) }; h.opaque = htonl(pop->opaque); - - memcpy(issue_buf_pos[level],&h,24); - issue_buf_pos[level] += 24; - issue_buf_size[level] += 24; uint32_t exp = 0; - memcpy(issue_buf_pos[level],&exp,4); - issue_buf_pos[level] += 4; - issue_buf_size[level] += 4; - - memcpy(issue_buf_pos[level],key,keylen); - issue_buf_pos[level] += keylen; - issue_buf_size[level] += keylen; - issue_buf_n[level]++; + evbuffer_add(output, &h, 24); + evbuffer_add(output, &exp, 4); + evbuffer_add(output, key, keylen); + - if (read_state != LOADING) { - stats.tx_bytes += 24 + keylen; - } + stats.tx_bytes += 24 + keylen; //stats.log_access(op); return 1; @@ -755,7 +642,16 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int /** * Issue a delete request to the server. */ -int ConnectionMulti::issue_delete(const char* key, double now, int level) { +int ConnectionMulti::issue_delete(const char* key, double now, int level, int log) { + struct evbuffer *output = NULL; + switch (level) { + case 1: + output = bufferevent_get_output(bev1); + break; + case 2: + output = bufferevent_get_output(bev2); + break; + } //Operation op; Operation *pop = new Operation(); @@ -779,6 +675,7 @@ int ConnectionMulti::issue_delete(const char* key, double now, int level) { pop->type = Operation::DELETE; pop->opaque = opaque[level]++; pop->level = level; + pop->log = log; op_queue[level][pop->opaque] = pop; //op_queue[level].push(op); op_queue_size[level]++; @@ -792,40 +689,40 @@ int ConnectionMulti::issue_delete(const char* key, double now, int level) { // each line is 4-bytes binary_header_t h = { 0x80, CMD_DELETE, htons(keylen), - 0x00, 0x00, {htons(0)}, + 0x00, 0x00, htons(0), htonl(keylen) }; h.opaque = htonl(pop->opaque); - - memcpy(issue_buf_pos[level],&h,24); - issue_buf_pos[level] += 24; - issue_buf_size[level] += 24; - memcpy(issue_buf_pos[level],key,keylen); - issue_buf_pos[level] += keylen; - issue_buf_size[level] += keylen; - issue_buf_n[level]++; - if (read_state != LOADING) { - stats.tx_bytes += 24 + keylen; - } + evbuffer_add(output, &h, 24); + evbuffer_add(output, key, keylen); + + stats.tx_bytes += 24 + keylen; //stats.log_access(op); return 1; } void ConnectionMulti::issue_noop(double now, int level) { - Operation op; - - if (now == 0.0) op.start_time = get_time(); - else op.start_time = now; + struct evbuffer *output = NULL; + switch (level) { + case 1: + output = bufferevent_get_output(bev1); + break; + case 2: + output = bufferevent_get_output(bev2); + break; + } + Operation op; + + if (now == 0.0) op.start_time = get_time(); + else op.start_time = now; - binary_header_t h = { 0x80, CMD_NOOP, 0x0000, - 0x00, 0x00, {htons(0)}, - 0x00 }; + binary_header_t h = { 0x80, CMD_NOOP, 0x0000, + 0x00, 0x00, {htons(0)}, + 0x00 }; + + evbuffer_add(output, &h, 24); - memcpy(issue_buf_pos[level],&h,24); - issue_buf_pos[level] += 24; - issue_buf_size[level] += 24; - issue_buf_n[level]++; } /** @@ -833,6 +730,16 @@ void ConnectionMulti::issue_noop(double now, int level) { */ int ConnectionMulti::issue_set(const char* key, const char* value, int length, double now, int level, int flags, uint32_t l1opaque, uint8_t log) { + + struct evbuffer *output = NULL; + switch (level) { + case 1: + output = bufferevent_get_output(bev1); + break; + case 2: + output = bufferevent_get_output(bev2); + break; + } //Operation op; Operation *pop = new Operation(); @@ -873,68 +780,16 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, htonl(keylen + 8 + length) }; h.opaque = htonl(pop->opaque); - memcpy(issue_buf_pos[level],&h,24); - issue_buf_pos[level] += 24; - issue_buf_size[level] += 24; - uint32_t f = htonl(flags); - memcpy(issue_buf_pos[level],&f,4); - issue_buf_pos[level] += 4; - issue_buf_size[level] += 4; - uint32_t exp = 0; - memcpy(issue_buf_pos[level],&exp,4); - issue_buf_pos[level] += 4; - issue_buf_size[level] += 4; - - memcpy(issue_buf_pos[level],key,keylen); - issue_buf_pos[level] += keylen; - issue_buf_size[level] += keylen; - -//if (issue_buf_pos[level]+length >= issue_buf[level]+MAX_BUFFER_SIZE-1 || -// issue_buf_size[level]+length >= MAX_BUFFER_SIZE-1) { -// -// fprintf(stderr,"issing set: %s, size: %u, level %d, flags: %d\n",key,length,level,flags); -// for (int i = 1; i <= 2; i++) { -// fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); -// } -// for (int i = 1; i <= 2; i++) { -// fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); -// } -// for (int i = 1; i <= 2; i++) { -// fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); -// } -// -//} - memcpy(issue_buf_pos[level],value,length); - issue_buf_pos[level] += length; - issue_buf_size[level] += length; - issue_buf_n[level]++; - if (issue_buf_n[level] > 500) { - fprintf(stderr,"issue_buf_n[%d] too big %d (set: %d)\n",level,issue_buf_n[level],log); - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - } - -#ifdef DEBUGS - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } -#endif + evbuffer_add(output, &h, 24); + evbuffer_add(output, &f, 4); + evbuffer_add(output, &exp, 4); + evbuffer_add(output, key, keylen); + evbuffer_add(output, value, length); - if (read_state != LOADING) { - stats.tx_bytes += length + 32 + keylen; - } - - //stats.log_access(op); + stats.tx_bytes += length + 32 + keylen; return 1; } @@ -1043,26 +898,16 @@ void ConnectionMulti::finish_op(Operation *op, int was_hit) { last_rx = now; uint8_t level = op->level; //op_queue[level].erase(op_queue[level].begin()+opopq); - op_queue[level][op->opaque] = 0; - delete op; + if (op == op_queue[level][op->opaque] && + op->opaque == op_queue[level][op->opaque]->opaque) { + delete op_queue[level][op->opaque]; + } else { + fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", + op,op_queue[level][op->opaque],op->opaque,op_queue[level][op->opaque]->opaque); + } op_queue_size[level]--; read_state = IDLE; - //lets check if we should output stats for the window - //Do the binning for percentile outputs - //crude at start - if ((options.misswindow != 0) && ( ((stats.window_accesses) % options.misswindow) == 0)) - { - if (stats.window_gets != 0) - { - //printf("%lu,%.4f\n",(stats.accesses), - // ((double)stats.window_get_misses/(double)stats.window_accesses)); - stats.window_gets = 0; - stats.window_get_misses = 0; - stats.window_sets = 0; - stats.window_accesses = 0; - } - } } @@ -1310,7 +1155,7 @@ static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, if (opcode == CMD_GET && status == RESP_NOT_FOUND) { switch(level) { case 1: - conn->stats.get_misses_l1++; + //conn->stats.get_misses_l1++; break; case 2: conn->stats.get_misses_l2++; @@ -1381,9 +1226,6 @@ void ConnectionMulti::read_callback1() { //GET was found, but wrong value size (i.e. update value) found = true; - //bool full_read = true; - //fprintf(stderr,"read_cb start with current queue of ops: %lu and issue_buf_n: %d\n",op_queue.size(),issue_buf_n); - //if (op_queue.size() == 0) V("Spurious read callback."); bool full_read = true; while (full_read) { @@ -1450,9 +1292,9 @@ void ConnectionMulti::read_callback1() { } else { if (found) { - //if (op->incl == 1) { - // issue_touch(op->key.c_str(),op->valuelen,now,2); - //} + if (op->incl == 1) { + issue_touch(op->key.c_str(),op->valuelen,now,2); + } finish_op(op,1); } else { finish_op(op,0); @@ -1465,9 +1307,6 @@ void ConnectionMulti::read_callback1() { } break; case Operation::SET: - //if (op->incl == 1) { - // issue_touch(op->key.c_str(),op->valuelen,0,2); - //} if (evict->evicted) { if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, 2, ITEM_INCL | ITEM_DIRTY, 0, 1); @@ -1476,7 +1315,21 @@ void ConnectionMulti::read_callback1() { issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, 2, ITEM_EXCL, 0, 1); this->stats.excl_wbs++; } + if (((evict->serverFlags & ITEM_DIRTY) == 0) && (op->log == 1)) { + //we sent a SET request to l1, the item that was set is new since it didn't not get + //marked as dirty + this->stats.set_misses_l1++; + } + + if (op->incl == 2) { + //if exclusive, remove from l2 + //here, we sent a set to l1 + issue_delete(op->key.c_str(),now,2,op->log); + } } + //if (op->incl == 1 && op->log == 1 && op->flags) { + // issue_touch(op->key.c_str(),op->valuelen,0,2); + //} finish_op(op,1); break; default: @@ -1497,116 +1350,6 @@ void ConnectionMulti::read_callback1() { if (check_exit_condition(now)) { return; } -#ifdef DEBUGMC - fprintf(stderr,"read_cb1 done with current queue of ops in l1: %d and issue_buf_n in l1: %d\n",op_queue_size[1],issue_buf_n[1]); - //for (auto x : op_queue[1]) { - // if (x.opaque > 0) { - // cerr << x.opaque << ": " << x.key << endl; - // } - //} - //fprintf(stderr,"read_cb1 done with current queue of ops in l2: %d and issue_buf_n in l2: %d\n",op_queue_size[2],issue_buf_n[2]); - //for (auto x : op_queue[2]) { - // if (x.opaque > 0) { - // cerr << x.opaque << ": " << x.key << endl; - // } - //} -#endif - //buffer is ready to go! - if (issue_buf_n[1] >= 4) { - if (last_quiet1) { - issue_noop(now,1); - last_quiet1 = false; - } -#ifdef DEBUGMC - fprintf(stderr,"read_cb1 writing %d reqs to l1, last quiet %d\n",issue_buf_n[1],last_quiet1); - char *output = (char*)malloc(sizeof(char)*(issue_buf_size[1]+512)); - //fprintf(stderr,"-------------------------------------\n"); - //memcpy(output,issue_buf[1],issue_buf_size[1]); - //write(2,output,issue_buf_size[1]); - //fprintf(stderr,"\n-------------------------------------\n"); - free(output); -#endif - - //bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); - - struct evbuffer* out1 = bufferevent_get_output(bev1); - if (evbuffer_expand(out1,issue_buf_size[1])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - - } - if (evbuffer_add(out1, issue_buf[1], issue_buf_size[1])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - } - - - memset(issue_buf[1],0,issue_buf_size[1]); - issue_buf_pos[1] = issue_buf[1]; - issue_buf_size[1] = 0; - issue_buf_n[1] = 0; - } - - if (issue_buf_n[2] >= 4) { - if (last_quiet2) { - issue_noop(now,2); - last_quiet2 = false; - } -#ifdef DEBUGMC - fprintf(stderr,"read_cb1 writing %d reqs to l2, last quiet %d\n",issue_buf_n[2],last_quiet2); - char *output = (char*)malloc(sizeof(char)*(issue_buf_size[2]+512)); - //fprintf(stderr,"-------------------------------------\n"); - //memcpy(output,issue_buf[2],issue_buf_size[2]); - //write(2,output,issue_buf_size[2]); - //fprintf(stderr,"\n-------------------------------------\n"); - free(output); -#endif - - //bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); - - struct evbuffer* out2 = bufferevent_get_output(bev2); - if (evbuffer_expand(out2,issue_buf_size[2])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - - } - if (evbuffer_add(out2, issue_buf[2], issue_buf_size[2])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - } - memset(issue_buf[2],0,issue_buf_size[2]); - issue_buf_pos[2] = issue_buf[2]; - issue_buf_size[2] = 0; - issue_buf_n[2] = 0; - } last_tx = now; stats.log_op(op_queue_size[1]); @@ -1639,8 +1382,6 @@ void ConnectionMulti::read_callback2() { //GET was found, but wrong value size (i.e. update value) found = true; - //bool full_read = true; - //fprintf(stderr,"read_cb start with current queue of ops: %lu and issue_buf_n: %d\n",op_queue.size(),issue_buf_n); //if (op_queue.size() == 0) V("Spurious read callback."); bool full_read = true; @@ -1713,10 +1454,10 @@ void ConnectionMulti::read_callback2() { SET_INCL(incl,flags); //found in l2, set in l1 issue_set(key, &random_char[index],valuelen, now, 1, flags, 0, 0); - if (incl == 2) { - //if exclusive, remove from l2 - issue_delete(key,now,2); - } + //if (incl == 2) { + // //if exclusive, remove from l2 + // issue_delete(key,now,2,0); + //} this->stats.copies_to_l1++; finish_op(op,1); @@ -1751,6 +1492,10 @@ void ConnectionMulti::read_callback2() { } break; case Operation::DELETE: + //check to see if it was a hit + if (!found && op->log) { + this->stats.set_misses_l2++; + } finish_op(op,1); break; default: @@ -1764,114 +1509,6 @@ void ConnectionMulti::read_callback2() { if (check_exit_condition(now)) { return; } -#ifdef DEBUGMC - fprintf(stderr,"read_cb2 done with current queue of ops l1: %d and issue_buf_n: %d\n",op_queue_size[1],issue_buf_n[1]); - //for (auto x : op_queue[1]) { - // if (x.opaque > 0) { - // cerr << x.opaque << ": " << x.key << endl; - // } - //} - //fprintf(stderr,"read_cb2 done with current queue of ops l2: %d and issue_buf_n: %d\n",op_queue_size[2],issue_buf_n[2]); - //for (auto x : op_queue[2]) { - // if (x.opaque > 0) { - // cerr << x.opaque << ": " << x.key << endl; - // } - //} -#endif - //buffer is ready to go! - if (issue_buf_n[2] >= 4) { - if (last_quiet2) { - issue_noop(now,2); - last_quiet2 = false; - } -#ifdef DEBUGMC - fprintf(stderr,"read_cb2 writing %d reqs, last quiet %d\n",issue_buf_n[2],last_quiet2); - char *output = (char*)malloc(sizeof(char)*(issue_buf_size[2]+512)); - //fprintf(stderr,"-------------------------------------\n"); - //memcpy(output,issue_buf[2],issue_buf_size[2]); - //write(2,output,issue_buf_size[2]); - //fprintf(stderr,"\n-------------------------------------\n"); - free(output); -#endif - - //bufferevent_write(bev2, issue_buf[2], issue_buf_size[2]); - - struct evbuffer* out2 = bufferevent_get_output(bev2); - if (evbuffer_expand(out2,issue_buf_size[2])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - - } - if (evbuffer_add(out2, issue_buf[2], issue_buf_size[2])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - } - memset(issue_buf[2],0,issue_buf_size[2]); - issue_buf_pos[2] = issue_buf[2]; - issue_buf_size[2] = 0; - issue_buf_n[2] = 0; - } - //buffer is ready to go! - if (issue_buf_n[1] >= 4) { - if (last_quiet1) { - issue_noop(now,1); - last_quiet1 = false; - } -#ifdef DEBUGMC - fprintf(stderr,"read_cb1 writing %d reqs to l1, last quiet %d\n",issue_buf_n[1],last_quiet1); - char *output = (char*)malloc(sizeof(char)*(issue_buf_size[1]+512)); - //fprintf(stderr,"-------------------------------------\n"); - //memcpy(output,issue_buf[1],issue_buf_size[1]); - //write(2,output,issue_buf_size[1]); - //fprintf(stderr,"\n-------------------------------------\n"); - free(output); -#endif - - //bufferevent_write(bev1, issue_buf[1], issue_buf_size[1]); - - struct evbuffer* out1 = bufferevent_get_output(bev1); - if (evbuffer_expand(out1,issue_buf_size[1])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - - } - if (evbuffer_add(out1, issue_buf[1], issue_buf_size[1])) { - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"op_queue_size[%d]: %u\n",i,op_queue_size[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_n[%d]: %u\n",i,issue_buf_n[i]); - } - for (int i = 1; i <= 2; i++) { - fprintf(stderr,"issue buf_size[%d]: %u\n",i,issue_buf_size[i]); - } - } - memset(issue_buf[1],0,issue_buf_size[1]); - issue_buf_pos[1] = issue_buf[1]; - issue_buf_size[1] = 0; - issue_buf_n[1] = 0; - } last_tx = now; stats.log_op(op_queue_size[2]); diff --git a/ConnectionStats.h b/ConnectionStats.h index a9151cd..7125b87 100644 --- a/ConnectionStats.h +++ b/ConnectionStats.h @@ -44,6 +44,7 @@ class ConnectionStats { accesses(0), get_misses(0), get_misses_l1(0), get_misses_l2(0), + set_misses_l1(0), set_misses_l2(0), excl_wbs(0), incl_wbs(0), copies_to_l1(0), window_gets(0), window_sets(0), window_accesses(0), @@ -83,6 +84,7 @@ class ConnectionStats { uint64_t gets_l1, sets_l1, gets_l2, sets_l2; uint64_t accesses, get_misses; uint64_t get_misses_l1, get_misses_l2; + uint64_t set_misses_l1, set_misses_l2; uint64_t excl_wbs, incl_wbs; uint64_t copies_to_l1; uint64_t window_gets, window_sets, window_accesses, window_get_misses; @@ -178,6 +180,8 @@ class ConnectionStats { get_misses += cs.get_misses; get_misses_l1 += cs.get_misses_l1; get_misses_l2 += cs.get_misses_l2; + set_misses_l1 += cs.set_misses_l1; + set_misses_l2 += cs.set_misses_l2; excl_wbs += cs.excl_wbs; incl_wbs += cs.incl_wbs; copies_to_l1 += cs.copies_to_l1; @@ -200,6 +204,8 @@ class ConnectionStats { get_misses += as.get_misses; get_misses_l1 += as.get_misses_l1; get_misses_l2 += as.get_misses_l2; + set_misses_l1 += as.set_misses_l1; + set_misses_l2 += as.set_misses_l2; excl_wbs += as.excl_wbs; incl_wbs += as.incl_wbs; copies_to_l1 += as.copies_to_l1; diff --git a/Operation.h b/Operation.h index 5f9c51c..97b7417 100644 --- a/Operation.h +++ b/Operation.h @@ -20,9 +20,7 @@ class Operation { uint32_t opaque; uint32_t l1opaque; uint16_t clsid; - uint8_t level; - uint8_t log; - uint8_t incl; + uint16_t flags; string key; double time() const { return (end_time - start_time) * 1000000; } diff --git a/mutilate.cc b/mutilate.cc index c3c2b12..18e897d 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -646,12 +646,12 @@ int main(int argc, char **argv) { printf("\n"); printf("Misses = %" PRIu64 " (%.1f%%)\n", stats.get_misses, - (double) stats.get_misses/(stats.gets+stats.sets)*100); + (double) stats.get_misses/(stats.gets)*100); if (servers.size() == 2) { - printf("Misses (L1) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l1, - (double) stats.get_misses_l1/(stats.gets + stats.sets)*100); - printf("Misses (L2) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l2, - (double) stats.get_misses_l2/(stats.gets + stats.sets)*100); + printf("Misses (L1) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l1 + stats.set_misses_l1, + (double) (stats.get_misses_l1 + stats.set_misses_l1) /(stats.gets + stats.sets)*100); + printf("Misses (L2) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l2 + stats.set_misses_l2, + (double) (stats.get_misses_l2 + stats.set_misses_l2) /(stats.gets + stats.sets)*100); printf("L2 Writes = %" PRIu64 " (%.1f%%)\n", stats.sets_l2, (double) stats.sets_l2/(stats.gets+stats.sets)*100); From 5d95de80e93f1739643648ffaec0e37186e3856f Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 6 Oct 2021 10:44:29 -0400 Subject: [PATCH 42/57] updates --- AgentStats.h | 3 + Connection.h | 21 +- ConnectionMulti.cc | 452 +++++++++++++++++++++++++++----------------- ConnectionOptions.h | 3 + ConnectionStats.h | 16 +- Operation.h | 4 +- cmdline.ggo | 3 + mutilate.cc | 270 +++++++++++--------------- 8 files changed, 432 insertions(+), 340 deletions(-) diff --git a/AgentStats.h b/AgentStats.h index 2c23711..e73bb8c 100644 --- a/AgentStats.h +++ b/AgentStats.h @@ -11,6 +11,9 @@ class AgentStats { uint64_t set_misses_l1, set_misses_l2; uint64_t excl_wbs, incl_wbs; uint64_t copies_to_l1; + uint64_t delete_misses_l2; + uint64_t delete_hits_l2; + uint64_t set_incl_hits_l1, set_excl_hits_l1; uint64_t skips; double start, stop; diff --git a/Connection.h b/Connection.h index fdab077..8aae444 100644 --- a/Connection.h +++ b/Connection.h @@ -39,6 +39,7 @@ void bev_read_cb1(struct bufferevent *bev, void *ptr); void bev_event_cb2(struct bufferevent *bev, short events, void *ptr); void bev_read_cb2(struct bufferevent *bev, void *ptr); void bev_write_cb(struct bufferevent *bev, void *ptr); +void bev_write_cb_m(struct bufferevent *bev, void *ptr); void timer_cb(evutil_socket_t fd, short what, void *ptr); void timer_cb_m(evutil_socket_t fd, short what, void *ptr); @@ -180,7 +181,7 @@ class ConnectionMulti { public: ConnectionMulti(struct event_base* _base, struct evdns_base* _evdns, string _hostname1, string _hostname2, string _port, options_t options, - bool sampling = true); + bool sampling = true, int fd1 = -1, int fd2 = -1); ~ConnectionMulti(); @@ -194,7 +195,10 @@ class ConnectionMulti { void set_priority(int pri); // state commands - void start() { drive_write_machine(); } + void start() { + //fprintf(stderr,"connid: %d starting...\n",cid); + drive_write_machine(); + } void start_loading(); void reset(); bool check_exit_condition(double now = 0.0); @@ -207,6 +211,7 @@ class ConnectionMulti { void write_callback(); void timer_callback(); + int eof; uint32_t get_cid(); //void set_queue(ConcurrentQueue *a_trace_queue); void set_queue(queue *a_trace_queue); @@ -227,6 +232,7 @@ class ConnectionMulti { double last_rx; // Used to moderate transmission rate. double last_tx; + vector wb_keys; enum read_state_enum { INIT_READ, CONN_SETUP, @@ -261,12 +267,12 @@ class ConnectionMulti { bool last_quiet2; uint32_t total; uint32_t cid; - int eof; //std::vector> op_queue; Operation ***op_queue; uint32_t *op_queue_size; + map key_hist; Generator *valuesize; Generator *keysize; @@ -287,11 +293,10 @@ class ConnectionMulti { // request functions void issue_sasl(); void issue_noop(double now = 0.0, int level = 1); - int issue_touch(const char* key, int valuelen, double now = 0.0, int level = 1); - int issue_delete(const char* key, double now = 0.0, int level = 1, int log = 1); - int issue_get_with_len(const char* key, int valuelen, double now = 0.0, bool quiet = false, int level = 1, int flags = 0, uint32_t l1opaque = 0, uint8_t log = 1); - int issue_set(const char* key, const char* value, int length, - double now = 0.0, int level = 1, int flags = 0, uint32_t l1opaque = 0, uint8_t log = 1); + int issue_touch(const char* key, int valuelen, double now, int level); + int issue_delete(const char* key, double now, uint32_t flags); + int issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); + int issue_set(const char* key, const char* value, int length, double now, uint32_t flags); // protocol fucntions int set_request_ascii(const char* key, const char* value, int length); diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index bddba55..339c5dd 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -2,7 +2,7 @@ #include #include #include - +#include #include #include @@ -36,10 +36,13 @@ #define SRC_L2_H 64 #define SRC_DIRECT_SET 128 #define SRC_L1_COPY 256 +#define SRC_WB 512 #define ITEM_INCL 4096 #define ITEM_EXCL 8192 #define ITEM_DIRTY 16384 +#define ITEM_SIZE_CHANGE 131072 +#define ITEM_WAS_HIT 262144 #define LEVELS 2 #define SET_INCL(incl,flags) \ @@ -58,12 +61,24 @@ else if (flags & ITEM_EXCL) incl = 2; \ //#define OP_level(op) ( ((op)->flags & ITEM_L1) ? ITEM_L1 : ITEM_L2 ) -#define OP_level(op) ( (op)->flags ~& (LOG_OP | \ - ITEM_INCL | ITEM_EXCL | ITEM_DIRTY \ - SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H \ - SRC_DIRECT_SET | SRC_L1_COPY ) ) -#define OP_src(op) ( (op)->flags ~& (ITEM_L1 | ITEM_L2 | LOG_OP | \ +#define OP_level(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define FLAGS_level(flags) ( flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_clu(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_L1 | ITEM_L2 | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_src(op) ( (op)->flags & ~(ITEM_L1 | ITEM_L2 | LOG_OP | \ ITEM_INCL | ITEM_EXCL | ITEM_DIRTY ) ) + #define OP_log(op) ((op)->flags & LOG_OP) #define OP_incl(op) ((op)->flags & ITEM_INCL) #define OP_excl(op) ((op)->flags & ITEM_EXCL) @@ -205,7 +220,7 @@ void ConnectionMulti::output_op(Operation *op, int type, bool found) { */ ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _evdns, string _hostname1, string _hostname2, string _port, options_t _options, - bool sampling ) : + bool sampling, int fd1, int fd2 ) : start_time(0), stats(sampling), options(_options), hostname1(_hostname1), hostname2(_hostname2), port(_port), base(_base), evdns(_evdns) { @@ -213,7 +228,7 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _e init_inclusives(options.inclusives); valuesize = createGenerator(options.valuesize); keysize = createGenerator(options.keysize); - + srand(time(NULL)); keygen = new KeyGenerator(keysize, options.records); total = 0; @@ -250,8 +265,17 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _e } + bev1 = bufferevent_socket_new(base, fd1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev1, bev_read_cb1, bev_write_cb_m, bev_event_cb1, this); + bufferevent_enable(bev1, EV_READ | EV_WRITE); + + bev2 = bufferevent_socket_new(base, fd2, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev2, bev_read_cb2, bev_write_cb_m, bev_event_cb2, this); + bufferevent_enable(bev2, EV_READ | EV_WRITE); + timer = evtimer_new(base, timer_cb_m, this); + read_state = IDLE; } @@ -272,13 +296,6 @@ int ConnectionMulti::do_connect() { int connected = 0; if (options.unix_socket) { - bev1 = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); - bufferevent_setcb(bev1, bev_read_cb1, NULL, bev_event_cb1, this); - bufferevent_enable(bev1, EV_READ | EV_WRITE); - - bev2 = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); - bufferevent_setcb(bev2, bev_read_cb2, NULL, bev_event_cb2, this); - bufferevent_enable(bev2, EV_READ | EV_WRITE); struct sockaddr_un sin1; memset(&sin1, 0, sizeof(sin1)); @@ -287,14 +304,14 @@ int ConnectionMulti::do_connect() { int addrlen; addrlen = sizeof(sin1); + int err = bufferevent_socket_connect(bev1, (struct sockaddr*)&sin1, addrlen); if (err == 0) { connected = 1; } else { - connected = 0; + connected = 0; err = errno; - fprintf(stderr,"error %s\n",strerror(err)); - bufferevent_free(bev1); + fprintf(stderr,"l1 error %s\n",strerror(err)); } struct sockaddr_un sin2; @@ -309,10 +326,10 @@ int ConnectionMulti::do_connect() { } else { connected = 0; err = errno; - fprintf(stderr,"error %s\n",strerror(err)); - bufferevent_free(bev2); + fprintf(stderr,"l2 error %s\n",strerror(err)); } } + read_state = IDLE; return connected; } @@ -330,11 +347,11 @@ ConnectionMulti::~ConnectionMulti() { free(op_queue_size); free(opaque); free(op_queue); - event_free(timer); - timer = NULL; + //event_free(timer); + //timer = NULL; // FIXME: W("Drain op_q?"); - bufferevent_free(bev1); - bufferevent_free(bev2); + //bufferevent_free(bev1); + //bufferevent_free(bev2); delete iagen; delete keygen; @@ -389,11 +406,11 @@ int ConnectionMulti::issue_getsetorset(double now) { //pthread_mutex_lock(lock); if (!trace_queue->empty()) { line = trace_queue->front(); - trace_queue->pop(); if (line.compare("EOF") == 0) { eof = 1; + fprintf(stderr,"cid %d done\n",cid); return 1; - } + } stringstream ss(line); //pthread_mutex_unlock(lock); int Op = 0; @@ -441,14 +458,27 @@ int ConnectionMulti::issue_getsetorset(double now) { if (rOp.compare("write") == 0) Op = 2; } + + if ((int)wb_keys.size() > options.depth*10) { + fprintf(stderr,"wb_queue size %d\n",(int)wb_keys.size()); + return 1; + } + /* if this key is currently being written back wait for it to clear */ + if (find(wb_keys.begin(), wb_keys.end(), rKey) != wb_keys.end()) { + return 1; + } + char key[256]; memset(key,0,256); strncpy(key, rKey.c_str(),255); + + trace_queue->pop(); int issued = 0; int incl = get_incl(vl,strlen(key)); int flags = 0; + int touch = (rand() % 100); SET_INCL(incl,flags); switch(Op) { @@ -457,13 +487,19 @@ int ConnectionMulti::issue_getsetorset(double now) { // key,vl,stoi(rT)); break; case 1: - if (nissued < options.depth-1) { - issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); - last_quiet1 = false; - } else { - issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); - last_quiet1 = false; + //if (nissued < options.depth-1) { + // issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); + // last_quiet1 = false; + //} else { + //} + if (options.threshold > 0) { + key_hist[key]++; } + issued = issue_get_with_len(key, vl, now, false, flags | LOG_OP | ITEM_L1); + if (touch == 0 && incl == 1) { + issue_touch(key,vl,now, ITEM_L2 | SRC_L1_H); + } + last_quiet1 = false; this->stats.gets++; break; case 2: @@ -471,9 +507,11 @@ int ConnectionMulti::issue_getsetorset(double now) { issue_noop(now,1); } int index = lrand48() % (1024 * 1024); - flags |= ITEM_DIRTY; - - issued = issue_set(key, &random_char[index], vl, now, 1, flags, 0, 1); + wb_keys.push_back(string(key)); + issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); + if (touch == 0 && incl == 1) { + issue_touch(key,vl,now, ITEM_L2 | SRC_DIRECT_SET); + } last_quiet1 = false; this->stats.sets++; break; @@ -508,14 +546,17 @@ int ConnectionMulti::issue_getsetorset(double now) { /** * Issue a get request to the server. */ -int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, - int level, int flags, uint32_t l1opaque, uint8_t log) { +int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1) { + struct evbuffer *output = NULL; - switch (level) { + int level = 0; + switch (FLAGS_level(flags)) { case 1: + level = 1; output = bufferevent_get_output(bev1); break; case 2: + level = 2; output = bufferevent_get_output(bev2); break; } @@ -542,14 +583,18 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no pop->valuelen = valuelen; pop->type = Operation::GET; pop->opaque = opaque[level]++; - pop->level = level; - pop->l1opaque = l1opaque; - pop->log = log; - GET_INCL(pop->incl,flags); + pop->flags = flags; pop->clsid = get_class(valuelen,strlen(key)); + if (l1 != NULL) { + pop->l1 = l1; + } op_queue[level][pop->opaque] = pop; //op_queue[level].push(op); op_queue_size[level]++; + +#ifdef DEBUGS + fprintf(stderr,"issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",key,valuelen,level,flags,pop->opaque); +#endif if (opaque[level] > OPAQUE_MAX) { opaque[level] = 1; @@ -577,13 +622,16 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no /** * Issue a get request to the server. */ -int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int level) { +int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int flags) { struct evbuffer *output = NULL; - switch (level) { + int level = 0; + switch (FLAGS_level(flags)) { case 1: + level = 1; output = bufferevent_get_output(bev1); break; case 2: + level = 2; output = bufferevent_get_output(bev2); break; } @@ -609,7 +657,7 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int pop->valuelen = valuelen; pop->type = Operation::TOUCH; pop->opaque = opaque[level]++; - pop->level = level; + pop->flags = flags; op_queue[level][pop->opaque] = pop; //op_queue[level].push(op); op_queue_size[level]++; @@ -618,6 +666,9 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int opaque[level] = 1; } +#ifdef DEBUGS + fprintf(stderr,"issing touch: %s, size: %u, level %d, flags: %d, opaque: %d\n",key,valuelen,level,flags,pop->opaque); +#endif //if (read_state == IDLE) read_state = WAITING_FOR_GET; uint16_t keylen = strlen(key); @@ -642,13 +693,16 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int /** * Issue a delete request to the server. */ -int ConnectionMulti::issue_delete(const char* key, double now, int level, int log) { +int ConnectionMulti::issue_delete(const char* key, double now, uint32_t flags) { struct evbuffer *output = NULL; - switch (level) { + int level = 0; + switch (FLAGS_level(flags)) { case 1: + level = 1; output = bufferevent_get_output(bev1); break; case 2: + level = 2; output = bufferevent_get_output(bev2); break; } @@ -674,8 +728,7 @@ int ConnectionMulti::issue_delete(const char* key, double now, int level, int lo pop->key = string(key); pop->type = Operation::DELETE; pop->opaque = opaque[level]++; - pop->level = level; - pop->log = log; + pop->flags = flags; op_queue[level][pop->opaque] = pop; //op_queue[level].push(op); op_queue_size[level]++; @@ -728,18 +781,20 @@ void ConnectionMulti::issue_noop(double now, int level) { /** * Issue a set request to the server. */ -int ConnectionMulti::issue_set(const char* key, const char* value, int length, - double now, int level, int flags, uint32_t l1opaque, uint8_t log) { +int ConnectionMulti::issue_set(const char* key, const char* value, int length, double now, uint32_t flags) { - struct evbuffer *output = NULL; - switch (level) { - case 1: - output = bufferevent_get_output(bev1); - break; - case 2: - output = bufferevent_get_output(bev2); - break; - } + struct evbuffer *output = NULL; + int level = 0; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } //Operation op; Operation *pop = new Operation(); @@ -755,17 +810,13 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, pop->valuelen = length; pop->type = Operation::SET; pop->opaque = opaque[level]++; - pop->level = level; - pop->incl = 0; - pop->log = log; - pop->l1opaque = l1opaque; - GET_INCL(pop->incl,flags); + pop->flags = flags; pop->clsid = get_class(length,strlen(key)); op_queue[level][pop->opaque] = pop; //op_queue[level].push(op); op_queue_size[level]++; #ifdef DEBUGS - fprintf(stderr,"issing set: %s, size: %u, level %d, flags: %d\n",key,length,level,flags); + fprintf(stderr,"issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",key,length,level,flags,pop->opaque); #endif if (opaque[level] > OPAQUE_MAX) { @@ -798,7 +849,7 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, */ void ConnectionMulti::pop_op(Operation *op) { - uint8_t level = op->level; + uint8_t level = OP_level(op); //op_queue[level].erase(op); op_queue_size[level]--; @@ -840,7 +891,7 @@ void ConnectionMulti::finish_op(Operation *op, int was_hit) { if (options.successful_queries && was_hit) { switch (op->type) { case Operation::GET: - switch (op->level) { + switch (OP_level(op)) { case 1: stats.log_get_l1(*op); break; @@ -850,7 +901,7 @@ void ConnectionMulti::finish_op(Operation *op, int was_hit) { } break; case Operation::SET: - switch (op->level) { + switch (OP_level(op)) { case 1: stats.log_set_l1(*op); break; @@ -866,20 +917,24 @@ void ConnectionMulti::finish_op(Operation *op, int was_hit) { } else { switch (op->type) { case Operation::GET: - if (op->log) { - switch (op->level) { + if (OP_log(op)) { + switch (OP_level(op)) { case 1: stats.log_get_l1(*op); break; case 2: stats.log_get_l2(*op); + if (op->l1 != NULL) { + op->l1->end_time = now; + stats.log_get(*(op->l1)); + } break; } } break; case Operation::SET: - if (op->log) { - switch (op->level) { + if (OP_log(op)) { + switch (OP_level(op)) { case 1: stats.log_set_l1(*op); break; @@ -896,7 +951,11 @@ void ConnectionMulti::finish_op(Operation *op, int was_hit) { } last_rx = now; - uint8_t level = op->level; + uint8_t level = OP_level(op); + if (op->l1 != NULL) { + delete op_queue[1][op->l1->opaque]; + op_queue_size[1]--; + } //op_queue[level].erase(op_queue[level].begin()+opopq); if (op == op_queue[level][op->opaque] && op->opaque == op_queue[level][op->opaque]->opaque) { @@ -917,6 +976,9 @@ void ConnectionMulti::finish_op(Operation *op, int was_hit) { * Check if our testing is done and we should exit. */ bool ConnectionMulti::check_exit_condition(double now) { + if (eof) { + return true; + } if (read_state == INIT_READ) return false; if (now == 0.0) now = get_time(); @@ -969,7 +1031,6 @@ void ConnectionMulti::event_callback1(short events) { fprintf(stderr,"libevent connected %s, fd: %u\n",hostname1.c_str(),bufferevent_getfd(bev1)); #endif - drive_write_machine(); } else if (events & BEV_EVENT_ERROR) { int err = bufferevent_socket_get_dns_error(bev1); @@ -1045,7 +1106,6 @@ void ConnectionMulti::drive_write_machine(double now) { next_time = now + delay; double_to_tv(delay, &tv); evtimer_add(timer, &tv); - write_state = WAITING_FOR_TIME; write_state = ISSUING; break; @@ -1053,19 +1113,7 @@ void ConnectionMulti::drive_write_machine(double now) { if ( (op_queue_size[1] >= (size_t) options.depth) || (op_queue_size[2] >= (size_t) options.depth) ) { write_state = WAITING_FOR_OPQ; - return; - } else if (now < next_time) { - write_state = WAITING_FOR_TIME; - break; // We want to run through the state machine one more time - // to make sure the timer is armed. - } else if (options.moderate && now < last_rx + 0.00025) { - write_state = WAITING_FOR_TIME; - if (!event_pending(timer, EV_TIMEOUT, NULL)) { - delay = last_rx + 0.00025 - now; - double_to_tv(delay, &tv); - evtimer_add(timer, &tv); - } - return; + break; } if (options.getsetorset) { @@ -1077,29 +1125,9 @@ void ConnectionMulti::drive_write_machine(double now) { for (int i = 1; i <= 2; i++) { stats.log_op(op_queue_size[i]); } - next_time += iagen->generate(); - - if (options.skip && options.lambda > 0.0 && - now - next_time > 0.005000 && ( - op_queue_size[1] >= (size_t) options.depth || - op_queue_size[2] >= (size_t) options.depth)) { - - while (next_time < now - 0.004000) { - stats.skips++; - next_time += iagen->generate(); - } - } break; case WAITING_FOR_TIME: - if (now < next_time) { - if (!event_pending(timer, EV_TIMEOUT, NULL)) { - delay = next_time - now; - double_to_tv(delay, &tv); - evtimer_add(timer, &tv); - } - return; - } write_state = ISSUING; break; @@ -1107,9 +1135,10 @@ void ConnectionMulti::drive_write_machine(double now) { if ( (op_queue_size[1] >= (size_t) options.depth) || (op_queue_size[2] >= (size_t) options.depth) ) { return; + } else { + write_state = ISSUING; + break; } - write_state = ISSUING; - break; default: DIE("Not implemented"); } @@ -1131,11 +1160,6 @@ static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, binary_header_t* h = reinterpret_cast(evbuffer_pullup(input, 24)); //assert(h); -#ifdef DEBUGMC - fprintf(stderr,"handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",level, - h->opcode,ntohl(h->opaque),ntohl(h->key_len),h->extra_len, - ntohl(h->body_len),ntohs(h->status)); -#endif uint32_t bl = ntohl(h->body_len); uint16_t kl = ntohs(h->key_len); @@ -1149,13 +1173,18 @@ static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, opcode = h->opcode; opaque = ntohl(h->opaque); uint16_t status = ntohs(h->status); +#ifdef DEBUGMC + fprintf(stderr,"handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",level, + h->opcode,ntohl(h->opaque),ntohs(h->key_len),h->extra_len, + ntohl(h->body_len),ntohs(h->status)); +#endif // If something other than success, count it as a miss if (opcode == CMD_GET && status == RESP_NOT_FOUND) { switch(level) { case 1: - //conn->stats.get_misses_l1++; + conn->stats.get_misses_l1++; break; case 2: conn->stats.get_misses_l2++; @@ -1192,10 +1221,12 @@ static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, memcpy(evict->evictedKey,buf,kl); buf += kl; + evict->evictedLen = bl - kl - el; evict->evictedData = (char*)malloc(evict->evictedLen); memcpy(evict->evictedData,buf,evict->evictedLen); evict->evicted = true; + //fprintf(stderr,"class: %u, serverFlags: %u, evictedFlags: %u\n",evict->clsid,evict->serverFlags,evict->evictedFlags); evbuffer_drain(input,bl); } else { evbuffer_drain(input, targetLen); @@ -1203,6 +1234,9 @@ static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, } else if (opcode == CMD_TOUCH && status == RESP_NOT_FOUND) { found = false; evbuffer_drain(input, targetLen); + } else if (opcode == CMD_DELETE && status == RESP_NOT_FOUND) { + found = false; + evbuffer_drain(input, targetLen); } else { evbuffer_drain(input, targetLen); } @@ -1278,23 +1312,27 @@ void ConnectionMulti::read_callback1() { double now = get_time(); + int wb = 0; + if (options.rand_admit) { + wb = (rand() % options.rand_admit); + } switch (op->type) { case Operation::GET: if (done) { if ( !found && (options.getset || options.getsetorset) ) { /* issue a get a l2 */ - string key = op->key; + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); int vl = op->valuelen; - int flags = 0; - SET_INCL(op->incl,flags); - issue_get_with_len(key.c_str(),vl,now,false,2,flags,0,1); - finish_op(op,0); + int flags = OP_clu(op); + issue_get_with_len(key,vl,now,false, flags | SRC_L1_M | ITEM_L2 | LOG_OP, op); + op->end_time = now; + this->stats.log_get_l1(*op); + //finish_op(op,0); } else { if (found) { - if (op->incl == 1) { - issue_touch(op->key.c_str(),op->valuelen,now,2); - } finish_op(op,1); } else { finish_op(op,0); @@ -1307,29 +1345,63 @@ void ConnectionMulti::read_callback1() { } break; case Operation::SET: + if (OP_src(op) == SRC_L1_COPY || + OP_src(op) == SRC_DIRECT_SET || + OP_src(op) == SRC_L2_M ) { + vector::iterator position = std::find(wb_keys.begin(), wb_keys.end(), op->key); + if (position != wb_keys.end()) { + wb_keys.erase(position); + } else { + fprintf(stderr,"expected %s, got nuthin\n",op->key.c_str()); + } + } if (evict->evicted) { if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { - issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, 2, ITEM_INCL | ITEM_DIRTY, 0, 1); + //strncpy(wb_key,evict->evictedKey,255); + string wb_key(evict->evictedKey); + wb_keys.push_back(wb_key); + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | ITEM_DIRTY | LOG_OP | SRC_WB); + //fprintf(stderr,"incl writeback %s\n",evict->evictedKey); this->stats.incl_wbs++; } else if (evict->evictedFlags & ITEM_EXCL) { - issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, 2, ITEM_EXCL, 0, 1); + //fprintf(stderr,"excl writeback %s\n",evict->evictedKey); + //strncpy(wb_key,evict->evictedKey,255); + string wb_key(evict->evictedKey); + if (options.rand_admit && wb == 0) { + wb_keys.push_back(wb_key); + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + } else if (options.threshold && (key_hist[wb_key] >= options.threshold)) { + wb_keys.push_back(wb_key); + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + } else if (options.wb_all) { + wb_keys.push_back(wb_key); + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + } this->stats.excl_wbs++; } - if (((evict->serverFlags & ITEM_DIRTY) == 0) && (op->log == 1)) { - //we sent a SET request to l1, the item that was set is new since it didn't not get - //marked as dirty - this->stats.set_misses_l1++; + if (OP_src(op) == SRC_DIRECT_SET) { + if ( (evict->serverFlags & ITEM_SIZE_CHANGE) || ((evict->serverFlags & ITEM_WAS_HIT) == 0)) { + this->stats.set_misses_l1++; + } else if (OP_excl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_excl_hits_l1++; + } else if (OP_incl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_incl_hits_l1++; + } } - - if (op->incl == 2) { - //if exclusive, remove from l2 - //here, we sent a set to l1 - issue_delete(op->key.c_str(),now,2,op->log); + } + /* + if (OP_incl(op) && (OP_src(op) == SRC_DIRECT_SET)) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + int valuelen = op->valuelen; + if (class_change) { + int index = lrand48() % (1024 * 1024); + issue_set(key, &random_char[index], valuelen, now, ITEM_L2 | ITEM_INCL | LOG_OP); + } else { } } - //if (op->incl == 1 && op->log == 1 && op->flags) { - // issue_touch(op->key.c_str(),op->valuelen,0,2); - //} + */ finish_op(op,1); break; default: @@ -1427,38 +1499,36 @@ void ConnectionMulti::read_callback2() { if ( !found && (options.getset || options.getsetorset) ) {// && //(options.twitter_trace != 1)) { char key[256]; - string keystr = op->key; - strcpy(key, keystr.c_str()); + memset(key,0,256); + strncpy(key, op->key.c_str(),255); int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); - int incl = op->incl; - int flags = 0; - SET_INCL(incl,flags); - finish_op(op,0); // sets read_state = IDLE - issue_set(key, &random_char[index], valuelen, now, 1, flags, 0, 1); + int flags = OP_clu(op) | SRC_L2_M | LOG_OP; + wb_keys.push_back(op->key); + issue_set(key, &random_char[index], valuelen, now, flags | ITEM_L1); last_quiet1 = false; - if (incl == 1) { - issue_set(key, &random_char[index], valuelen, now, 2, flags, 0, 1); + if (OP_incl(op)) { + wb_keys.push_back(op->key); + issue_set(key, &random_char[index], valuelen, now, flags | ITEM_L2); last_quiet2 = false; } + finish_op(op,0); // sets read_state = IDLE } else { if (found) { char key[256]; - string keystr = op->key; - strcpy(key, keystr.c_str()); + memset(key,0,256); + strncpy(key, op->key.c_str(),255); int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); - int incl = op->incl; - int flags = 0; - SET_INCL(incl,flags); + int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; //found in l2, set in l1 - issue_set(key, &random_char[index],valuelen, now, 1, flags, 0, 0); - //if (incl == 2) { - // //if exclusive, remove from l2 - // issue_delete(key,now,2,0); - //} + wb_keys.push_back(op->key); + issue_set(key, &random_char[index],valuelen, now, flags); this->stats.copies_to_l1++; + //if (OP_excl(op)) { + // issue_delete(key,now, ITEM_L2 | SRC_L1_COPY ); + //} finish_op(op,1); } else { @@ -1472,29 +1542,59 @@ void ConnectionMulti::read_callback2() { } break; case Operation::SET: + if (OP_src(op) == SRC_WB || OP_src(op) == SRC_L2_M) { + vector::iterator position = std::find(wb_keys.begin(), wb_keys.end(), op->key); + if (position != wb_keys.end()) { + wb_keys.erase(position); + } else { + fprintf(stderr,"expected wb %s, got nuthin\n",op->key.c_str()); + } + } finish_op(op,1); break; case Operation::TOUCH: - if (!found) { - //char key[256]; - //string keystr = op->key; - //strcpy(key, keystr.c_str()); - //int valuelen = op->valuelen; - //int index = lrand48() % (1024 * 1024); - //int incl = op->incl; - //int flags = 0; - //SET_INCL(incl,flags); - //// not found in l2, set in l2 - //issue_set(key, &random_char[index],valuelen, 0, flags, 2, 0, 1); - finish_op(op,0); - } else { - finish_op(op,1); + if (!found && (OP_src(op) == SRC_DIRECT_SET)) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + wb_keys.push_back(op->key); + issue_set(key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP | SRC_L2_M); + this->stats.set_misses_l2++; } + //if (!found) { + // //int incl = op->incl; + // //int flags = 0; + // //SET_INCL(incl,flags); + // //// not found in l2, set in l2 + // char key[256]; + // memset(key,0,256); + // strncpy(key, op->key.c_str(),255); + // int valuelen = op->valuelen; + // int index = lrand48() % (1024 * 1024); + // if (OP_src(op) == SRC_DIRECT_SET) { + // issue_set(key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP); + // this->stats.set_misses_l2++; + // } + // //if (OP_src(op) == SRC_L1_H) { + // // fprintf(stderr,"expected op in l2: %s\n",key); + // //} + // finish_op(op,0); + //} else { + // finish_op(op,1); + //} + finish_op(op,0); break; case Operation::DELETE: //check to see if it was a hit - if (!found && op->log) { - this->stats.set_misses_l2++; + //fprintf(stderr," del %s -- %d from %d\n",op->key.c_str(),found,OP_src(op)); + if (OP_src(op) == SRC_DIRECT_SET) { + if (found) { + this->stats.delete_hits_l2++; + } else { + this->stats.delete_misses_l2++; + } } finish_op(op,1); break; @@ -1559,11 +1659,15 @@ void bev_read_cb1(struct bufferevent *bev, void *ptr) { conn->read_callback1(); } + void bev_read_cb2(struct bufferevent *bev, void *ptr) { ConnectionMulti* conn = (ConnectionMulti*) ptr; conn->read_callback2(); } +void bev_write_cb_m(struct bufferevent *bev, void *ptr) { +} + void timer_cb_m(evutil_socket_t fd, short what, void *ptr) { ConnectionMulti* conn = (ConnectionMulti*) ptr; conn->timer_callback(); diff --git a/ConnectionOptions.h b/ConnectionOptions.h index a9874e4..a33940c 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -5,6 +5,9 @@ typedef struct { int apps; + int rand_admit; + int threshold; + int wb_all; bool miss_through; int connections; bool blocking; diff --git a/ConnectionStats.h b/ConnectionStats.h index 7125b87..b884f13 100644 --- a/ConnectionStats.h +++ b/ConnectionStats.h @@ -47,6 +47,9 @@ class ConnectionStats { set_misses_l1(0), set_misses_l2(0), excl_wbs(0), incl_wbs(0), copies_to_l1(0), + delete_misses_l2(0), + delete_hits_l2(0), + set_incl_hits_l1(0),set_excl_hits_l1(0), window_gets(0), window_sets(0), window_accesses(0), window_get_misses(0), skips(0), sampling(_sampling) {} @@ -87,6 +90,9 @@ class ConnectionStats { uint64_t set_misses_l1, set_misses_l2; uint64_t excl_wbs, incl_wbs; uint64_t copies_to_l1; + uint64_t delete_misses_l2; + uint64_t delete_hits_l2; + uint64_t set_incl_hits_l1, set_excl_hits_l1; uint64_t window_gets, window_sets, window_accesses, window_get_misses; uint64_t skips; @@ -94,7 +100,7 @@ class ConnectionStats { bool sampling; - void log_get(Operation& op) { if (sampling) get_sampler.sample(op); window_gets++; gets++; } + void log_get(Operation& op) { if (sampling) get_sampler.sample(op); } //window_gets++; gets++; } void log_set(Operation& op) { if (sampling) set_sampler.sample(op); window_sets++; sets++; } void log_get_l1(Operation& op) { if (sampling) get_l1_sampler.sample(op); window_gets++; gets_l1++; } @@ -185,6 +191,10 @@ class ConnectionStats { excl_wbs += cs.excl_wbs; incl_wbs += cs.incl_wbs; copies_to_l1 += cs.copies_to_l1; + delete_misses_l2 += cs.delete_misses_l2; + delete_hits_l2 += cs.delete_hits_l2; + set_excl_hits_l1 += cs.set_excl_hits_l1; + set_incl_hits_l1 += cs.set_incl_hits_l1; skips += cs.skips; start = cs.start; @@ -209,6 +219,10 @@ class ConnectionStats { excl_wbs += as.excl_wbs; incl_wbs += as.incl_wbs; copies_to_l1 += as.copies_to_l1; + delete_misses_l2 += as.delete_misses_l2; + delete_hits_l2 += as.delete_hits_l2; + set_excl_hits_l1 += as.set_excl_hits_l1; + set_incl_hits_l1 += as.set_incl_hits_l1; skips += as.skips; start = as.start; diff --git a/Operation.h b/Operation.h index 97b7417..44cfc3d 100644 --- a/Operation.h +++ b/Operation.h @@ -18,10 +18,10 @@ class Operation { int valuelen; uint32_t opaque; - uint32_t l1opaque; + uint32_t flags; uint16_t clsid; - uint16_t flags; string key; + Operation *l1; double time() const { return (end_time - start_time) * 1000000; } }; diff --git a/cmdline.ggo b/cmdline.ggo index 2f5b44d..c33f3f3 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -36,6 +36,9 @@ option "assoc" - "We create hash tables by taking the truncating the \ option "qps" q "Target aggregate QPS. 0 = peak QPS." int default="0" option "time" t "Maximum time to run (seconds)." int default="5" option "apps" - "Number of apps, should eqaul total conns" int default="1" +option "rand_admit" - "random admission to nvm" int default="0" +option "wb_all" - "all admission to nvm" int default="1" +option "threshold" - "admission to nvm if in top n" int default="0" option "miss_through" - "All sets are considered dirty, expect for miss driven sets" option "read_file" - "Read keys from file." string default="" diff --git a/mutilate.cc b/mutilate.cc index 18e897d..c3130bf 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -7,6 +7,9 @@ #include #include #include +#include +#include /* Added for the nonblocking socket */ +#include #include #include @@ -454,6 +457,7 @@ string name_to_ipaddr(string host) { } int main(int argc, char **argv) { + //event_enable_debug_mode(); if (cmdline_parser(argc, argv, &args) != 0) exit(-1); for (unsigned int i = 0; i < args.verbose_given; i++) @@ -630,28 +634,49 @@ int main(int argc, char **argv) { if (!args.scan_given && !args.loadonly_given) { stats.print_header(); - stats.print_stats("read", stats.get_sampler); - stats.print_stats("update", stats.set_sampler); - stats.print_stats("op_q", stats.op_sampler); + stats.print_stats("read ", stats.get_sampler); + stats.print_stats("read_l1 ", stats.get_l1_sampler); + stats.print_stats("read_l2 ", stats.get_l2_sampler); + stats.print_stats("update_l1", stats.set_l1_sampler); + stats.print_stats("update_l2", stats.set_l2_sampler); + stats.print_stats("op_q ", stats.op_sampler); int total = stats.gets_l1 + stats.gets_l2 + stats.sets_l1 + stats.sets_l2; printf("\nTotal QPS = %.1f (%d / %.1fs)\n", total / (stats.stop - stats.start), total, stats.stop - stats.start); + + int rtotal = stats.gets + stats.sets; + printf("\nTotal RPS = %.1f (%d / %.1fs)\n", + rtotal / (stats.stop - stats.start), + rtotal, stats.stop - stats.start); if (args.search_given && peak_qps > 0.0) printf("Peak QPS = %.1f\n", peak_qps); printf("\n"); - printf("Misses = %" PRIu64 " (%.1f%%)\n", stats.get_misses, + printf("GET Misses = %" PRIu64 " (%.1f%%)\n", stats.get_misses, (double) stats.get_misses/(stats.gets)*100); if (servers.size() == 2) { - printf("Misses (L1) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l1 + stats.set_misses_l1, - (double) (stats.get_misses_l1 + stats.set_misses_l1) /(stats.gets + stats.sets)*100); - printf("Misses (L2) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l2 + stats.set_misses_l2, - (double) (stats.get_misses_l2 + stats.set_misses_l2) /(stats.gets + stats.sets)*100); + int64_t additional = 0; + if (stats.delete_misses_l2 > 0) { + additional = stats.delete_misses_l2 - stats.set_excl_hits_l1; + fprintf(stderr,"delete misses_l2 %lu, delete hits_l2 %lu, excl_set_l1_hits: %lu\n",stats.delete_misses_l2,stats.delete_hits_l2,stats.set_excl_hits_l1); + if (additional < 0) { + fprintf(stderr,"additional misses is neg! %ld\n",additional); + additional = 0; + } + } + //printf("Misses (L1) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l1 + stats.set_misses_l1, + // (double) (stats.get_misses_l1 + stats.set_misses_l1) /(stats.gets + stats.sets)*100); + printf("Misses (L1) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l1 , + (double) (stats.get_misses_l1) /(stats.gets)*100); + printf("SET Misses (L1) = %" PRIu64 " (%.1f%%)\n", stats.set_misses_l1 , + (double) (stats.set_misses_l1) /(stats.sets)*100); + //printf("Misses (L2) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l2, + // (double) (stats.get_misses_l2) /(stats.gets)*100); printf("L2 Writes = %" PRIu64 " (%.1f%%)\n", stats.sets_l2, (double) stats.sets_l2/(stats.gets+stats.sets)*100); @@ -988,8 +1013,9 @@ void* reader_thread(void *arg) { stringstream ss(full_line); string rT; string rApp; + string rKey; + string rOp; if (twitter_trace == 1) { - string rKey; string rKeySize; string rvaluelen; size_t n = std::count(full_line.begin(), full_line.end(), ','); @@ -1010,6 +1036,8 @@ void* reader_thread(void *arg) { if (n == 4) { getline( ss, rT, ','); getline( ss, rApp, ','); + getline( ss, rOp, ',' ); + getline( ss, rKey, ',' ); appid = (stoi(rApp)) % trace_queue->size(); } else { continue; @@ -1121,7 +1149,6 @@ void do_mutilate(const vector& servers, options_t& options, struct event_base *base; struct evdns_base *evdns; struct event_config *config; - //event_enable_debug_mode(); if ((config = event_config_new()) == NULL) DIE("event_config_new() fail"); @@ -1141,8 +1168,6 @@ void do_mutilate(const vector& servers, options_t& options, // event_base_priority_init(base, 2); // FIXME: May want to move this to after all connections established. - double start = get_time(); - double now = start; if (servers.size() == 1) { @@ -1177,9 +1202,7 @@ void do_mutilate(const vector& servers, options_t& options, int connected = 0; int s = 2; for (int i = 0; i < tries; i++) { - pthread_mutex_lock(&flock); int ret = conn->do_connect(); - pthread_mutex_unlock(&flock); if (ret) { connected = 1; fprintf(stderr,"thread %lu, conn: %d, connected!\n",pthread_self(),c+1); @@ -1203,6 +1226,8 @@ void do_mutilate(const vector& servers, options_t& options, if (c == 0) server_lead.push_back(conn); } } + double start = get_time(); + double now = start; // Wait for all Connections to become IDLE. while (1) { @@ -1413,25 +1438,71 @@ void do_mutilate(const vector& servers, options_t& options, srand(time(NULL)); for (int c = 0; c < conns; c++) { - ConnectionMulti* conn = new ConnectionMulti(base, evdns, - hostname1, hostname2, port, options,args.agentmode_given ? false : true); - int tries = 120; - int connected = 0; + + int fd1 = -1; + + if ( (fd1 = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket error"); + exit(-1); + } + + struct sockaddr_un sin1; + memset(&sin1, 0, sizeof(sin1)); + sin1.sun_family = AF_LOCAL; + strcpy(sin1.sun_path, hostname1.c_str()); + + fcntl(fd1, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state */ + int addrlen; + addrlen = sizeof(sin1); + + int max_tries = 13; + int n_tries = 0; int s = 2; - for (int i = 0; i < tries; i++) { - pthread_mutex_lock(&flock); - int ret = conn->do_connect(); - pthread_mutex_unlock(&flock); - if (ret) { - connected = 1; - fprintf(stderr,"thread %lu, multi conn: %d, connected!\n",pthread_self(),c+1); - break; + while (connect(fd1, (struct sockaddr*)&sin1, addrlen) == -1) { + perror("l1 connect error"); + if (n_tries++ > max_tries) { + exit(-1); + } + int d = s + rand() % 10; + sleep(d); + s += 4; + } + + int fd2 = -1; + if ( (fd2 = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("l2 socket error"); + exit(-1); + } + struct sockaddr_un sin2; + memset(&sin2, 0, sizeof(sin2)); + sin2.sun_family = AF_LOCAL; + strcpy(sin2.sun_path, hostname2.c_str()); + fcntl(fd2, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state */ + addrlen = sizeof(sin2); + n_tries = 0; + s = 2; + while (connect(fd2, (struct sockaddr*)&sin2, addrlen) == -1) { + perror("l2 connect error"); + if (n_tries++ > max_tries) { + exit(-1); } int d = s + rand() % 10; sleep(d); - } + s += 4; + } + + + ConnectionMulti* conn = new ConnectionMulti(base, evdns, + hostname1, hostname2, port, options,args.agentmode_given ? false : true, fd1, fd2); + + int connected = 0; + if (conn) { + connected = 1; + } int cid = conn->get_cid(); + if (connected) { + fprintf(stderr,"cid %d gets l1 fd %d l2 fd %d\n",cid,fd1,fd2); fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front().c_str()); conn->set_queue(trace_queue->at(cid)); conn->set_lock(mutexes->at(cid)); @@ -1440,7 +1511,6 @@ void do_mutilate(const vector& servers, options_t& options, fprintf(stderr,"conn multi: %d, not connected!!\n",c); } - if (c == 0) server_lead.push_back(conn); } // Wait for all Connections to become IDLE. @@ -1456,149 +1526,36 @@ void do_mutilate(const vector& servers, options_t& options, if (restart) continue; else break; } - - - // FIXME: Remove. Not needed, testing only. - // // FIXME: Synchronize start_time here across threads/nodes. - // pthread_barrier_wait(&barrier); - - // Warmup connection. - if (options.warmup > 0) { - if (master) V("Warmup start."); - -#ifdef HAVE_LIBZMQ - if (args.agent_given || args.agentmode_given) { - if (master) V("Synchronizing."); - - // 1. thread barrier: make sure our threads ready before syncing agents - // 2. sync agents: all threads across all agents are now ready - // 3. thread barrier: don't release our threads until all agents ready - pthread_barrier_wait(&barrier); - if (master) sync_agent(socket); - pthread_barrier_wait(&barrier); - - if (master) V("Synchronized."); - } -#endif - - int old_time = options.time; - // options.time = 1; - - start = get_time(); - for (ConnectionMulti *conn: connections) { + + double start = get_time(); + double now = start; + for (ConnectionMulti *conn: connections) { conn->start_time = start; - conn->options.time = options.warmup; conn->start(); // Kick the Connection into motion. - } - - while (1) { - event_base_loop(base, loop_flag); - - //#ifdef USE_CLOCK_GETTIME - // now = get_time(); - //#else - struct timeval now_tv; - event_base_gettimeofday_cached(base, &now_tv); - now = tv_to_double(&now_tv); - //#endif - - bool restart = false; - for (ConnectionMulti *conn: connections) - if (!conn->check_exit_condition(now)) - restart = true; - - if (restart) continue; - else break; - } - - bool restart = false; - for (ConnectionMulti *conn: connections) - if (!conn->is_ready()) restart = true; - - if (restart) { - - // Wait for all Connections to become IDLE. - while (1) { - // FIXME: If there were to use EVLOOP_ONCE and all connections - // become ready before event_base_loop is called, this will - // deadlock. We should check for IDLE before calling - // event_base_loop. - event_base_loop(base, EVLOOP_ONCE); // EVLOOP_NONBLOCK); - - bool restart = false; - for (ConnectionMulti *conn: connections) - if (!conn->is_ready()) restart = true; - - if (restart) continue; - else break; - } - } - - for (ConnectionMulti *conn: connections) { - conn->reset(); - conn->options.time = old_time; - } - - if (master) V("Warmup stop."); - } - - - // FIXME: Synchronize start_time here across threads/nodes. - pthread_barrier_wait(&barrier); - - if (master && args.wait_given) { - if (get_time() < boot_time + args.wait_arg) { - double t = (boot_time + args.wait_arg)-get_time(); - V("Sleeping %.1fs for -W.", t); - sleep_time(t); - } - } - -#ifdef HAVE_LIBZMQ - if (args.agent_given || args.agentmode_given) { - if (master) V("Synchronizing."); - - pthread_barrier_wait(&barrier); - if (master) sync_agent(socket); - pthread_barrier_wait(&barrier); - - if (master) V("Synchronized."); - } -#endif - - if (master && !args.scan_given && !args.search_given) - V("started at %f", get_time()); - - start = get_time(); - for (ConnectionMulti *conn: connections) { - conn->start_time = start; - conn->start(); // Kick the Connection into motion. - } - - // V("Start = %f", start); - fprintf(stderr,"Start = %f", start); + } + fprintf(stderr,"Start = %f\n", start); // Main event loop. while (1) { event_base_loop(base, loop_flag); - - //#if USE_CLOCK_GETTIME - // now = get_time(); - //#else struct timeval now_tv; event_base_gettimeofday_cached(base, &now_tv); now = tv_to_double(&now_tv); - //#endif bool restart = false; - for (ConnectionMulti *conn: connections) - if (!conn->check_exit_condition(now)) + for (ConnectionMulti *conn: connections) { + if (!conn->check_exit_condition(now)) { restart = true; - + } + } if (restart) continue; else break; + } + + // V("Start = %f", start); + if (master && !args.scan_given && !args.search_given) V("stopped at %f options.time = %d", get_time(), options.time); @@ -1626,6 +1583,9 @@ void args_to_options(options_t* options) { options->server_given = args.server_given; options->roundrobin = args.roundrobin_given; options->apps = args.apps_arg; + options->rand_admit = args.rand_admit_arg; + options->threshold = args.threshold_arg; + options->wb_all = args.wb_all_arg; if (args.inclusives_given) { memset(options->inclusives,0,256); strncpy(options->inclusives,args.inclusives_arg,256); From b02676c8a8d064be529ed0e248a17d73f10df801 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Thu, 7 Oct 2021 15:40:34 -0400 Subject: [PATCH 43/57] updated incl --- ConnectionMulti.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 339c5dd..3980f9e 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -508,9 +508,11 @@ int ConnectionMulti::issue_getsetorset(double now) { } int index = lrand48() % (1024 * 1024); wb_keys.push_back(string(key)); - issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); if (touch == 0 && incl == 1) { issue_touch(key,vl,now, ITEM_L2 | SRC_DIRECT_SET); + issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + } else { + issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); } last_quiet1 = false; this->stats.sets++; From eb4ed618479259f0565ec409dc186d13d166f40b Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Tue, 14 Dec 2021 17:43:31 -0500 Subject: [PATCH 44/57] updates --- Connection.h | 11 +- ConnectionMulti.cc | 514 ++++++++++++++++++++++++--------------------- ConnectionStats.h | 7 + Operation.h | 2 + SConstruct | 4 +- mutilate.cc | 228 +++++++++++++++----- 6 files changed, 479 insertions(+), 287 deletions(-) diff --git a/Connection.h b/Connection.h index 8aae444..8e3aba5 100644 --- a/Connection.h +++ b/Connection.h @@ -214,7 +214,10 @@ class ConnectionMulti { int eof; uint32_t get_cid(); //void set_queue(ConcurrentQueue *a_trace_queue); - void set_queue(queue *a_trace_queue); + int add_to_wb_keys(string wb_key); + void del_wb_keys(string wb_key); + void set_g_wbkeys(unordered_map *a_wb_keys); + void set_queue(queue *a_trace_queue); void set_lock(pthread_mutex_t* a_lock); private: @@ -222,6 +225,8 @@ class ConnectionMulti { string hostname2; string port; + double o_percent; + int trace_queue_n; struct event_base *base; struct evdns_base *evdns; struct bufferevent *bev1; @@ -279,8 +284,8 @@ class ConnectionMulti { KeyGenerator *keygen; Generator *iagen; pthread_mutex_t* lock; - //ConcurrentQueue *trace_queue; - queue *trace_queue; + unordered_map *g_wb_keys; + queue *trace_queue; // state machine functions / event processing void pop_op(Operation *op); diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 3980f9e..28bd84a 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -109,6 +109,8 @@ typedef struct _evicted_type { char *evictedData; } evicted_t; +static vector cid_rate; + extern int max_n[3]; static void init_inclusives(char *inclusive_str) { @@ -128,7 +130,7 @@ static void init_classes() { double factor = 1.25; unsigned int chunk_size = 48; unsigned int item_size = 24; - unsigned int size = item_size + chunk_size; + unsigned int size = 96; //warning if you change this you die unsigned int i = 0; unsigned int chunk_size_max = 1048576/2; while (++i < NCLASSES-1) { @@ -146,11 +148,12 @@ static void init_classes() { } static int get_class(int vl, uint32_t kl) { - int vsize = vl+kl+24+1+2; + //warning if you change this you die + int vsize = vl+kl+48+1+2; int res = 1; while (vsize > sizes[res]) if (res++ == classes) { - fprintf(stderr,"item larger than max class size. vsize: %d, class size: %d\n",vsize,sizes[res]); + //fprintf(stderr,"item larger than max class size. vsize: %d, class size: %d\n",vsize,sizes[res]); return -1; } return res; @@ -158,7 +161,11 @@ static int get_class(int vl, uint32_t kl) { static int get_incl(int vl, int kl) { int clsid = get_class(vl,kl); - return inclusives[clsid]; + if (clsid) { + return inclusives[clsid]; + } else { + return -1; + } } void ConnectionMulti::output_op(Operation *op, int type, bool found) { @@ -224,8 +231,19 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _e start_time(0), stats(sampling), options(_options), hostname1(_hostname1), hostname2(_hostname2), port(_port), base(_base), evdns(_evdns) { - init_classes(); - init_inclusives(options.inclusives); + pthread_mutex_lock(&cid_lock_m); + cid = connids_m++; + if (cid == 1) { + cid_rate.push_back(100); + cid_rate.push_back(0); + init_classes(); + init_inclusives(options.inclusives); + } else { + cid_rate.push_back(0); + } + + pthread_mutex_unlock(&cid_lock_m); + valuesize = createGenerator(options.valuesize); keysize = createGenerator(options.keysize); srand(time(NULL)); @@ -233,6 +251,7 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _e total = 0; eof = 0; + o_percent = 0; if (options.lambda <= 0) { iagen = createGenerator("0"); @@ -249,9 +268,6 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _e last_tx = last_rx = 0.0; - pthread_mutex_lock(&cid_lock_m); - cid = connids_m++; - pthread_mutex_unlock(&cid_lock_m); op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); @@ -279,18 +295,56 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _e } -void ConnectionMulti::set_queue(queue* a_trace_queue) { +void ConnectionMulti::set_queue(queue* a_trace_queue) { trace_queue = a_trace_queue; + trace_queue_n = a_trace_queue->size(); } void ConnectionMulti::set_lock(pthread_mutex_t* a_lock) { lock = a_lock; } +void ConnectionMulti::set_g_wbkeys(unordered_map *a_wb_keys) { + g_wb_keys = a_wb_keys; +} + uint32_t ConnectionMulti::get_cid() { return cid; } +int ConnectionMulti::add_to_wb_keys(string key) { + int ret = -1; + pthread_mutex_lock(lock); + auto pos = g_wb_keys->find(key); + if (pos == g_wb_keys->end()) { + g_wb_keys->insert( {key,cid }); + ret = 1; + //fprintf(stderr,"----set: %s----\n",Op.key.c_str()); + //for (auto iter = g_wb_keys->begin(); iter != g_wb_keys->end(); ++iter){ + // fprintf(stderr,"%s,%d\n",iter->first.c_str(),iter->second); + //} + //fprintf(stderr,"----%d----\n",cid); + } else { + ret = 2; + } + + pthread_mutex_unlock(lock); + return ret; +} + +void ConnectionMulti::del_wb_keys(string key) { + + pthread_mutex_lock(lock); + auto position = g_wb_keys->find(key); + if (position != g_wb_keys->end()) { + g_wb_keys->erase(position); + } else { + fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); + } + pthread_mutex_unlock(lock); +} + + int ConnectionMulti::do_connect() { int connected = 0; @@ -390,152 +444,156 @@ void ConnectionMulti::set_priority(int pri) { int ConnectionMulti::issue_getsetorset(double now) { - string line; - string rT; - string rApp; - string rOp; - string rKey; - string rKeySize; - string rvaluelen; int ret = 0; int nissued = 0; - while (nissued < options.depth) { + //while (nissued < options.depth) { + + //pthread_mutex_lock(lock); + if (!trace_queue->empty()) { + Operation Op = trace_queue->front(); + if (Op.type == Operation::SASL) { + eof = 1; + cid_rate[cid] = 100; + fprintf(stderr,"cid %d done\n",cid); + string op_queue1; + string op_queue2; + for (int j = 0; j < 2; j++) { + for (int i = 0; i < OPAQUE_MAX; i++) { + if (op_queue[j+1][i] != NULL) { + if (j == 0) { + op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + } else { + op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + } + } + } + } + fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + return 1; + } - //pthread_mutex_lock(lock); - if (!trace_queue->empty()) { - line = trace_queue->front(); - if (line.compare("EOF") == 0) { - eof = 1; - fprintf(stderr,"cid %d done\n",cid); - return 1; - } - stringstream ss(line); - //pthread_mutex_unlock(lock); - int Op = 0; - int vl = 0; - - if (options.twitter_trace == 1) { - getline( ss, rT, ',' ); - getline( ss, rKey, ',' ); - getline( ss, rKeySize, ',' ); - getline( ss, rvaluelen, ',' ); - getline( ss, rApp, ',' ); - getline( ss, rOp, ',' ); - vl = stoi(rvaluelen); - if (vl < 1) continue; - if (vl > 524000) vl = 524000; - if (rOp.compare("get") == 0) { - Op = 1; - } else if (rOp.compare("set") == 0) { - Op = 2; - } else { - Op = 0; + /* check if in global wb queue */ + pthread_mutex_lock(lock); + double percent = (double)total/((double)trace_queue_n) * 100; + if (percent > o_percent+1) { + //update the percentage table and see if we should execute + std::vector::iterator mp = std::min_element(cid_rate.begin(), cid_rate.end()); + double min_percent = *mp; + + if (percent > min_percent+2) { + pthread_mutex_unlock(lock); + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + int good = 0; + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + good = evtimer_add(timer, &tv); } - - - } else if (options.twitter_trace == 2) { - getline( ss, rT, ',' ); - getline( ss, rApp, ',' ); - getline( ss, rOp, ',' ); - getline( ss, rKey, ',' ); - getline( ss, rvaluelen, ',' ); - Op = stoi(rOp); - vl = stoi(rvaluelen); - if (vl < 1 || vl > 524000) { - fprintf(stderr,"bad val for line: %s, %d\n",line.c_str(),vl); + if (good != 0) { + fprintf(stderr,"eventimer is messed up!\n"); + return 2; } - } else { - getline( ss, rT, ',' ); - getline( ss, rApp, ',' ); - getline( ss, rOp, ',' ); - getline( ss, rKey, ',' ); - getline( ss, rvaluelen, ',' ); - vl = stoi(rvaluelen); - if (rOp.compare("read") == 0) - Op = 1; - if (rOp.compare("write") == 0) - Op = 2; - } - - - - if ((int)wb_keys.size() > options.depth*10) { - fprintf(stderr,"wb_queue size %d\n",(int)wb_keys.size()); return 1; } - /* if this key is currently being written back wait for it to clear */ - if (find(wb_keys.begin(), wb_keys.end(), rKey) != wb_keys.end()) { - return 1; - } - - char key[256]; - memset(key,0,256); - strncpy(key, rKey.c_str(),255); - - trace_queue->pop(); - int issued = 0; - int incl = get_incl(vl,strlen(key)); - int flags = 0; - int touch = (rand() % 100); - SET_INCL(incl,flags); - switch(Op) - { - case 0: - //fprintf(stderr,"invalid line: %s, vl: %d @T: %d\n", - // key,vl,stoi(rT)); - break; - case 1: - //if (nissued < options.depth-1) { - // issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); - // last_quiet1 = false; - //} else { - //} - if (options.threshold > 0) { - key_hist[key]++; - } - issued = issue_get_with_len(key, vl, now, false, flags | LOG_OP | ITEM_L1); - if (touch == 0 && incl == 1) { - issue_touch(key,vl,now, ITEM_L2 | SRC_L1_H); - } - last_quiet1 = false; - this->stats.gets++; - break; - case 2: - if (last_quiet1) { - issue_noop(now,1); - } - int index = lrand48() % (1024 * 1024); - wb_keys.push_back(string(key)); - if (touch == 0 && incl == 1) { - issue_touch(key,vl,now, ITEM_L2 | SRC_DIRECT_SET); - issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); - } else { - issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); - } - last_quiet1 = false; - this->stats.sets++; - break; - + cid_rate[cid] = percent; + fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); + o_percent = percent; + } + auto check = g_wb_keys->find(Op.key); + if (check != g_wb_keys->end()) { + pthread_mutex_unlock(lock); + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + int good = 0; + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + good = evtimer_add(timer, &tv); } - if (issued) { - nissued++; - total++; - } else { - if (Op != 0) { - fprintf(stderr,"failed to issue line: %s, vl: %d @T: %d\n", - key,vl,stoi(rT)); - } - break; + if (good != 0) { + fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op.key.c_str()); + return 2; } + return 1; } else { - //pthread_mutex_unlock(lock); - //we should protect this with a condition variable - //since trace queue size is 0 and not EOF. - return 0; + g_wb_keys->insert( {Op.key, cid} ); + //g_wb_keys->insert( {Op.key+"l2", cid} ); } + pthread_mutex_unlock(lock); + + + + char key[256]; + memset(key,0,256); + strncpy(key, Op.key.c_str(),255); + int vl = Op.valuelen; + + trace_queue->pop(); + + int issued = 0; + int incl = get_incl(vl,strlen(key)); + int cid = get_class(vl,strlen(key)); + int flags = 0; + int touch = (rand() % 100); + int index = lrand48() % (1024 * 1024); + //int touch = 1; + SET_INCL(incl,flags); + + switch(Op.type) + { + case Operation::GET: + //if (nissued < options.depth-1) { + // issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); + // last_quiet1 = false; + //} else { + //} + if (options.threshold > 0) { + if (Op.future) { + key_hist[key] = 1; + } + } + issued = issue_get_with_len(key, vl, now, false, flags | LOG_OP | ITEM_L1); + if (touch == 1 && incl == 1) { + issue_touch(key,vl,now, ITEM_L2 | SRC_L1_H); + } + last_quiet1 = false; + this->stats.gets++; + this->stats.gets_cid[cid]++; + + break; + case Operation::SET: + if (last_quiet1) { + issue_noop(now,1); + } + if (incl == 1) { + issue_touch(key,vl,now, ITEM_L2 | SRC_DIRECT_SET); + } else if (incl == 2) { + issue_delete(key,now, ITEM_L2 | SRC_DIRECT_SET ); + } + issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + last_quiet1 = false; + this->stats.sets++; + this->stats.sets_cid[cid]++; + break; + case Operation::DELETE: + case Operation::TOUCH: + case Operation::NOOP: + case Operation::SASL: + fprintf(stderr,"invalid line: %s, vl: %d\n",key,vl); + break; + + } + if (issued) { + nissued++; + total++; + } else { + fprintf(stderr,"failed to issue line: %s, vl: %d @T: XX \n",key,vl); + } + } else { + return 1; } + //} if (last_quiet1) { issue_noop(now,1); last_quiet1 = false; @@ -595,7 +653,7 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no op_queue_size[level]++; #ifdef DEBUGS - fprintf(stderr,"issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",key,valuelen,level,flags,pop->opaque); + fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,valuelen,level,flags,pop->opaque); #endif if (opaque[level] > OPAQUE_MAX) { @@ -681,6 +739,9 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int h.opaque = htonl(pop->opaque); uint32_t exp = 0; + if (flags & ITEM_DIRTY) { + exp = htonl(flags); + } evbuffer_add(output, &h, 24); evbuffer_add(output, &exp, 4); evbuffer_add(output, key, keylen); @@ -738,6 +799,9 @@ int ConnectionMulti::issue_delete(const char* key, double now, uint32_t flags) { if (opaque[level] > OPAQUE_MAX) { opaque[level] = 1; } +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing delete: %s, level %d, flags: %d, opaque: %d\n",cid,key,level,flags,pop->opaque); +#endif //if (read_state == IDLE) read_state = WAITING_FOR_GET; uint16_t keylen = strlen(key); @@ -818,7 +882,7 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, d //op_queue[level].push(op); op_queue_size[level]++; #ifdef DEBUGS - fprintf(stderr,"issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",key,length,level,flags,pop->opaque); + fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,length,level,flags,pop->opaque); #endif if (opaque[level] > OPAQUE_MAX) { @@ -956,12 +1020,14 @@ void ConnectionMulti::finish_op(Operation *op, int was_hit) { uint8_t level = OP_level(op); if (op->l1 != NULL) { delete op_queue[1][op->l1->opaque]; + op_queue[1][op->l1->opaque] = 0; op_queue_size[1]--; } //op_queue[level].erase(op_queue[level].begin()+opopq); if (op == op_queue[level][op->opaque] && op->opaque == op_queue[level][op->opaque]->opaque) { delete op_queue[level][op->opaque]; + op_queue[level][op->opaque] = 0; } else { fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", op,op_queue[level][op->opaque],op->opaque,op_queue[level][op->opaque]->opaque); @@ -978,38 +1044,10 @@ void ConnectionMulti::finish_op(Operation *op, int was_hit) { * Check if our testing is done and we should exit. */ bool ConnectionMulti::check_exit_condition(double now) { - if (eof) { + if (eof && op_queue_size[1] == 0 && op_queue_size[2] == 0) { return true; } if (read_state == INIT_READ) return false; - if (now == 0.0) now = get_time(); - - if (options.read_file) { - if (eof) { - return true; - } - else if ((options.queries == 1) && - (now > start_time + options.time)) - { - return true; - } - else { - return false; - } - - } else { - if (options.queries != 0 && - (((long unsigned)options.queries) == (stats.accesses))) - { - return true; - } - if ((options.queries == 0) && - (now > start_time + options.time)) - { - return true; - } - if (options.loadonly && read_state == IDLE) return true; - } return false; } @@ -1120,7 +1158,7 @@ void ConnectionMulti::drive_write_machine(double now) { if (options.getsetorset) { int ret = issue_getsetorset(now); - if (ret) return; //if at EOF + if (ret == 1) return; //if at EOF } last_tx = now; @@ -1136,6 +1174,10 @@ void ConnectionMulti::drive_write_machine(double now) { case WAITING_FOR_OPQ: if ( (op_queue_size[1] >= (size_t) options.depth) || (op_queue_size[2] >= (size_t) options.depth) ) { + //double delay = 0.01; + //struct timeval tv; + //double_to_tv(delay, &tv); + //evtimer_add(timer, &tv); return; } else { write_state = ISSUING; @@ -1176,7 +1218,7 @@ static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, opaque = ntohl(h->opaque); uint16_t status = ntohs(h->status); #ifdef DEBUGMC - fprintf(stderr,"handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",level, + fprintf(stderr,"cid: %d handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",conn->get_cid(),level, h->opcode,ntohl(h->opaque),ntohs(h->key_len),h->extra_len, ntohl(h->body_len),ntohs(h->status)); #endif @@ -1204,6 +1246,7 @@ static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, evbuffer_drain(input,24); unsigned char *buf = evbuffer_pullup(input,bl); + evict->clsid = *((uint32_t*)buf); evict->clsid = ntohl(evict->clsid); buf += 4; @@ -1334,11 +1377,8 @@ void ConnectionMulti::read_callback1() { //finish_op(op,0); } else { - if (found) { - finish_op(op,1); - } else { - finish_op(op,0); - } + del_wb_keys(op->key); + finish_op(op,found); } } else { char out[128]; @@ -1347,40 +1387,46 @@ void ConnectionMulti::read_callback1() { } break; case Operation::SET: - if (OP_src(op) == SRC_L1_COPY || - OP_src(op) == SRC_DIRECT_SET || - OP_src(op) == SRC_L2_M ) { - vector::iterator position = std::find(wb_keys.begin(), wb_keys.end(), op->key); - if (position != wb_keys.end()) { - wb_keys.erase(position); - } else { - fprintf(stderr,"expected %s, got nuthin\n",op->key.c_str()); - } - } + //if (OP_src(op) == SRC_L1_COPY || + // OP_src(op) == SRC_DIRECT_SET || + // OP_src(op) == SRC_L2_M ) { + //} if (evict->evicted) { + string wb_key(evict->evictedKey); if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { - //strncpy(wb_key,evict->evictedKey,255); - string wb_key(evict->evictedKey); - wb_keys.push_back(wb_key); - issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | ITEM_DIRTY | LOG_OP | SRC_WB); + //wb_keys.push_back(wb_key); + int ret = add_to_wb_keys(wb_key); + if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_WB); + } //fprintf(stderr,"incl writeback %s\n",evict->evictedKey); this->stats.incl_wbs++; } else if (evict->evictedFlags & ITEM_EXCL) { //fprintf(stderr,"excl writeback %s\n",evict->evictedKey); //strncpy(wb_key,evict->evictedKey,255); - string wb_key(evict->evictedKey); - if (options.rand_admit && wb == 0) { - wb_keys.push_back(wb_key); - issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); - } else if (options.threshold && (key_hist[wb_key] >= options.threshold)) { - wb_keys.push_back(wb_key); - issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); - } else if (options.wb_all) { - wb_keys.push_back(wb_key); - issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + if ( (options.rand_admit && wb == 0) || + (options.threshold && (key_hist[wb_key] == 1)) || + (options.wb_all) ) { + int ret = add_to_wb_keys(wb_key); + if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + } + this->stats.excl_wbs++; } - this->stats.excl_wbs++; } + /* + if (evict->serverFlags & ITEM_SIZE_CHANGE && OP_src(op) == SRC_DIRECT_SET) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + if (evict->serverFlags & ITEM_INCL) { + int index = lrand48() % (1024 * 1024); + int valuelen = op->valuelen; + //the item's size was changed, issue a SET to L2 as a new command + issue_set(key, &random_char[index], valuelen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_L2_M); + } + } + */ if (OP_src(op) == SRC_DIRECT_SET) { if ( (evict->serverFlags & ITEM_SIZE_CHANGE) || ((evict->serverFlags & ITEM_WAS_HIT) == 0)) { this->stats.set_misses_l1++; @@ -1391,19 +1437,10 @@ void ConnectionMulti::read_callback1() { } } } - /* - if (OP_incl(op) && (OP_src(op) == SRC_DIRECT_SET)) { - char key[256]; - memset(key,0,256); - strncpy(key, op->key.c_str(),255); - int valuelen = op->valuelen; - if (class_change) { - int index = lrand48() % (1024 * 1024); - issue_set(key, &random_char[index], valuelen, now, ITEM_L2 | ITEM_INCL | LOG_OP); - } else { - } - } - */ + del_wb_keys(op->key); + finish_op(op,1); + break; + case Operation::TOUCH: finish_op(op,1); break; default: @@ -1506,14 +1543,21 @@ void ConnectionMulti::read_callback2() { int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); int flags = OP_clu(op) | SRC_L2_M | LOG_OP; - wb_keys.push_back(op->key); issue_set(key, &random_char[index], valuelen, now, flags | ITEM_L1); + //wb_keys.push_back(op->key); last_quiet1 = false; if (OP_incl(op)) { - wb_keys.push_back(op->key); + //wb_keys.push_back(op->key); issue_set(key, &random_char[index], valuelen, now, flags | ITEM_L2); last_quiet2 = false; } + //pthread_mutex_lock(lock); + //fprintf(stderr,"----miss: %s----\n",key); + //for (auto iter = g_wb_keys->begin(); iter != g_wb_keys->end(); ++iter){ + // fprintf(stderr,"%s,%d\n",iter->first.c_str(),iter->second); + //} + //fprintf(stderr,"----%d----\n",cid); + //pthread_mutex_unlock(lock); finish_op(op,0); // sets read_state = IDLE } else { @@ -1525,7 +1569,7 @@ void ConnectionMulti::read_callback2() { int index = lrand48() % (1024 * 1024); int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; //found in l2, set in l1 - wb_keys.push_back(op->key); + //wb_keys.push_back(op->key); issue_set(key, &random_char[index],valuelen, now, flags); this->stats.copies_to_l1++; //if (OP_excl(op)) { @@ -1544,26 +1588,27 @@ void ConnectionMulti::read_callback2() { } break; case Operation::SET: - if (OP_src(op) == SRC_WB || OP_src(op) == SRC_L2_M) { - vector::iterator position = std::find(wb_keys.begin(), wb_keys.end(), op->key); - if (position != wb_keys.end()) { - wb_keys.erase(position); - } else { - fprintf(stderr,"expected wb %s, got nuthin\n",op->key.c_str()); - } + if (OP_src(op) == SRC_WB) { + del_wb_keys(op->key); } finish_op(op,1); break; case Operation::TOUCH: - if (!found && (OP_src(op) == SRC_DIRECT_SET)) { + if (OP_src(op) == SRC_DIRECT_SET) { char key[256]; memset(key,0,256); strncpy(key, op->key.c_str(),255); int valuelen = op->valuelen; - int index = lrand48() % (1024 * 1024); - wb_keys.push_back(op->key); - issue_set(key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP | SRC_L2_M); - this->stats.set_misses_l2++; + if (!found) { + int index = lrand48() % (1024 * 1024); + //int ret = add_to_wb_keys(op->key+"l2"); + //if (ret == 1) { + issue_set(key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP | SRC_L2_M); + //} + this->stats.set_misses_l2++; + } else { + issue_touch(key,valuelen,now, ITEM_L1 | SRC_L2_H | ITEM_DIRTY); + } } //if (!found) { // //int incl = op->incl; @@ -1637,7 +1682,8 @@ void ConnectionMulti::write_callback() { /** * Callback for timer timeouts. */ -void ConnectionMulti::timer_callback() { +void ConnectionMulti::timer_callback() { + //fprintf(stderr,"timer up: %d\n",cid); drive_write_machine(); } diff --git a/ConnectionStats.h b/ConnectionStats.h index b884f13..1c79ea4 100644 --- a/ConnectionStats.h +++ b/ConnectionStats.h @@ -49,6 +49,7 @@ class ConnectionStats { copies_to_l1(0), delete_misses_l2(0), delete_hits_l2(0), + gets_cid(40), sets_cid(40), set_incl_hits_l1(0),set_excl_hits_l1(0), window_gets(0), window_sets(0), window_accesses(0), window_get_misses(0), skips(0), sampling(_sampling) {} @@ -92,6 +93,8 @@ class ConnectionStats { uint64_t copies_to_l1; uint64_t delete_misses_l2; uint64_t delete_hits_l2; + vector gets_cid; + vector sets_cid; uint64_t set_incl_hits_l1, set_excl_hits_l1; uint64_t window_gets, window_sets, window_accesses, window_get_misses; uint64_t skips; @@ -174,6 +177,10 @@ class ConnectionStats { op_sampler.accumulate(cs.op_sampler); #endif + for (int i = 0; i < 40; i++) { + gets_cid[i] += cs.gets_cid[i]; + sets_cid[i] += cs.sets_cid[i]; + } rx_bytes += cs.rx_bytes; tx_bytes += cs.tx_bytes; gets += cs.gets; diff --git a/Operation.h b/Operation.h index 44cfc3d..e2faf10 100644 --- a/Operation.h +++ b/Operation.h @@ -20,6 +20,8 @@ class Operation { uint32_t opaque; uint32_t flags; uint16_t clsid; + uint32_t future; + uint32_t curr; string key; Operation *l1; diff --git a/SConstruct b/SConstruct index c6b5cef..65ffeba 100644 --- a/SConstruct +++ b/SConstruct @@ -51,8 +51,8 @@ env.Append(CPPFLAGS = ' -O3 -Wall -g') #env.Append(LDFLAGS = '-fsantize=address') #env.Append(CFLAGS = ' -O3 -Wall -g -fsantize=address') #env.Append(CPPFLAGS = ' -O3 -Wall -g -fsanitize=address') -#env.Append(CFLAGS = ' -O0 -Wall -g -fsantize=address') -#env.Append(CPPFLAGS = ' -O0 -Wall -g -fsanitize=address') +#env.Append(CFLAGS = ' -O0 -Wall -g') +#env.Append(CPPFLAGS = ' -O0 -Wall -g') #env.Append(CFLAGS = '-g -std=c++11 -D_GNU_SOURCE -static-libsan -fsanitize=address -I/u/dbyrne99/local/include' ) #env.Append(CCFLAGS = '-g -std=c++11 -D_GNU_SOURCE -static-libsan -fsanitize=address -I/u/dbyrne99/local/include' ) diff --git a/mutilate.cc b/mutilate.cc index c3130bf..b304070 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -82,13 +82,15 @@ struct thread_data { #endif int id; //std::vector*> trace_queue; - std::vector*> *trace_queue; - std::vector *mutexes; + std::vector*> *trace_queue; + //std::vector *mutexes; + pthread_mutex_t* g_lock; + std::unordered_map *g_wb_keys; }; struct reader_data { //std::vector*> trace_queue; - std::vector*> *trace_queue; + std::vector*> *trace_queue; std::vector *mutexes; string *trace_filename; int twitter_trace; @@ -114,7 +116,7 @@ void go(const vector &servers, options_t &options, //void do_mutilate(const vector &servers, options_t &options, // ConnectionStats &stats,std::vector*> trace_queue, bool master = true void do_mutilate(const vector &servers, options_t &options, - ConnectionStats &stats,std::vector*> *trace_queue, std::vector *mutexes, bool master = true + ConnectionStats &stats,std::vector*> *trace_queue, pthread_mutex_t *g_lock, unordered_map *g_wb_keys, bool master = true #ifdef HAVE_LIBZMQ , zmq::socket_t* socket = NULL #endif @@ -669,6 +671,10 @@ int main(int argc, char **argv) { additional = 0; } } + + for (int i = 0; i < 40; i++) { + fprintf(stderr,"class %d, gets: %lu, sets: %lu\n",i,stats.gets_cid[i],stats.sets_cid[i]); + } //printf("Misses (L1) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l1 + stats.set_misses_l1, // (double) (stats.get_misses_l1 + stats.set_misses_l1) /(stats.gets + stats.sets)*100); printf("Misses (L1) = %" PRIu64 " (%.1f%%)\n", stats.get_misses_l1 , @@ -737,15 +743,20 @@ void go(const vector& servers, options_t& options, #endif //std::vector*> trace_queue; // = (ConcurrentQueue**)malloc(sizeof(ConcurrentQueue) - std::vector*> *trace_queue = new std::vector*>(); + std::vector*> *trace_queue = new std::vector*>(); // = (ConcurrentQueue**)malloc(sizeof(ConcurrentQueue) - std::vector *mutexes = new std::vector(); + //std::vector *mutexes = new std::vector(); + pthread_mutex_t *g_lock = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); + *g_lock = PTHREAD_MUTEX_INITIALIZER; + + unordered_map *g_wb_keys = new unordered_map(); + for (int i = 0; i <= options.apps; i++) { - //trace_queue.push_back(new ConcurrentQueue(2000000)); - trace_queue->push_back(new std::queue()); - pthread_mutex_t *lock = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); - *lock = PTHREAD_MUTEX_INITIALIZER; - mutexes->push_back(lock); + // //trace_queue.push_back(new ConcurrentQueue(2000000)); + // pthread_mutex_t *lock = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); + // *lock = PTHREAD_MUTEX_INITIALIZER; + // mutexes->push_back(lock); + trace_queue->push_back(new std::queue()); } pthread_mutex_init(&reader_l, NULL); pthread_cond_init(&reader_ready, NULL); @@ -753,7 +764,7 @@ void go(const vector& servers, options_t& options, //ConcurrentQueue *trace_queue = new ConcurrentQueue(20000000); struct reader_data *rdata = (struct reader_data*)malloc(sizeof(struct reader_data)); rdata->trace_queue = trace_queue; - rdata->mutexes = mutexes; + //rdata->mutexes = mutexes; rdata->twitter_trace = options.twitter_trace; pthread_t rtid; if (options.read_file) { @@ -794,7 +805,8 @@ void go(const vector& servers, options_t& options, td[t].options = &options; td[t].id = t; td[t].trace_queue = trace_queue; - td[t].mutexes = mutexes; + td[t].g_lock = g_lock; + td[t].g_wb_keys = g_wb_keys; #ifdef HAVE_LIBZMQ td[t].socket = socket; #endif @@ -856,7 +868,7 @@ void go(const vector& servers, options_t& options, //delete trace_queue; } else if (options.threads == 1) { - do_mutilate(servers, options, stats, trace_queue, mutexes, true + do_mutilate(servers, options, stats, trace_queue, g_lock, g_wb_keys, true #ifdef HAVE_LIBZMQ , socket #endif @@ -977,10 +989,11 @@ static char *get_stream(ZSTD_DCtx* dctx, FILE *fin, size_t const buffInSize, voi void* reader_thread(void *arg) { struct reader_data *rdata = (struct reader_data *) arg; //std::vector*> trace_queue = (std::vector*>) rdata->trace_queue; - std::vector*> *trace_queue = (std::vector*>*) rdata->trace_queue; - std::vector *mutexes = (std::vector*) rdata->mutexes; + std::vector*> *trace_queue = (std::vector*>*) rdata->trace_queue; + // std::vector *mutexes = (std::vector*) rdata->mutexes; int twitter_trace = rdata->twitter_trace; string fn = *(rdata->trace_filename); + srand(time(NULL)); if (hasEnding(fn,".zst")) { //init const char *filename = fn.c_str(); @@ -990,13 +1003,16 @@ void* reader_thread(void *arg) { size_t const buffOutSize = ZSTD_DStreamOutSize()*1000; void* const buffOut = malloc_orDie(buffOutSize); + map key_hist; ZSTD_DCtx* const dctx = ZSTD_createDCtx(); - CHECK(dctx != NULL, "ZSTD_createDCtx() failed!"); + //CHECK(dctx != NULL, "ZSTD_createDCtx() failed!"); //char *leftover = malloc(buffOutSize); //memset(leftover,0,buffOutSize); //char *trace = (char*)decompress(filename); uint64_t nwrites = 0; - uint64_t n = 0; + uint64_t nout = 1; + int cappid = 1; + fprintf(stderr,"%lu trace queues for connections\n",trace_queue->size()); char *trace = get_stream(dctx, fin, buffInSize, buffIn, buffOutSize, buffOut); while (trace != NULL) { char *ftrace = trace; @@ -1009,15 +1025,16 @@ void* reader_thread(void *arg) { int appid = 0; if (full_line.length() > 10) { - if (trace_queue->size() > 1) { + if (trace_queue->size() > 0) { stringstream ss(full_line); string rT; string rApp; string rKey; string rOp; + string rvaluelen; + Operation Op; if (twitter_trace == 1) { string rKeySize; - string rvaluelen; size_t n = std::count(full_line.begin(), full_line.end(), ','); if (n == 6) { getline( ss, rT, ',' ); @@ -1025,7 +1042,25 @@ void* reader_thread(void *arg) { getline( ss, rKeySize, ',' ); getline( ss, rvaluelen, ',' ); getline( ss, rApp, ',' ); - appid = (stoi(rApp)) % trace_queue->size(); + getline( ss, rOp, ',' ); + if (rOp.compare("get") == 0) { + Op.type = Operation::GET; + } else if (rOp.compare("set") == 0) { + Op.type = Operation::SET; + } + if (rvaluelen.compare("") == 0 || rvaluelen.size() < 1 || rvaluelen.empty()) { + continue; + } + //appid = cappid; + //if (nout % 1000 == 0) { + // cappid++; + // cappid = cappid % trace_queue->size(); + // if (cappid == 0) cappid = 1; + //} + appid = stoi(rApp) % trace_queue->size(); + if (appid == 0) appid = 1; + + } else { continue; } @@ -1038,20 +1073,102 @@ void* reader_thread(void *arg) { getline( ss, rApp, ','); getline( ss, rOp, ',' ); getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + int ot = stoi(rOp); + switch (ot) { + case 1: + Op.type = Operation::GET; + break; + case 2: + Op.type = Operation::SET; + break; + } appid = (stoi(rApp)) % trace_queue->size(); + //appid = (nout) % trace_queue->size(); + } else { + continue; + } + } + else if (twitter_trace == 3) { + size_t n = std::count(full_line.begin(), full_line.end(), ','); + if (n == 4) { + getline( ss, rT, ','); + getline( ss, rApp, ','); + getline( ss, rOp, ',' ); + getline( ss, rKey, ',' ); + getline( ss, rvaluelen, ',' ); + int ot = stoi(rOp); + switch (ot) { + case 1: + Op.type = Operation::GET; + break; + case 2: + Op.type = Operation::SET; + break; + } + + //appid = (rand() % (trace_queue->size()-1)) + 1; + appid = cappid; + if (nout % 1000 == 0) { + cappid++; + cappid = cappid % trace_queue->size(); + if (cappid == 0) cappid = 1; + } } else { continue; } } - if (appid < (int)trace_queue->size()) { - //pthread_mutex_lock(mutexes[appid]); - trace_queue->at(appid)->push(full_line); - //pthread_mutex_unlock(mutexes[appid]); + else if (twitter_trace == 4) { + size_t n = std::count(full_line.begin(), full_line.end(), ','); + if (n == 3) { + getline( ss, rT, ','); + getline( ss, rKey, ',' ); + getline( ss, rOp, ',' ); + getline( ss, rvaluelen, ',' ); + int ot = stoi(rOp); + switch (ot) { + case 1: + Op.type = Operation::GET; + break; + case 2: + Op.type = Operation::SET; + break; + } + if (rvaluelen == "0") { + rvaluelen = "50000"; + } + + appid = (rand() % (trace_queue->size()-1)) + 1; + //appid = cappid; + //if (nout % 1000 == 0) { + // cappid++; + // cappid = cappid % trace_queue->size(); + // if (cappid == 0) cappid = 1; + //} + } else { + continue; + } + } + int vl = stoi(rvaluelen); + if (appid < (int)trace_queue->size() && vl < 524000 && vl > 1) { + Op.valuelen = vl; + Op.key = rKey; + if (Op.type == Operation::GET) { + //find when was last read + Operation last_op = key_hist[rKey]; + if (last_op.valuelen > 0) { + last_op.future = nout; //THE FUTURE IS NOW + Op.curr = nout; + key_hist[rKey] = Op; + } else { + //first ref + key_hist[rKey] = Op; + } + } + trace_queue->at(appid)->push(Op); } } else { - //pthread_mutex_lock(mutexes[appid]); - trace_queue->at(appid)->push(full_line); - //pthread_mutex_unlock(mutexes[appid]); + fprintf(stderr,"big error!\n"); } } //bool res = trace_queue[appid]->try_enqueue(full_line); @@ -1060,8 +1177,8 @@ void* reader_thread(void *arg) { // //res = trace_queue[appid]->try_enqueue(full_line); // nwrites++; //} - n++; - if (n % 1000000 == 0) fprintf(stderr,"decompressed requests: %lu, waits: %lu\n",n,nwrites); + nout++; + if (nout % 1000000 == 0) fprintf(stderr,"decompressed requests: %lu, waits: %lu\n",nout,nwrites); //if (n > 100000000) { // pthread_mutex_lock(&reader_l); // reader_not_ready = 0; @@ -1075,11 +1192,15 @@ void* reader_thread(void *arg) { trace = get_stream(dctx, fin, buffInSize, buffIn, buffOutSize, buffOut); } - string eof = "EOF"; for (int i = 0; i < 1000; i++) { for (int j = 0; j < (int)trace_queue->size(); j++) { //trace_queue[j]->enqueue(eof); + Operation eof; + eof.type = Operation::SASL; trace_queue->at(j)->push(eof); + if (i == 0) { + fprintf(stderr,"appid %d, tq size: %ld\n",j,trace_queue->at(j)->size()); + } } } @@ -1126,7 +1247,7 @@ void* thread_main(void *arg) { } ConnectionStats *cs = new ConnectionStats(); - do_mutilate(*td->servers, *td->options, *cs, td->trace_queue, td->mutexes, td->master + do_mutilate(*td->servers, *td->options, *cs, td->trace_queue, td->g_lock, td->g_wb_keys, td->master #ifdef HAVE_LIBZMQ , td->socket #endif @@ -1136,7 +1257,7 @@ void* thread_main(void *arg) { } void do_mutilate(const vector& servers, options_t& options, - ConnectionStats& stats, vector*> *trace_queue, vector *mutexes, bool master + ConnectionStats& stats, vector*> *trace_queue, pthread_mutex_t* g_lock, unordered_map *g_wb_keys, bool master #ifdef HAVE_LIBZMQ , zmq::socket_t* socket #endif @@ -1215,9 +1336,9 @@ void do_mutilate(const vector& servers, options_t& options, sleep(d); } if (connected) { - fprintf(stderr,"cid %d gets trace_queue\nfirst: %s",conn->get_cid(),trace_queue->at(conn->get_cid())->front().c_str()); - conn->set_queue(trace_queue->at(conn->get_cid())); - conn->set_lock(mutexes->at(conn->get_cid())); + //fprintf(stderr,"cid %d gets trace_queue\nfirst: %s",conn->get_cid(),trace_queue->at(conn->get_cid())->front().c_str()); + //conn->set_queue(trace_queue->at(conn->get_cid())); + //conn->set_lock(mutexes->at(conn->get_cid())); connections.push_back(conn); } else { fprintf(stderr,"conn: %d, not connected!!\n",c); @@ -1455,17 +1576,18 @@ void do_mutilate(const vector& servers, options_t& options, int addrlen; addrlen = sizeof(sin1); - int max_tries = 13; + int max_tries = 50; int n_tries = 0; - int s = 2; + int s = 10; while (connect(fd1, (struct sockaddr*)&sin1, addrlen) == -1) { perror("l1 connect error"); if (n_tries++ > max_tries) { + fprintf(stderr,"conn l1 %d unable to connect after sleep for %d\n",c+1,s); exit(-1); } - int d = s + rand() % 10; - sleep(d); - s += 4; + int d = s + rand() % 100; + usleep(d); + s = (int)((double)s*1.25); } int fd2 = -1; @@ -1480,15 +1602,16 @@ void do_mutilate(const vector& servers, options_t& options, fcntl(fd2, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state */ addrlen = sizeof(sin2); n_tries = 0; - s = 2; + s = 10; while (connect(fd2, (struct sockaddr*)&sin2, addrlen) == -1) { perror("l2 connect error"); if (n_tries++ > max_tries) { + fprintf(stderr,"conn l2 %d unable to connect after sleep for %d\n",c+1,s); exit(-1); } - int d = s + rand() % 10; - sleep(d); - s += 4; + int d = s + rand() % 100; + usleep(d); + s = (int)((double)s*1.25); } @@ -1503,16 +1626,23 @@ void do_mutilate(const vector& servers, options_t& options, if (connected) { fprintf(stderr,"cid %d gets l1 fd %d l2 fd %d\n",cid,fd1,fd2); - fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front().c_str()); + fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front().key.c_str()); + if (g_lock != NULL) { + conn->set_g_wbkeys(g_wb_keys); + conn->set_lock(g_lock); + } conn->set_queue(trace_queue->at(cid)); - conn->set_lock(mutexes->at(cid)); connections.push_back(conn); } else { fprintf(stderr,"conn multi: %d, not connected!!\n",c); } } + + // wait for all threads to reach here + pthread_barrier_wait(&barrier); + fprintf(stderr,"thread %ld gtg\n",pthread_self()); // Wait for all Connections to become IDLE. while (1) { // FIXME: If all connections become ready before event_base_loop @@ -1526,14 +1656,16 @@ void do_mutilate(const vector& servers, options_t& options, if (restart) continue; else break; } + + double start = get_time(); double now = start; for (ConnectionMulti *conn: connections) { conn->start_time = start; conn->start(); // Kick the Connection into motion. } - fprintf(stderr,"Start = %f\n", start); + //fprintf(stderr,"Start = %f\n", start); // Main event loop. while (1) { From fa01502b8d956e065987003af7aa28a9c95a5fc5 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 15 Dec 2021 19:40:15 -0500 Subject: [PATCH 45/57] added in approx --- Connection.cc | 6 +- Connection.h | 148 ++++ ConnectionMulti.cc | 6 +- ConnectionMultiApprox.cc | 1688 ++++++++++++++++++++++++++++++++++++++ Protocol.cc | 6 +- cmdline.ggo | 1 + mutilate.cc | 224 ++++- 7 files changed, 2037 insertions(+), 42 deletions(-) create mode 100644 ConnectionMultiApprox.cc diff --git a/Connection.cc b/Connection.cc index e3b942f..3cebd6f 100644 --- a/Connection.cc +++ b/Connection.cc @@ -700,7 +700,7 @@ int Connection::issue_get_with_len(const char* key, int valuelen, double now, bo // each line is 4-bytes binary_header_t h = { 0x80, CMD_GET, htons(keylen), - 0x00, 0x00, {htons(0)}, + 0x00, 0x00, htons(0), htonl(keylen) }; if (quiet) { @@ -874,7 +874,7 @@ void Connection::issue_noop(double now) { //op_queue[op.opaque] = op; //op_queue_size++; binary_header_t h = { 0x80, CMD_NOOP, 0x0000, - 0x00, 0x00, {htons(0)}, + 0x00, 0x00, htons(0), 0x00 }; //h.opaque = htonl(op.opaque); @@ -926,7 +926,7 @@ int Connection::issue_set(const char* key, const char* value, int length, // each line is 4-bytes binary_header_t h = { 0x80, CMD_SET, htons(keylen), - 0x08, 0x00, {htons(0)}, + 0x08, 0x00, htons(0), htonl(keylen + 8 + length) }; h.opaque = htonl(op.opaque); diff --git a/Connection.h b/Connection.h index 8e3aba5..e6128e5 100644 --- a/Connection.h +++ b/Connection.h @@ -35,13 +35,19 @@ using namespace moodycamel; void bev_event_cb(struct bufferevent *bev, short events, void *ptr); void bev_read_cb(struct bufferevent *bev, void *ptr); void bev_event_cb1(struct bufferevent *bev, short events, void *ptr); +void bev_event_cb1_approx(struct bufferevent *bev, short events, void *ptr); void bev_read_cb1(struct bufferevent *bev, void *ptr); +void bev_read_cb1_approx(struct bufferevent *bev, void *ptr); void bev_event_cb2(struct bufferevent *bev, short events, void *ptr); +void bev_event_cb2_approx(struct bufferevent *bev, short events, void *ptr); void bev_read_cb2(struct bufferevent *bev, void *ptr); +void bev_read_cb2_approx(struct bufferevent *bev, void *ptr); void bev_write_cb(struct bufferevent *bev, void *ptr); void bev_write_cb_m(struct bufferevent *bev, void *ptr); +void bev_write_cb_m_approx(struct bufferevent *bev, void *ptr); void timer_cb(evutil_socket_t fd, short what, void *ptr); void timer_cb_m(evutil_socket_t fd, short what, void *ptr); +void timer_cb_m_approx(evutil_socket_t fd, short what, void *ptr); class Protocol; @@ -317,4 +323,146 @@ class ConnectionMulti { bool consume_resp_line(evbuffer *input, bool &done); }; +class ConnectionMultiApprox { +public: + ConnectionMultiApprox(struct event_base* _base, struct evdns_base* _evdns, + string _hostname1, string _hostname2, string _port, options_t options, + bool sampling = true, int fd1 = -1, int fd2 = -1); + + ~ConnectionMultiApprox(); + + int do_connect(); + + double start_time; // Time when this connection began operations. + ConnectionStats stats; + options_t options; + + bool is_ready() { return read_state == IDLE; } + void set_priority(int pri); + + // state commands + void start() { + //fprintf(stderr,"connid: %d starting...\n",cid); + drive_write_machine(); + } + void start_loading(); + void reset(); + bool check_exit_condition(double now = 0.0); + + void event_callback1(short events); + void event_callback2(short events); + void read_callback1(); + void read_callback2(); + // event callbacks + void write_callback(); + void timer_callback(); + + int eof; + uint32_t get_cid(); + //void set_queue(ConcurrentQueue *a_trace_queue); + int add_to_wb_keys(string wb_key); + void del_wb_keys(string wb_key); + void set_g_wbkeys(unordered_map *a_wb_keys); + void set_queue(queue *a_trace_queue); + void set_lock(pthread_mutex_t* a_lock); + +private: + string hostname1; + string hostname2; + string port; + + double o_percent; + int trace_queue_n; + struct event_base *base; + struct evdns_base *evdns; + struct bufferevent *bev1; + struct bufferevent *bev2; + + struct event *timer; // Used to control inter-transmission time. + double next_time; // Inter-transmission time parameters. + double last_rx; // Used to moderate transmission rate. + double last_tx; + + vector wb_keys; + enum read_state_enum { + INIT_READ, + CONN_SETUP, + LOADING, + IDLE, + WAITING_FOR_GET, + WAITING_FOR_SET, + WAITING_FOR_DELETE, + MAX_READ_STATE, + }; + + enum write_state_enum { + INIT_WRITE, + ISSUING, + WAITING_FOR_TIME, + WAITING_FOR_OPQ, + MAX_WRITE_STATE, + }; + + read_state_enum read_state; + write_state_enum write_state; + + // Parameters to track progress of the data loader. + int loader_issued, loader_completed; + + uint32_t *opaque; + int *issue_buf_size; + int *issue_buf_n; + unsigned char **issue_buf_pos; + unsigned char **issue_buf; + bool last_quiet1; + bool last_quiet2; + uint32_t total; + uint32_t cid; + uint32_t gets; + uint32_t gloc; + + //std::vector> op_queue; + Operation ***op_queue; + uint32_t *op_queue_size; + + map key_hist; + + Generator *valuesize; + Generator *keysize; + KeyGenerator *keygen; + Generator *iagen; + pthread_mutex_t* lock; + unordered_map *g_wb_keys; + queue *trace_queue; + + // state machine functions / event processing + void pop_op(Operation *op); + void output_op(Operation *op, int type, bool was_found); + //void finish_op(Operation *op); + void finish_op(Operation *op,int was_hit); + int issue_getsetorset(double now = 0.0); + void drive_write_machine(double now = 0.0); + + // request functions + void issue_sasl(); + void issue_noop(double now = 0.0, int level = 1); + int issue_touch(const char* key, int valuelen, double now, int level); + int issue_delete(const char* key, double now, uint32_t flags); + int issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); + int issue_set(const char* key, const char* value, int length, double now, uint32_t flags); + + // protocol fucntions + int set_request_ascii(const char* key, const char* value, int length); + int set_request_binary(const char* key, const char* value, int length); + int set_request_resp(const char* key, const char* value, int length); + + int get_request_ascii(const char* key); + int get_request_binary(const char* key); + int get_request_resp(const char* key); + + bool consume_binary_response(evbuffer *input); + bool consume_ascii_line(evbuffer *input, bool &done); + bool consume_resp_line(evbuffer *input, bool &done); +}; + #endif diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 28bd84a..688ad3c 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -665,7 +665,7 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no // each line is 4-bytes binary_header_t h = { 0x80, CMD_GET, htons(keylen), - 0x00, 0x00, {htons(0)}, + 0x00, 0x00, htons(0), htonl(keylen) }; if (quiet) { h.opcode = CMD_GETQ; @@ -837,7 +837,7 @@ void ConnectionMulti::issue_noop(double now, int level) { else op.start_time = now; binary_header_t h = { 0x80, CMD_NOOP, 0x0000, - 0x00, 0x00, {htons(0)}, + 0x00, 0x00, htons(0), 0x00 }; evbuffer_add(output, &h, 24); @@ -893,7 +893,7 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, d // each line is 4-bytes binary_header_t h = { 0x80, CMD_SET, htons(keylen), - 0x08, 0x00, {htons(0)}, + 0x08, 0x00, htons(0), htonl(keylen + 8 + length) }; h.opaque = htonl(pop->opaque); diff --git a/ConnectionMultiApprox.cc b/ConnectionMultiApprox.cc new file mode 100644 index 0000000..5e4bc90 --- /dev/null +++ b/ConnectionMultiApprox.cc @@ -0,0 +1,1688 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "Connection.h" +#include "distributions.h" +#include "Generator.h" +#include "mutilate.h" +#include "binary_protocol.h" +#include "util.h" +#include +#include +#include +#include +#include +#include "blockingconcurrentqueue.h" + +#define ITEM_L1 1 +#define ITEM_L2 2 +#define LOG_OP 4 +#define SRC_L1_M 8 +#define SRC_L1_H 16 +#define SRC_L2_M 32 +#define SRC_L2_H 64 +#define SRC_DIRECT_SET 128 +#define SRC_L1_COPY 256 +#define SRC_WB 512 + +#define ITEM_INCL 4096 +#define ITEM_EXCL 8192 +#define ITEM_DIRTY 16384 +#define ITEM_SIZE_CHANGE 131072 +#define ITEM_WAS_HIT 262144 + +#define LEVELS 2 +#define SET_INCL(incl,flags) \ + switch (incl) { \ + case 1: \ + flags |= ITEM_INCL; \ + break; \ + case 2: \ + flags |= ITEM_EXCL; \ + break; \ + \ + } \ + +#define GET_INCL(incl,flags) \ + if (flags & ITEM_INCL) incl = 1; \ + else if (flags & ITEM_EXCL) incl = 2; \ + +//#define OP_level(op) ( ((op)->flags & ITEM_L1) ? ITEM_L1 : ITEM_L2 ) +#define OP_level(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define FLAGS_level(flags) ( flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_clu(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_L1 | ITEM_L2 | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_src(op) ( (op)->flags & ~(ITEM_L1 | ITEM_L2 | LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY ) ) + +#define OP_log(op) ((op)->flags & LOG_OP) +#define OP_incl(op) ((op)->flags & ITEM_INCL) +#define OP_excl(op) ((op)->flags & ITEM_EXCL) +#define OP_set_flag(op,flag) ((op))->flags |= flag; + +//#define DEBUGMC +//#define DEBUGS + +using namespace moodycamel; + +pthread_mutex_t cid_lock_m_approx = PTHREAD_MUTEX_INITIALIZER; +static uint32_t connids_m = 1; + +#define NCLASSES 40 +#define CHUNK_ALIGN_BYTES 8 +static int classes = 0; +static int sizes[NCLASSES+1]; +static int inclusives[NCLASSES+1]; + +typedef struct _evicted_type { + bool evicted; + uint32_t evictedFlags; + uint32_t serverFlags; + uint32_t clsid; + uint32_t evictedKeyLen; + uint32_t evictedLen; + char *evictedKey; + char *evictedData; +} evicted_t; + +static vector cid_rate; + +extern int max_n[3]; + +static void init_inclusives(char *inclusive_str) { + int j = 1; + for (int i = 0; i < (int)strlen(inclusive_str); i++) { + if (inclusive_str[i] == '-') { + continue; + } else { + inclusives[j] = inclusive_str[i] - '0'; + j++; + } + } +} + +static void init_classes() { + + double factor = 1.25; + unsigned int chunk_size = 48; + unsigned int item_size = 24; + unsigned int size = 96; //warning if you change this you die + unsigned int i = 0; + unsigned int chunk_size_max = 1048576/2; + while (++i < NCLASSES-1) { + if (size >= chunk_size_max / factor) { + break; + } + if (size % CHUNK_ALIGN_BYTES) + size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); + sizes[i] = size; + size *= factor; + } + sizes[i] = chunk_size_max; + classes = i; + +} + +static int get_class(int vl, uint32_t kl) { + //warning if you change this you die + int vsize = vl+kl+48+1+2; + int res = 1; + while (vsize > sizes[res]) + if (res++ == classes) { + //fprintf(stderr,"item larger than max class size. vsize: %d, class size: %d\n",vsize,sizes[res]); + return -1; + } + return res; +} + +static int get_incl(int vl, int kl) { + int clsid = get_class(vl,kl); + if (clsid) { + return inclusives[clsid]; + } else { + return -1; + } +} + +void ConnectionMultiApprox::output_op(Operation *op, int type, bool found) { + char output[1024]; + char k[256]; + char a[256]; + char s[256]; + memset(k,0,256); + memset(a,0,256); + memset(s,0,256); + strcpy(k,op->key.c_str()); + switch (type) { + case 0: //get + sprintf(a,"issue_get"); + break; + case 1: //set + sprintf(a,"issue_set"); + break; + case 2: //resp + sprintf(a,"resp"); + break; + } + switch(read_state) { + case INIT_READ: + sprintf(s,"init"); + break; + case CONN_SETUP: + sprintf(s,"setup"); + break; + case LOADING: + sprintf(s,"load"); + break; + case IDLE: + sprintf(s,"idle"); + break; + case WAITING_FOR_GET: + sprintf(s,"waiting for get"); + break; + case WAITING_FOR_SET: + sprintf(s,"waiting for set"); + break; + case WAITING_FOR_DELETE: + sprintf(s,"waiting for del"); + break; + case MAX_READ_STATE: + sprintf(s,"max"); + break; + } + if (type == 2) { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, found: %d, type: %d\n",cid,a,k,op->opaque,found,op->type); + } else { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, type: %d\n",cid,a,k,op->opaque,op->type); + } + write(2,output,strlen(output)); +} + +/** + * Create a new connection to a server endpoint. + */ +ConnectionMultiApprox::ConnectionMultiApprox(struct event_base* _base, struct evdns_base* _evdns, + string _hostname1, string _hostname2, string _port, options_t _options, + bool sampling, int fd1, int fd2 ) : + start_time(0), stats(sampling), options(_options), + hostname1(_hostname1), hostname2(_hostname2), port(_port), base(_base), evdns(_evdns) +{ + pthread_mutex_lock(&cid_lock_m_approx); + cid = connids_m++; + if (cid == 1) { + cid_rate.push_back(100); + cid_rate.push_back(0); + init_classes(); + init_inclusives(options.inclusives); + } else { + cid_rate.push_back(0); + } + + pthread_mutex_unlock(&cid_lock_m_approx); + + valuesize = createGenerator(options.valuesize); + keysize = createGenerator(options.keysize); + srand(time(NULL)); + keygen = new KeyGenerator(keysize, options.records); + + total = 0; + eof = 0; + o_percent = 0; + + if (options.lambda <= 0) { + iagen = createGenerator("0"); + } else { + D("iagen = createGenerator(%s)", options.ia); + iagen = createGenerator(options.ia); + iagen->set_lambda(options.lambda); + } + + read_state = IDLE; + write_state = INIT_WRITE; + last_quiet1 = false; + last_quiet2 = false; + + last_tx = last_rx = 0.0; + gets = 0; + gloc = rand() % (1000*2-1)+1; + + op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + op_queue = (Operation***)malloc(sizeof(Operation**)*(LEVELS+1)); + + for (int i = 0; i <= LEVELS; i++) { + op_queue_size[i] = 0; + opaque[i] = 1; + //op_queue[i] = (Operation*)malloc(sizeof(int)*OPAQUE_MAX); + op_queue[i] = (Operation**)malloc(sizeof(Operation*)*(OPAQUE_MAX*2)); + + } + + bev1 = bufferevent_socket_new(base, fd1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev1, bev_read_cb1_approx, bev_write_cb_m_approx, bev_event_cb1_approx, this); + bufferevent_enable(bev1, EV_READ | EV_WRITE); + + bev2 = bufferevent_socket_new(base, fd2, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev2, bev_read_cb2_approx, bev_write_cb_m_approx, bev_event_cb2_approx, this); + bufferevent_enable(bev2, EV_READ | EV_WRITE); + + timer = evtimer_new(base, timer_cb_m_approx, this); + + read_state = IDLE; +} + + +void ConnectionMultiApprox::set_queue(queue* a_trace_queue) { + trace_queue = a_trace_queue; + trace_queue_n = a_trace_queue->size(); +} + +void ConnectionMultiApprox::set_lock(pthread_mutex_t* a_lock) { + lock = a_lock; +} + +void ConnectionMultiApprox::set_g_wbkeys(unordered_map *a_wb_keys) { + g_wb_keys = a_wb_keys; +} + +uint32_t ConnectionMultiApprox::get_cid() { + return cid; +} + +int ConnectionMultiApprox::add_to_wb_keys(string key) { + int ret = -1; + pthread_mutex_lock(lock); + auto pos = g_wb_keys->find(key); + if (pos == g_wb_keys->end()) { + g_wb_keys->insert( {key,cid }); + ret = 1; + //fprintf(stderr,"----set: %s----\n",Op.key.c_str()); + //for (auto iter = g_wb_keys->begin(); iter != g_wb_keys->end(); ++iter){ + // fprintf(stderr,"%s,%d\n",iter->first.c_str(),iter->second); + //} + //fprintf(stderr,"----%d----\n",cid); + } else { + ret = 2; + } + + pthread_mutex_unlock(lock); + return ret; +} + +void ConnectionMultiApprox::del_wb_keys(string key) { + + pthread_mutex_lock(lock); + auto position = g_wb_keys->find(key); + if (position != g_wb_keys->end()) { + g_wb_keys->erase(position); + } else { + fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); + } + pthread_mutex_unlock(lock); +} + + +int ConnectionMultiApprox::do_connect() { + + int connected = 0; + if (options.unix_socket) { + + + struct sockaddr_un sin1; + memset(&sin1, 0, sizeof(sin1)); + sin1.sun_family = AF_LOCAL; + strcpy(sin1.sun_path, hostname1.c_str()); + + int addrlen; + addrlen = sizeof(sin1); + + int err = bufferevent_socket_connect(bev1, (struct sockaddr*)&sin1, addrlen); + if (err == 0) { + connected = 1; + } else { + connected = 0; + err = errno; + fprintf(stderr,"l1 error %s\n",strerror(err)); + } + + struct sockaddr_un sin2; + memset(&sin2, 0, sizeof(sin2)); + sin2.sun_family = AF_LOCAL; + strcpy(sin2.sun_path, hostname2.c_str()); + + addrlen = sizeof(sin2); + err = bufferevent_socket_connect(bev2, (struct sockaddr*)&sin2, addrlen); + if (err == 0) { + connected = 1; + } else { + connected = 0; + err = errno; + fprintf(stderr,"l2 error %s\n",strerror(err)); + } + } + read_state = IDLE; + return connected; +} + +/** + * Destroy a connection, performing cleanup. + */ +ConnectionMultiApprox::~ConnectionMultiApprox() { + + + for (int i = 0; i <= LEVELS; i++) { + free(op_queue[i]); + + } + + free(op_queue_size); + free(opaque); + free(op_queue); + //event_free(timer); + //timer = NULL; + // FIXME: W("Drain op_q?"); + //bufferevent_free(bev1); + //bufferevent_free(bev2); + + delete iagen; + delete keygen; + delete keysize; + delete valuesize; +} + +/** + * Reset the connection back to an initial, fresh state. + */ +void ConnectionMultiApprox::reset() { + // FIXME: Actually check the connection, drain all bufferevents, drain op_q. + //assert(op_queue.size() == 0); + //evtimer_del(timer); + read_state = IDLE; + write_state = INIT_WRITE; + stats = ConnectionStats(stats.sampling); +} + +/** + * Set our event processing priority. + */ +void ConnectionMultiApprox::set_priority(int pri) { + if (bufferevent_priority_set(bev1, pri)) { + DIE("bufferevent_set_priority(bev, %d) failed", pri); + } +} + + + +/** + * Get/Set or Set Style + * If a GET command: Issue a get first, if not found then set + * If trace file (or prob. write) says to set, then set it + */ +int ConnectionMultiApprox::issue_getsetorset(double now) { + + + + int ret = 0; + int nissued = 0; + //while (nissued < options.depth) { + + //pthread_mutex_lock(lock); + if (!trace_queue->empty()) { + Operation Op = trace_queue->front(); + if (Op.type == Operation::SASL) { + eof = 1; + cid_rate[cid] = 100; + fprintf(stderr,"cid %d done\n",cid); + string op_queue1; + string op_queue2; + for (int j = 0; j < 2; j++) { + for (int i = 0; i < OPAQUE_MAX; i++) { + if (op_queue[j+1][i] != NULL) { + if (j == 0) { + op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + } else { + op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + } + } + } + } + fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + return 1; + } + + + /* check if in global wb queue */ + pthread_mutex_lock(lock); + double percent = (double)total/((double)trace_queue_n) * 100; + if (percent > o_percent+1) { + //update the percentage table and see if we should execute + std::vector::iterator mp = std::min_element(cid_rate.begin(), cid_rate.end()); + double min_percent = *mp; + + if (percent > min_percent+2) { + pthread_mutex_unlock(lock); + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + int good = 0; + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + good = evtimer_add(timer, &tv); + } + if (good != 0) { + fprintf(stderr,"eventimer is messed up!\n"); + return 2; + } + return 1; + } + cid_rate[cid] = percent; + fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); + o_percent = percent; + } + auto check = g_wb_keys->find(Op.key); + if (check != g_wb_keys->end()) { + pthread_mutex_unlock(lock); + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + int good = 0; + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + good = evtimer_add(timer, &tv); + } + if (good != 0) { + fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op.key.c_str()); + return 2; + } + return 1; + } else { + g_wb_keys->insert( {Op.key, cid} ); + //g_wb_keys->insert( {Op.key+"l2", cid} ); + } + pthread_mutex_unlock(lock); + + + + char key[256]; + memset(key,0,256); + strncpy(key, Op.key.c_str(),255); + int vl = Op.valuelen; + + trace_queue->pop(); + + int issued = 0; + int incl = get_incl(vl,strlen(key)); + int cid = get_class(vl,strlen(key)); + int flags = 0; + int index = lrand48() % (1024 * 1024); + //int touch = 1; + SET_INCL(incl,flags); + + switch(Op.type) + { + case Operation::GET: + //if (nissued < options.depth-1) { + // issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); + // last_quiet1 = false; + //} else { + //} + if (options.threshold > 0) { + if (Op.future) { + key_hist[key] = 1; + } + } + issued = issue_get_with_len(key, vl, now, false, flags | LOG_OP | ITEM_L1); + last_quiet1 = false; + this->stats.gets++; + gets++; + this->stats.gets_cid[cid]++; + + break; + case Operation::SET: + if (last_quiet1) { + issue_noop(now,1); + } + if (incl == 1) { + issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); + } else if (incl == 2) { + issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + } + last_quiet1 = false; + this->stats.sets++; + this->stats.sets_cid[cid]++; + break; + case Operation::DELETE: + case Operation::TOUCH: + case Operation::NOOP: + case Operation::SASL: + fprintf(stderr,"invalid line: %s, vl: %d\n",key,vl); + break; + + } + if (issued) { + nissued++; + total++; + } else { + fprintf(stderr,"failed to issue line: %s, vl: %d @T: XX \n",key,vl); + } + } else { + return 1; + } + if (last_quiet1) { + issue_noop(now,1); + last_quiet1 = false; + } + + return ret; + +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApprox::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1) { + + struct evbuffer *output = NULL; + int level = 0; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } + //Operation op; + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + pop->key = string(key); + pop->valuelen = valuelen; + pop->type = Operation::GET; + pop->opaque = opaque[level]++; + pop->flags = flags; + pop->clsid = get_class(valuelen,strlen(key)); + if (l1 != NULL) { + pop->l1 = l1; + } + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,valuelen,level,flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_GET, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + if (quiet) { + h.opcode = CMD_GETQ; + } + h.opaque = htonl(pop->opaque); + + evbuffer_add(output, &h, 24); + evbuffer_add(output, key, keylen); + + stats.tx_bytes += 24 + keylen; + return 1; +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApprox::issue_touch(const char* key, int valuelen, double now, int flags) { + struct evbuffer *output = NULL; + int level = 0; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + pop->key = string(key); + pop->valuelen = valuelen; + pop->type = Operation::TOUCH; + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + +#ifdef DEBUGS + fprintf(stderr,"issing touch: %s, size: %u, level %d, flags: %d, opaque: %d\n",key,valuelen,level,flags,pop->opaque); +#endif + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_TOUCH, htons(keylen), + 0x04, 0x00, htons(0), + htonl(keylen + 4) }; + h.opaque = htonl(pop->opaque); + + uint32_t exp = 0; + if (flags & ITEM_DIRTY) { + exp = htonl(flags); + } + evbuffer_add(output, &h, 24); + evbuffer_add(output, &exp, 4); + evbuffer_add(output, key, keylen); + + + stats.tx_bytes += 24 + keylen; + + //stats.log_access(op); + return 1; +} + +/** + * Issue a delete request to the server. + */ +int ConnectionMultiApprox::issue_delete(const char* key, double now, uint32_t flags) { + struct evbuffer *output = NULL; + int level = 0; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } + //Operation op; + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + pop->key = string(key); + pop->type = Operation::DELETE; + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing delete: %s, level %d, flags: %d, opaque: %d\n",cid,key,level,flags,pop->opaque); +#endif + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_DELETE, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + h.opaque = htonl(pop->opaque); + + evbuffer_add(output, &h, 24); + evbuffer_add(output, key, keylen); + + stats.tx_bytes += 24 + keylen; + + //stats.log_access(op); + return 1; +} + +void ConnectionMultiApprox::issue_noop(double now, int level) { + struct evbuffer *output = NULL; + switch (level) { + case 1: + output = bufferevent_get_output(bev1); + break; + case 2: + output = bufferevent_get_output(bev2); + break; + } + Operation op; + + if (now == 0.0) op.start_time = get_time(); + else op.start_time = now; + + binary_header_t h = { 0x80, CMD_NOOP, 0x0000, + 0x00, 0x00, htons(0), + 0x00 }; + + evbuffer_add(output, &h, 24); + +} + +/** + * Issue a set request to the server. + */ +int ConnectionMultiApprox::issue_set(const char* key, const char* value, int length, double now, uint32_t flags) { + + struct evbuffer *output = NULL; + int level = 0; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } + //Operation op; + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) pop->start_time = get_time(); + else pop->start_time = now; +#endif + + + pop->key = string(key); + pop->valuelen = length; + pop->type = Operation::SET; + pop->opaque = opaque[level]++; + pop->flags = flags; + pop->clsid = get_class(length,strlen(key)); + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,length,level,flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_SET, htons(keylen), + 0x08, 0x00, htons(0), + htonl(keylen + 8 + length) }; + h.opaque = htonl(pop->opaque); + + uint32_t f = htonl(flags); + uint32_t exp = 0; + + evbuffer_add(output, &h, 24); + evbuffer_add(output, &f, 4); + evbuffer_add(output, &exp, 4); + evbuffer_add(output, key, keylen); + evbuffer_add(output, value, length); + + stats.tx_bytes += length + 32 + keylen; + return 1; +} + +/** + * Return the oldest live operation in progress. + */ +void ConnectionMultiApprox::pop_op(Operation *op) { + + uint8_t level = OP_level(op); + //op_queue[level].erase(op); + op_queue_size[level]--; + + + if (read_state == LOADING) return; + read_state = IDLE; + + // Advance the read state machine. + //if (op_queue.size() > 0) { + // Operation& op = op_queue.front(); + // switch (op.type) { + // case Operation::GET: read_state = WAITING_FOR_GET; break; + // case Operation::SET: read_state = WAITING_FOR_SET; break; + // case Operation::DELETE: read_state = WAITING_FOR_DELETE; break; + // default: DIE("Not implemented."); + // } + //} +} + +/** + * Finish up (record stats) an operation that just returned from the + * server. + */ +void ConnectionMultiApprox::finish_op(Operation *op, int was_hit) { + double now; +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); +#else + now = get_time(); +#endif +#if HAVE_CLOCK_GETTIME + op->end_time = get_time_accurate(); +#else + op->end_time = now; +#endif + + if (options.successful_queries && was_hit) { + switch (op->type) { + case Operation::GET: + switch (OP_level(op)) { + case 1: + stats.log_get_l1(*op); + break; + case 2: + stats.log_get_l2(*op); + break; + } + break; + case Operation::SET: + switch (OP_level(op)) { + case 1: + stats.log_set_l1(*op); + break; + case 2: + stats.log_set_l2(*op); + break; + } + break; + case Operation::DELETE: break; + case Operation::TOUCH: break; + default: DIE("Not implemented."); + } + } else { + switch (op->type) { + case Operation::GET: + if (OP_log(op)) { + switch (OP_level(op)) { + case 1: + stats.log_get_l1(*op); + break; + case 2: + stats.log_get_l2(*op); + if (op->l1 != NULL) { + op->l1->end_time = now; + stats.log_get(*(op->l1)); + } + break; + } + } + break; + case Operation::SET: + if (OP_log(op)) { + switch (OP_level(op)) { + case 1: + stats.log_set_l1(*op); + break; + case 2: + stats.log_set_l2(*op); + break; + } + } + break; + case Operation::DELETE: break; + case Operation::TOUCH: break; + default: DIE("Not implemented."); + } + } + + last_rx = now; + uint8_t level = OP_level(op); + if (op->l1 != NULL) { + delete op_queue[1][op->l1->opaque]; + op_queue[1][op->l1->opaque] = 0; + op_queue_size[1]--; + } + //op_queue[level].erase(op_queue[level].begin()+opopq); + if (op == op_queue[level][op->opaque] && + op->opaque == op_queue[level][op->opaque]->opaque) { + delete op_queue[level][op->opaque]; + op_queue[level][op->opaque] = 0; + } else { + fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", + op,op_queue[level][op->opaque],op->opaque,op_queue[level][op->opaque]->opaque); + } + op_queue_size[level]--; + read_state = IDLE; + + +} + + + +/** + * Check if our testing is done and we should exit. + */ +bool ConnectionMultiApprox::check_exit_condition(double now) { + if (eof && op_queue_size[1] == 0 && op_queue_size[2] == 0) { + return true; + } + if (read_state == INIT_READ) return false; + + return false; +} + +/** + * Handle new connection and error events. + */ +void ConnectionMultiApprox::event_callback1(short events) { + if (events & BEV_EVENT_CONNECTED) { + D("Connected to %s:%s.", hostname1.c_str(), port.c_str()); + int fd = bufferevent_getfd(bev1); + if (fd < 0) DIE("bufferevent_getfd"); + + if (!options.no_nodelay && !options.unix_socket) { + int one = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (void *) &one, sizeof(one)) < 0) + DIE("setsockopt()"); + } +#ifdef DEBUGMC + fprintf(stderr,"libevent connected %s, fd: %u\n",hostname1.c_str(),bufferevent_getfd(bev1)); +#endif + + + } else if (events & BEV_EVENT_ERROR) { + int err = bufferevent_socket_get_dns_error(bev1); + //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); + if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); + fprintf(stderr,"CID: %d - Got an error: %s\n",this->cid, + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); + + //DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + + } else if (events & BEV_EVENT_EOF) { + fprintf(stderr,"Unexpected EOF from server."); + return; + } +} + +/** + * Handle new connection and error events. + */ +void ConnectionMultiApprox::event_callback2(short events) { + if (events & BEV_EVENT_CONNECTED) { + D("Connected to %s:%s.", hostname2.c_str(), port.c_str()); + int fd = bufferevent_getfd(bev2); + if (fd < 0) DIE("bufferevent_getfd"); + + if (!options.no_nodelay && !options.unix_socket) { + int one = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (void *) &one, sizeof(one)) < 0) + DIE("setsockopt()"); + } +#ifdef DEBUGMC + fprintf(stderr,"libevent connected %s, fd: %u\n",hostname2.c_str(),bufferevent_getfd(bev2)); +#endif + + + } else if (events & BEV_EVENT_ERROR) { + int err = bufferevent_socket_get_dns_error(bev2); + //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); + if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); + fprintf(stderr,"CID: %d - Got an error: %s\n",this->cid, + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); + + //DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + + + } else if (events & BEV_EVENT_EOF) { + fprintf(stderr,"Unexpected EOF from server."); + return; + } +} + +/** + * Request generation loop. Determines whether or not to issue a new command, + * based on timer events. + * + * Note that this function loops. Be wary of break vs. return. + */ +void ConnectionMultiApprox::drive_write_machine(double now) { + if (now == 0.0) now = get_time(); + + double delay; + struct timeval tv; + + if (check_exit_condition(now)) { + return; + } + + while (1) { + switch (write_state) { + case INIT_WRITE: + delay = iagen->generate(); + next_time = now + delay; + double_to_tv(delay, &tv); + evtimer_add(timer, &tv); + write_state = ISSUING; + break; + + case ISSUING: + if ( (op_queue_size[1] >= (size_t) options.depth) || + (op_queue_size[2] >= (size_t) options.depth) ) { + write_state = WAITING_FOR_OPQ; + break; + } + + if (options.getsetorset) { + int ret = issue_getsetorset(now); + if (ret == 1) return; //if at EOF + } + + last_tx = now; + for (int i = 1; i <= 2; i++) { + stats.log_op(op_queue_size[i]); + } + break; + + case WAITING_FOR_TIME: + write_state = ISSUING; + break; + + case WAITING_FOR_OPQ: + if ( (op_queue_size[1] >= (size_t) options.depth) || + (op_queue_size[2] >= (size_t) options.depth) ) { + //double delay = 0.01; + //struct timeval tv; + //double_to_tv(delay, &tv); + //evtimer_add(timer, &tv); + return; + } else { + write_state = ISSUING; + break; + } + + default: DIE("Not implemented"); + } + } +} + + + +/** + * Tries to consume a binary response (in its entirety) from an evbuffer. + * + * @param input evBuffer to read response from + * @return true if consumed, false if not enough data in buffer. + */ +static bool handle_response(ConnectionMultiApprox *conn, evbuffer *input, bool &done, bool &found, int &opcode, uint32_t &opaque, evicted_t *evict, int level) { + // Read the first 24 bytes as a header + int length = evbuffer_get_length(input); + if (length < 24) return false; + binary_header_t* h = + reinterpret_cast(evbuffer_pullup(input, 24)); + //assert(h); + + uint32_t bl = ntohl(h->body_len); + uint16_t kl = ntohs(h->key_len); + uint8_t el = h->extra_len; + // Not whole response + int targetLen = 24 + bl; + if (length < targetLen) { + return false; + } + + opcode = h->opcode; + opaque = ntohl(h->opaque); + uint16_t status = ntohs(h->status); +#ifdef DEBUGMC + fprintf(stderr,"cid: %d handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",conn->get_cid(),level, + h->opcode,ntohl(h->opaque),ntohs(h->key_len),h->extra_len, + ntohl(h->body_len),ntohs(h->status)); +#endif + + + // If something other than success, count it as a miss + if (opcode == CMD_GET && status == RESP_NOT_FOUND) { + switch(level) { + case 1: + conn->stats.get_misses_l1++; + break; + case 2: + conn->stats.get_misses_l2++; + conn->stats.get_misses++; + conn->stats.window_get_misses++; + break; + + } + found = false; + evbuffer_drain(input, targetLen); + + } else if (opcode == CMD_SET && kl > 0) { + //first data is extras: clsid, flags, eflags + if (evict) { + evbuffer_drain(input,24); + unsigned char *buf = evbuffer_pullup(input,bl); + + + evict->clsid = *((uint32_t*)buf); + evict->clsid = ntohl(evict->clsid); + buf += 4; + + evict->serverFlags = *((uint32_t*)buf); + evict->serverFlags = ntohl(evict->serverFlags); + buf += 4; + + evict->evictedFlags = *((uint32_t*)buf); + evict->evictedFlags = ntohl(evict->evictedFlags); + buf += 4; + + + evict->evictedKeyLen = kl; + evict->evictedKey = (char*)malloc(kl+1); + memset(evict->evictedKey,0,kl+1); + memcpy(evict->evictedKey,buf,kl); + buf += kl; + + + evict->evictedLen = bl - kl - el; + evict->evictedData = (char*)malloc(evict->evictedLen); + memcpy(evict->evictedData,buf,evict->evictedLen); + evict->evicted = true; + //fprintf(stderr,"class: %u, serverFlags: %u, evictedFlags: %u\n",evict->clsid,evict->serverFlags,evict->evictedFlags); + evbuffer_drain(input,bl); + } else { + evbuffer_drain(input, targetLen); + } + } else if (opcode == CMD_TOUCH && status == RESP_NOT_FOUND) { + found = false; + evbuffer_drain(input, targetLen); + } else if (opcode == CMD_DELETE && status == RESP_NOT_FOUND) { + found = false; + evbuffer_drain(input, targetLen); + } else { + evbuffer_drain(input, targetLen); + } + + conn->stats.rx_bytes += targetLen; + done = true; + return true; +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMultiApprox::read_callback1() { + struct evbuffer *input = bufferevent_get_input(bev1); + + Operation *op = NULL; + bool done, found; + + //initially assume found (for sets that may come through here) + //is this correct? do we want to assume true in case that + //GET was found, but wrong value size (i.e. update value) + found = true; + + //if (op_queue.size() == 0) V("Spurious read callback."); + bool full_read = true; + while (full_read) { + + + int opcode; + uint32_t opaque; + evicted_t *evict = (evicted_t*)malloc(sizeof(evicted_t)); + memset(evict,0,sizeof(evicted_t)); + + full_read = handle_response(this,input, done, found, opcode, opaque, evict,1); + if (full_read) { + if (opcode == CMD_NOOP) { +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + continue; + } + op = op_queue[1][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + if (op->key.length() < 1) { +#ifdef DEBUGMC + char out2[128]; + sprintf(out2,"conn l1: %u, bad op: %s\n",cid,op->key.c_str()); + write(2,out2,strlen(out2)); +#endif + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + continue; + } + } else { + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + break; + } + + + double now = get_time(); + int wb = 0; + if (options.rand_admit) { + wb = (rand() % options.rand_admit); + } + switch (op->type) { + case Operation::GET: + if (done) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + //int touch = (rand() % 100); + + int vl = op->valuelen; + if ( !found && (options.getset || options.getsetorset) ) { + /* issue a get a l2 */ + int flags = OP_clu(op); + issue_get_with_len(key,vl,now,false, flags | SRC_L1_M | ITEM_L2 | LOG_OP, op); + op->end_time = now; + this->stats.log_get_l1(*op); + //finish_op(op,0); + + } else { + if (OP_incl(op) && gets == gloc) { + issue_touch(key,vl,now, ITEM_L2 | SRC_L1_H); + gloc += rand()%(1000*2-1)+1; + } + del_wb_keys(op->key); + finish_op(op,found); + } + } else { + char out[128]; + sprintf(out,"conn l1: %u, not done reading, should do something",cid); + write(2,out,strlen(out)); + } + break; + case Operation::SET: + //if (OP_src(op) == SRC_L1_COPY || + // OP_src(op) == SRC_DIRECT_SET || + // OP_src(op) == SRC_L2_M ) { + //} + if (evict->evicted) { + string wb_key(evict->evictedKey); + if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { + //wb_keys.push_back(wb_key); + int ret = add_to_wb_keys(wb_key); + if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_WB); + } + //fprintf(stderr,"incl writeback %s\n",evict->evictedKey); + this->stats.incl_wbs++; + } else if (evict->evictedFlags & ITEM_EXCL) { + //fprintf(stderr,"excl writeback %s\n",evict->evictedKey); + //strncpy(wb_key,evict->evictedKey,255); + if ( (options.rand_admit && wb == 0) || + (options.threshold && (key_hist[wb_key] == 1)) || + (options.wb_all) ) { + int ret = add_to_wb_keys(wb_key); + if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + } + this->stats.excl_wbs++; + } + } + /* + if (evict->serverFlags & ITEM_SIZE_CHANGE && OP_src(op) == SRC_DIRECT_SET) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + if (evict->serverFlags & ITEM_INCL) { + int index = lrand48() % (1024 * 1024); + int valuelen = op->valuelen; + //the item's size was changed, issue a SET to L2 as a new command + issue_set(key, &random_char[index], valuelen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_L2_M); + } + } + */ + if (OP_src(op) == SRC_DIRECT_SET) { + if ( (evict->serverFlags & ITEM_SIZE_CHANGE) || ((evict->serverFlags & ITEM_WAS_HIT) == 0)) { + this->stats.set_misses_l1++; + } else if (OP_excl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_excl_hits_l1++; + } else if (OP_incl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_incl_hits_l1++; + } + } + } + del_wb_keys(op->key); + finish_op(op,1); + break; + case Operation::TOUCH: + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + DIE("not implemented"); + } + + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + + } + + + double now = get_time(); + if (check_exit_condition(now)) { + return; + } + + last_tx = now; + stats.log_op(op_queue_size[1]); + stats.log_op(op_queue_size[2]); + //for (int i = 1; i <= 2; i++) { + // fprintf(stderr,"max issue buf n[%d]: %u\n",i,max_n[i]); + //} + drive_write_machine(); + + // update events + //if (bev != NULL) { + // // no pending response (nothing to read) and output buffer empty (nothing to write) + // if ((op_queue.size() == 0) && (evbuffer_get_length(bufferevent_get_output(bev)) == 0)) { + // bufferevent_disable(bev, EV_WRITE|EV_READ); + // } + //} +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMultiApprox::read_callback2() { + struct evbuffer *input = bufferevent_get_input(bev2); + + Operation *op = NULL; + bool done, found; + + //initially assume found (for sets that may come through here) + //is this correct? do we want to assume true in case that + //GET was found, but wrong value size (i.e. update value) + found = true; + + + //if (op_queue.size() == 0) V("Spurious read callback."); + bool full_read = true; + while (full_read) { + + + int opcode; + uint32_t opaque; + full_read = handle_response(this,input, done, found, opcode, opaque, NULL,2); + if (full_read) { + if (opcode == CMD_NOOP) { +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif + continue; + } + op = op_queue[2][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + if (op->key.length() < 1) { +#ifdef DEBUGMC + char out2[128]; + sprintf(out2,"conn l2: %u, bad op: %s\n",cid,op->key.c_str()); + write(2,out2,strlen(out2)); +#endif + continue; + } + } else { + break; + } + + + double now = get_time(); + switch (op->type) { + case Operation::GET: + if (done) { + if ( !found && (options.getset || options.getsetorset) ) {// && + //(options.twitter_trace != 1)) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | SRC_L2_M | LOG_OP; + issue_set(key, &random_char[index], valuelen, now, flags | ITEM_L1); + //wb_keys.push_back(op->key); + last_quiet1 = false; + if (OP_incl(op)) { + //wb_keys.push_back(op->key); + issue_set(key, &random_char[index], valuelen, now, flags | ITEM_L2); + last_quiet2 = false; + } + //pthread_mutex_lock(lock); + //fprintf(stderr,"----miss: %s----\n",key); + //for (auto iter = g_wb_keys->begin(); iter != g_wb_keys->end(); ++iter){ + // fprintf(stderr,"%s,%d\n",iter->first.c_str(),iter->second); + //} + //fprintf(stderr,"----%d----\n",cid); + //pthread_mutex_unlock(lock); + finish_op(op,0); // sets read_state = IDLE + + } else { + if (found) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; + //found in l2, set in l1 + //wb_keys.push_back(op->key); + issue_set(key, &random_char[index],valuelen, now, flags); + this->stats.copies_to_l1++; + //if (OP_excl(op)) { //djb: todo should we delete here for approx or just let it die a slow death? + // issue_delete(key,now, ITEM_L2 | SRC_L1_COPY ); + //} + finish_op(op,1); + + } else { + finish_op(op,0); + } + } + } else { + char out[128]; + sprintf(out,"conn l2: %u, not done reading, should do something",cid); + write(2,out,strlen(out)); + } + break; + case Operation::SET: + if (OP_src(op) == SRC_WB) { + del_wb_keys(op->key); + } + finish_op(op,1); + break; + case Operation::TOUCH: + finish_op(op,0); + break; + case Operation::DELETE: + //check to see if it was a hit + //fprintf(stderr," del %s -- %d from %d\n",op->key.c_str(),found,OP_src(op)); + if (OP_src(op) == SRC_DIRECT_SET) { + if (found) { + this->stats.delete_hits_l2++; + } else { + this->stats.delete_misses_l2++; + } + } + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + DIE("not implemented"); + } + + } + + double now = get_time(); + if (check_exit_condition(now)) { + return; + } + + last_tx = now; + stats.log_op(op_queue_size[2]); + stats.log_op(op_queue_size[1]); + drive_write_machine(); + + // update events + //if (bev != NULL) { + // // no pending response (nothing to read) and output buffer empty (nothing to write) + // if ((op_queue.size() == 0) && (evbuffer_get_length(bufferevent_get_output(bev)) == 0)) { + // bufferevent_disable(bev, EV_WRITE|EV_READ); + // } + //} +} + +/** + * Callback called when write requests finish. + */ +void ConnectionMultiApprox::write_callback() { + + //fprintf(stderr,"loaded evbuffer with ops: %u\n",op_queue.size()); +} + +/** + * Callback for timer timeouts. + */ +void ConnectionMultiApprox::timer_callback() { + //fprintf(stderr,"timer up: %d\n",cid); + drive_write_machine(); +} + + +/* The follow are C trampolines for libevent callbacks. */ +void bev_event_cb1_approx(struct bufferevent *bev, short events, void *ptr) { + + ConnectionMultiApprox* conn = (ConnectionMultiApprox*) ptr; + conn->event_callback1(events); +} + +/* The follow are C trampolines for libevent callbacks. */ +void bev_event_cb2_approx(struct bufferevent *bev, short events, void *ptr) { + + ConnectionMultiApprox* conn = (ConnectionMultiApprox*) ptr; + conn->event_callback2(events); +} + +void bev_read_cb1_approx(struct bufferevent *bev, void *ptr) { + ConnectionMultiApprox* conn = (ConnectionMultiApprox*) ptr; + conn->read_callback1(); +} + + +void bev_read_cb2_approx(struct bufferevent *bev, void *ptr) { + ConnectionMultiApprox* conn = (ConnectionMultiApprox*) ptr; + conn->read_callback2(); +} + +void bev_write_cb_m_approx(struct bufferevent *bev, void *ptr) { +} + +void timer_cb_m_approx(evutil_socket_t fd, short what, void *ptr) { + ConnectionMultiApprox* conn = (ConnectionMultiApprox*) ptr; + conn->timer_callback(); +} + diff --git a/Protocol.cc b/Protocol.cc index 89d52de..2a46f40 100644 --- a/Protocol.cc +++ b/Protocol.cc @@ -503,7 +503,7 @@ bool ProtocolBinary::setup_connection_w() { string user = string(opts.username); string pass = string(opts.password); - binary_header_t header = {0x80, CMD_SASL, 0, 0, 0, {0}, 0, 0, 0}; + binary_header_t header = {0x80, CMD_SASL, 0, 0, 0, 0, 0, 0, 0}; header.key_len = htons(5); header.body_len = htonl(6 + user.length() + 1 + pass.length()); @@ -538,7 +538,7 @@ int ProtocolBinary::get_request(const char* key, uint32_t opaque) { // each line is 4-bytes binary_header_t h = { 0x80, CMD_GET, htons(keylen), - 0x00, 0x00, {htons(0)}, + 0x00, 0x00, htons(0), htonl(keylen) }; h.opaque = htonl(opaque); @@ -561,7 +561,7 @@ int ProtocolBinary::set_request(const char* key, const char* value, int len, uin // each line is 4-bytes binary_header_t h = { 0x80, CMD_SET, htons(keylen), - 0x08, 0x00, {htons(0)}, + 0x08, 0x00, htons(0), htonl(keylen + 8 + len) }; h.opaque = htonl(opaque); //bufferevent_write(bev, &h, 32); // With extras diff --git a/cmdline.ggo b/cmdline.ggo index c33f3f3..25f9a55 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -13,6 +13,7 @@ text "\nBasic options:" option "server" s "Memcached server hostname[:port]. \ Repeat to specify multiple servers." string multiple option "unix_socket" - "Use UNIX socket instead of TCP." +option "approx" - "approximate two level caching with inclusive/exclusive" option "inclusives" - "give a list of 1 == inclusive, 2 == exclusives for each class" string default="" option "binary" - "Use binary memcached protocol instead of ASCII." option "redis" - "Use Redis RESP protocol instead of memchached." diff --git a/mutilate.cc b/mutilate.cc index b304070..bfce272 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -78,7 +78,7 @@ struct thread_data { options_t *options; bool master; // Thread #0, not to be confused with agent master. #ifdef HAVE_LIBZMQ - zmq::socket_t *socket; + zmq::socket_t *socketz; #endif int id; //std::vector*> trace_queue; @@ -109,7 +109,7 @@ void init_random_stuff(); void go(const vector &servers, options_t &options, ConnectionStats &stats #ifdef HAVE_LIBZMQ -, zmq::socket_t* socket = NULL +, zmq::socket_t* socketz = NULL #endif ); @@ -118,7 +118,7 @@ void go(const vector &servers, options_t &options, void do_mutilate(const vector &servers, options_t &options, ConnectionStats &stats,std::vector*> *trace_queue, pthread_mutex_t *g_lock, unordered_map *g_wb_keys, bool master = true #ifdef HAVE_LIBZMQ -, zmq::socket_t* socket = NULL +, zmq::socket_t* socketz = NULL #endif ); void args_to_options(options_t* options); @@ -126,19 +126,19 @@ void* thread_main(void *arg); void* reader_thread(void *arg); #ifdef HAVE_LIBZMQ -static std::string s_recv (zmq::socket_t &socket) { +static std::string s_recv (zmq::socket_t &socketz) { zmq::message_t message; - socket.recv(&message); + socketz.recv(&message); return std::string(static_cast(message.data()), message.size()); } // Convert string to 0MQ string and send to socket -static bool s_send (zmq::socket_t &socket, const std::string &string) { +static bool s_send (zmq::socket_t &socketz, const std::string &string) { zmq::message_t message(string.size()); memcpy(message.data(), string.data(), string.size()); - return socket.send(message); + return socketz.send(message); } /* @@ -198,21 +198,21 @@ static bool s_send (zmq::socket_t &socket, const std::string &string) { void agent() { zmq::context_t context(1); - zmq::socket_t socket(context, ZMQ_REP); + zmq::socket_t socketz(context, ZMQ_REP); if (atoi(args.agent_port_arg) == -1) { - socket.bind(string("ipc:///tmp/memcached.sock").c_str()); + socketz.bind(string("ipc:///tmp/memcached.sock").c_str()); } else { - socket.bind((string("tcp://*:")+string(args.agent_port_arg)).c_str()); + socketz.bind((string("tcp://*:")+string(args.agent_port_arg)).c_str()); } while (true) { zmq::message_t request; - socket.recv(&request); + socketz.recv(&request); zmq::message_t num(sizeof(int)); *((int *) num.data()) = args.threads_arg * args.lambda_mul_arg; - socket.send(num); + socketz.send(num); options_t options; memcpy(&options, request.data(), sizeof(options)); @@ -220,8 +220,8 @@ void agent() { vector servers; for (int i = 0; i < options.server_given; i++) { - servers.push_back(s_recv(socket)); - s_send(socket, "ACK"); + servers.push_back(s_recv(socketz)); + s_send(socketz, "ACK"); } for (auto i: servers) { @@ -230,9 +230,9 @@ void agent() { options.threads = args.threads_arg; - socket.recv(&request); + socketz.recv(&request); options.lambda_denom = *((int *) request.data()); - s_send(socket, "THANKS"); + s_send(socketz, "THANKS"); // V("AGENT SLEEPS"); sleep(1); options.lambda = (double) options.qps / options.lambda_denom * args.lambda_mul_arg; @@ -245,7 +245,7 @@ void agent() { ConnectionStats stats; - go(servers, options, stats, &socket); + go(servers, options, stats, &socketz); AgentStats as; @@ -258,11 +258,11 @@ void agent() { as.stop = stats.stop; as.skips = stats.skips; - string req = s_recv(socket); + string req = s_recv(socketz); // V("req = %s", req.c_str()); request.rebuild(sizeof(as)); memcpy(request.data(), &as, sizeof(as)); - socket.send(request); + socketz.send(request); } } @@ -365,7 +365,7 @@ void finish_agent(ConnectionStats &stats) { * skew. */ -void sync_agent(zmq::socket_t* socket) { +void sync_agent(zmq::socket_t* socketz) { // V("agent: synchronizing"); if (args.agent_given) { @@ -384,16 +384,16 @@ void sync_agent(zmq::socket_t* socket) { if (s_recv(*s).compare(string("ack"))) DIE("sync_agent[M]: out of sync [2]"); } else if (args.agentmode_given) { - if (s_recv(*socket).compare(string("sync_req"))) + if (s_recv(*socketz).compare(string("sync_req"))) DIE("sync_agent[A]: out of sync [1]"); /* The real sync */ - s_send(*socket, "sync"); - if (s_recv(*socket).compare(string("proceed"))) + s_send(*socketz, "sync"); + if (s_recv(*socketz).compare(string("proceed"))) DIE("sync_agent[A]: out of sync [2]"); /* End sync */ - s_send(*socket, "ack"); + s_send(*socketz, "ack"); } // V("agent: synchronized"); @@ -733,7 +733,7 @@ int main(int argc, char **argv) { void go(const vector& servers, options_t& options, ConnectionStats &stats #ifdef HAVE_LIBZMQ -, zmq::socket_t* socket +, zmq::socket_t* socketz #endif ) { #ifdef HAVE_LIBZMQ @@ -808,7 +808,7 @@ void go(const vector& servers, options_t& options, td[t].g_lock = g_lock; td[t].g_wb_keys = g_wb_keys; #ifdef HAVE_LIBZMQ - td[t].socket = socket; + td[t].socketz = socketz; #endif if (t == 0) td[t].master = true; else td[t].master = false; @@ -870,13 +870,13 @@ void go(const vector& servers, options_t& options, } else if (options.threads == 1) { do_mutilate(servers, options, stats, trace_queue, g_lock, g_wb_keys, true #ifdef HAVE_LIBZMQ -, socket +, socketz #endif ); } else { #ifdef HAVE_LIBZMQ if (args.agent_given) { - sync_agent(socket); + sync_agent(socketz); } #endif } @@ -1084,6 +1084,7 @@ void* reader_thread(void *arg) { break; } appid = (stoi(rApp)) % trace_queue->size(); + if (appid == 0) appid = 1; //appid = (nout) % trace_queue->size(); } else { continue; @@ -1249,7 +1250,7 @@ void* thread_main(void *arg) { do_mutilate(*td->servers, *td->options, *cs, td->trace_queue, td->g_lock, td->g_wb_keys, td->master #ifdef HAVE_LIBZMQ -, td->socket +, td->socketz #endif ); @@ -1259,7 +1260,7 @@ void* thread_main(void *arg) { void do_mutilate(const vector& servers, options_t& options, ConnectionStats& stats, vector*> *trace_queue, pthread_mutex_t* g_lock, unordered_map *g_wb_keys, bool master #ifdef HAVE_LIBZMQ -, zmq::socket_t* socket +, zmq::socket_t* socketz #endif ) { int loop_flag = @@ -1407,7 +1408,7 @@ void do_mutilate(const vector& servers, options_t& options, // 2. sync agents: all threads across all agents are now ready // 3. thread barrier: don't release our threads until all agents ready pthread_barrier_wait(&barrier); - if (master) sync_agent(socket); + if (master) sync_agent(socketz); pthread_barrier_wait(&barrier); if (master) V("Synchronized."); @@ -1492,7 +1493,7 @@ void do_mutilate(const vector& servers, options_t& options, if (master) V("Synchronizing."); pthread_barrier_wait(&barrier); - if (master) sync_agent(socket); + if (master) sync_agent(socketz); pthread_barrier_wait(&barrier); if (master) V("Synchronized."); @@ -1546,7 +1547,7 @@ void do_mutilate(const vector& servers, options_t& options, event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); - } else if (servers.size() == 2) { + } else if (servers.size() == 2 && !args.approx_given) { vector connections; vector server_lead; @@ -1700,6 +1701,163 @@ void do_mutilate(const vector& servers, options_t& options, stats.start = start; stats.stop = now; + event_config_free(config); + evdns_base_free(evdns, 0); + event_base_free(base); + } else if (servers.size() == 2 && args.approx_given) { + vector connections; + vector server_lead; + + string hostname1 = servers[0]; + string hostname2 = servers[1]; + string port = "11211"; + + int conns = args.measure_connections_given ? args.measure_connections_arg : + options.connections; + + srand(time(NULL)); + for (int c = 0; c < conns; c++) { + + int fd1 = -1; + + if ( (fd1 = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket error"); + exit(-1); + } + + struct sockaddr_un sin1; + memset(&sin1, 0, sizeof(sin1)); + sin1.sun_family = AF_LOCAL; + strcpy(sin1.sun_path, hostname1.c_str()); + + fcntl(fd1, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state */ + int addrlen; + addrlen = sizeof(sin1); + + int max_tries = 50; + int n_tries = 0; + int s = 10; + while (connect(fd1, (struct sockaddr*)&sin1, addrlen) == -1) { + perror("l1 connect error"); + if (n_tries++ > max_tries) { + fprintf(stderr,"conn l1 %d unable to connect after sleep for %d\n",c+1,s); + exit(-1); + } + int d = s + rand() % 100; + usleep(d); + s = (int)((double)s*1.25); + } + + int fd2 = -1; + if ( (fd2 = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("l2 socket error"); + exit(-1); + } + struct sockaddr_un sin2; + memset(&sin2, 0, sizeof(sin2)); + sin2.sun_family = AF_LOCAL; + strcpy(sin2.sun_path, hostname2.c_str()); + fcntl(fd2, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state */ + addrlen = sizeof(sin2); + n_tries = 0; + s = 10; + while (connect(fd2, (struct sockaddr*)&sin2, addrlen) == -1) { + perror("l2 connect error"); + if (n_tries++ > max_tries) { + fprintf(stderr,"conn l2 %d unable to connect after sleep for %d\n",c+1,s); + exit(-1); + } + int d = s + rand() % 100; + usleep(d); + s = (int)((double)s*1.25); + } + + + ConnectionMultiApprox* conn = new ConnectionMultiApprox(base, evdns, + hostname1, hostname2, port, options,args.agentmode_given ? false : true, fd1, fd2); + + int connected = 0; + if (conn) { + connected = 1; + } + int cid = conn->get_cid(); + + if (connected) { + fprintf(stderr,"cid %d gets l1 fd %d l2 fd %d\n",cid,fd1,fd2); + fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front().key.c_str()); + if (g_lock != NULL) { + conn->set_g_wbkeys(g_wb_keys); + conn->set_lock(g_lock); + } + conn->set_queue(trace_queue->at(cid)); + connections.push_back(conn); + } else { + fprintf(stderr,"conn multi: %d, not connected!!\n",c); + + } + } + + // wait for all threads to reach here + pthread_barrier_wait(&barrier); + + fprintf(stderr,"thread %ld gtg\n",pthread_self()); + // Wait for all Connections to become IDLE. + while (1) { + // FIXME: If all connections become ready before event_base_loop + // is called, this will deadlock. + event_base_loop(base, EVLOOP_ONCE); + + bool restart = false; + for (ConnectionMultiApprox *conn: connections) + if (!conn->is_ready()) restart = true; + + if (restart) continue; + else break; + } + + + + double start = get_time(); + double now = start; + for (ConnectionMultiApprox *conn: connections) { + conn->start_time = start; + conn->start(); // Kick the Connection into motion. + } + //fprintf(stderr,"Start = %f\n", start); + + // Main event loop. + while (1) { + event_base_loop(base, loop_flag); + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); + + bool restart = false; + for (ConnectionMultiApprox *conn: connections) { + if (!conn->check_exit_condition(now)) { + restart = true; + } + } + if (restart) continue; + else break; + + } + + + // V("Start = %f", start); + + if (master && !args.scan_given && !args.search_given) + V("stopped at %f options.time = %d", get_time(), options.time); + + // Tear-down and accumulate stats. + for (ConnectionMultiApprox *conn: connections) { + stats.accumulate(conn->stats); + delete conn; + } + + stats.start = start; + stats.stop = now; + event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); From b6442354ae3ec6564629dee6baa57ed968d6313f Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Fri, 11 Mar 2022 14:50:21 -0500 Subject: [PATCH 46/57] updates --- Connection.cc | 19 +- Connection.h | 10 +- ConnectionMulti.cc | 38 ++-- ConnectionMultiApprox.cc | 388 +++++++++++++++++++-------------------- ConnectionOptions.h | 1 + Operation.h | 24 ++- SConstruct | 12 +- cmdline.ggo | 1 + mutilate.cc | 189 ++++++++++++------- 9 files changed, 373 insertions(+), 309 deletions(-) diff --git a/Connection.cc b/Connection.cc index 3cebd6f..9232d91 100644 --- a/Connection.cc +++ b/Connection.cc @@ -78,13 +78,10 @@ void item_trylock_unlock(void *lock, uint32_t cid) { void Connection::output_op(Operation *op, int type, bool found) { char output[1024]; - char k[256]; char a[256]; char s[256]; - memset(k,0,256); memset(a,0,256); memset(s,0,256); - strcpy(k,op->key.c_str()); switch (type) { case 0: //get sprintf(a,"issue_get"); @@ -123,9 +120,9 @@ void Connection::output_op(Operation *op, int type, bool found) { break; } if (type == 2) { - sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, found: %d, type: %d\n",cid,a,k,op->opaque,found,op->type); + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, found: %d, type: %d\n",cid,a,op->key,op->opaque,found,op->type); } else { - sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, type: %d\n",cid,a,k,op->opaque,op->type); + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, type: %d\n",cid,a,op->key,op->opaque,op->type); } write(2,output,strlen(output)); } @@ -684,7 +681,7 @@ int Connection::issue_get_with_len(const char* key, int valuelen, double now, bo } //pthread_mutex_unlock(&opaque_lock); - op.key = string(key); + strncpy(op.key,key,255); op.valuelen = valuelen; op.type = Operation::GET; //op.hv = hashstr(op.key); @@ -753,7 +750,7 @@ void Connection::issue_get(const char* key, double now) { opaque = 0; } - op.key = string(key); + strncpy(op.key,key,255); op.type = Operation::GET; //op.hv = hashstr(op.key); //item_lock(op.hv,cid); @@ -836,7 +833,7 @@ void Connection::issue_set_miss(const char* key, const char* value, int length) opaque = 0; } - op.key = string(key); + strncpy(op.key,key,255); op.valuelen = length; op.type = Operation::SET; //op.hv = hashstr(op.key); @@ -911,9 +908,9 @@ int Connection::issue_set(const char* key, const char* value, int length, opaque = 0; } - op.key = string(key); op.valuelen = length; op.type = Operation::SET; + strncpy(op.key,key,255); //op.hv = hashstr(op.key); //pthread_mutex_t *lock = (pthread_mutex_t*)item_trylock(op.hv,cid); //if (lock != NULL) { @@ -1281,7 +1278,7 @@ void Connection::read_callback() { write(2,out,strlen(out)); output_op(op,2,found); #endif - if (op->key.length() < 1) { + if (strlen(op->key) < 1) { //char out2[128]; //sprintf(out2,"conn: %u, bad op: %s\n",cid,op->key.c_str()); //write(2,out2,strlen(out2)); @@ -1327,7 +1324,7 @@ void Connection::read_callback() { finish_op(op,1); break; default: - fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); DIE("not implemented"); } diff --git a/Connection.h b/Connection.h index e6128e5..640ab65 100644 --- a/Connection.h +++ b/Connection.h @@ -223,7 +223,7 @@ class ConnectionMulti { int add_to_wb_keys(string wb_key); void del_wb_keys(string wb_key); void set_g_wbkeys(unordered_map *a_wb_keys); - void set_queue(queue *a_trace_queue); + void set_queue(queue *a_trace_queue); void set_lock(pthread_mutex_t* a_lock); private: @@ -291,7 +291,7 @@ class ConnectionMulti { Generator *iagen; pthread_mutex_t* lock; unordered_map *g_wb_keys; - queue *trace_queue; + queue *trace_queue; // state machine functions / event processing void pop_op(Operation *op); @@ -363,7 +363,7 @@ class ConnectionMultiApprox { int add_to_wb_keys(string wb_key); void del_wb_keys(string wb_key); void set_g_wbkeys(unordered_map *a_wb_keys); - void set_queue(queue *a_trace_queue); + void set_queue(queue *a_trace_queue); void set_lock(pthread_mutex_t* a_lock); private: @@ -420,12 +420,12 @@ class ConnectionMultiApprox { uint32_t cid; uint32_t gets; uint32_t gloc; + uint32_t ghits; //std::vector> op_queue; Operation ***op_queue; uint32_t *op_queue_size; - map key_hist; Generator *valuesize; Generator *keysize; @@ -433,7 +433,7 @@ class ConnectionMultiApprox { Generator *iagen; pthread_mutex_t* lock; unordered_map *g_wb_keys; - queue *trace_queue; + queue *trace_queue; // state machine functions / event processing void pop_op(Operation *op); diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 688ad3c..6aa6d83 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -176,7 +176,7 @@ void ConnectionMulti::output_op(Operation *op, int type, bool found) { memset(k,0,256); memset(a,0,256); memset(s,0,256); - strcpy(k,op->key.c_str()); + strncpy(k,op->key,255); switch (type) { case 0: //get sprintf(a,"issue_get"); @@ -295,7 +295,7 @@ ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _e } -void ConnectionMulti::set_queue(queue* a_trace_queue) { +void ConnectionMulti::set_queue(queue* a_trace_queue) { trace_queue = a_trace_queue; trace_queue_n = a_trace_queue->size(); } @@ -451,7 +451,7 @@ int ConnectionMulti::issue_getsetorset(double now) { //pthread_mutex_lock(lock); if (!trace_queue->empty()) { - Operation Op = trace_queue->front(); + Operation Op = *(trace_queue->front()); if (Op.type == Operation::SASL) { eof = 1; cid_rate[cid] = 100; @@ -512,7 +512,7 @@ int ConnectionMulti::issue_getsetorset(double now) { good = evtimer_add(timer, &tv); } if (good != 0) { - fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op.key.c_str()); + fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op.key); return 2; } return 1; @@ -526,7 +526,7 @@ int ConnectionMulti::issue_getsetorset(double now) { char key[256]; memset(key,0,256); - strncpy(key, Op.key.c_str(),255); + strncpy(key, Op.key,255); int vl = Op.valuelen; trace_queue->pop(); @@ -639,7 +639,7 @@ int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double no } #endif - pop->key = string(key); + strncpy(pop->key,key,255); pop->valuelen = valuelen; pop->type = Operation::GET; pop->opaque = opaque[level]++; @@ -713,7 +713,7 @@ int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int } #endif - pop->key = string(key); + strncpy(pop->key,key,255); pop->valuelen = valuelen; pop->type = Operation::TOUCH; pop->opaque = opaque[level]++; @@ -788,7 +788,7 @@ int ConnectionMulti::issue_delete(const char* key, double now, uint32_t flags) { } #endif - pop->key = string(key); + strncpy(pop->key,key,255); pop->type = Operation::DELETE; pop->opaque = opaque[level]++; pop->flags = flags; @@ -872,7 +872,7 @@ int ConnectionMulti::issue_set(const char* key, const char* value, int length, d #endif - pop->key = string(key); + strncpy(pop->key,key,255); pop->valuelen = length; pop->type = Operation::SET; pop->opaque = opaque[level]++; @@ -1335,10 +1335,10 @@ void ConnectionMulti::read_callback1() { write(2,out,strlen(out)); output_op(op,2,found); #endif - if (op->key.length() < 1) { + if (strlen(op->key) < 1) { #ifdef DEBUGMC char out2[128]; - sprintf(out2,"conn l1: %u, bad op: %s\n",cid,op->key.c_str()); + sprintf(out2,"conn l1: %u, bad op: %s\n",cid,op->key); write(2,out2,strlen(out2)); #endif if (evict->evictedKey) free(evict->evictedKey); @@ -1368,7 +1368,7 @@ void ConnectionMulti::read_callback1() { /* issue a get a l2 */ char key[256]; memset(key,0,256); - strncpy(key, op->key.c_str(),255); + strncpy(key, op->key,255); int vl = op->valuelen; int flags = OP_clu(op); issue_get_with_len(key,vl,now,false, flags | SRC_L1_M | ITEM_L2 | LOG_OP, op); @@ -1444,7 +1444,7 @@ void ConnectionMulti::read_callback1() { finish_op(op,1); break; default: - fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); DIE("not implemented"); } @@ -1518,10 +1518,10 @@ void ConnectionMulti::read_callback2() { write(2,out,strlen(out)); output_op(op,2,found); #endif - if (op->key.length() < 1) { + if (strlen(op->key) < 1) { #ifdef DEBUGMC char out2[128]; - sprintf(out2,"conn l2: %u, bad op: %s\n",cid,op->key.c_str()); + sprintf(out2,"conn l2: %u, bad op: %s\n",cid,op->key); write(2,out2,strlen(out2)); #endif continue; @@ -1539,7 +1539,7 @@ void ConnectionMulti::read_callback2() { //(options.twitter_trace != 1)) { char key[256]; memset(key,0,256); - strncpy(key, op->key.c_str(),255); + strncpy(key, op->key,255); int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); int flags = OP_clu(op) | SRC_L2_M | LOG_OP; @@ -1564,7 +1564,7 @@ void ConnectionMulti::read_callback2() { if (found) { char key[256]; memset(key,0,256); - strncpy(key, op->key.c_str(),255); + strncpy(key, op->key,255); int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; @@ -1597,7 +1597,7 @@ void ConnectionMulti::read_callback2() { if (OP_src(op) == SRC_DIRECT_SET) { char key[256]; memset(key,0,256); - strncpy(key, op->key.c_str(),255); + strncpy(key, op->key,255); int valuelen = op->valuelen; if (!found) { int index = lrand48() % (1024 * 1024); @@ -1646,7 +1646,7 @@ void ConnectionMulti::read_callback2() { finish_op(op,1); break; default: - fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); DIE("not implemented"); } diff --git a/ConnectionMultiApprox.cc b/ConnectionMultiApprox.cc index 5e4bc90..0eef921 100644 --- a/ConnectionMultiApprox.cc +++ b/ConnectionMultiApprox.cc @@ -110,7 +110,7 @@ typedef struct _evicted_type { } evicted_t; static vector cid_rate; - +extern map g_key_hist; extern int max_n[3]; static void init_inclusives(char *inclusive_str) { @@ -176,7 +176,7 @@ void ConnectionMultiApprox::output_op(Operation *op, int type, bool found) { memset(k,0,256); memset(a,0,256); memset(s,0,256); - strcpy(k,op->key.c_str()); + strncpy(k,op->key,255); switch (type) { case 0: //get sprintf(a,"issue_get"); @@ -268,7 +268,8 @@ ConnectionMultiApprox::ConnectionMultiApprox(struct event_base* _base, struct ev last_tx = last_rx = 0.0; gets = 0; - gloc = rand() % (1000*2-1)+1; + ghits = 0; + gloc = rand() % (100*2-1)+1; op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); @@ -296,7 +297,7 @@ ConnectionMultiApprox::ConnectionMultiApprox(struct event_base* _base, struct ev } -void ConnectionMultiApprox::set_queue(queue* a_trace_queue) { +void ConnectionMultiApprox::set_queue(queue* a_trace_queue) { trace_queue = a_trace_queue; trace_queue_n = a_trace_queue->size(); } @@ -448,147 +449,134 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { int ret = 0; int nissued = 0; - //while (nissued < options.depth) { + while (nissued < 2) { //pthread_mutex_lock(lock); - if (!trace_queue->empty()) { - Operation Op = trace_queue->front(); - if (Op.type == Operation::SASL) { - eof = 1; - cid_rate[cid] = 100; - fprintf(stderr,"cid %d done\n",cid); - string op_queue1; - string op_queue2; - for (int j = 0; j < 2; j++) { - for (int i = 0; i < OPAQUE_MAX; i++) { - if (op_queue[j+1][i] != NULL) { - if (j == 0) { - op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; - } else { - op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + if (!trace_queue->empty()) { + Operation Op = *(trace_queue->front()); + if (Op.type == Operation::SASL) { + eof = 1; + cid_rate[cid] = 100; + fprintf(stderr,"cid %d done\n",cid); + string op_queue1; + string op_queue2; + for (int j = 0; j < 2; j++) { + for (int i = 0; i < OPAQUE_MAX; i++) { + if (op_queue[j+1][i] != NULL) { + if (j == 0) { + op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + } else { + op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + } } } } + fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + return 1; + } + + + /* check if in global wb queue */ + double percent = (double)total/((double)trace_queue_n) * 100; + if (percent > o_percent+1) { + //update the percentage table and see if we should execute + std::vector::iterator mp = std::min_element(cid_rate.begin(), cid_rate.end()); + double min_percent = *mp; + + if (options.ratelimit && percent > min_percent+2) { + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + int good = 0; + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + good = evtimer_add(timer, &tv); + } + if (good != 0) { + fprintf(stderr,"eventimer is messed up!\n"); + return 2; + } + return 1; + } + cid_rate[cid] = percent; + //fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); + o_percent = percent; } - fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); - return 1; - } - - - /* check if in global wb queue */ - pthread_mutex_lock(lock); - double percent = (double)total/((double)trace_queue_n) * 100; - if (percent > o_percent+1) { - //update the percentage table and see if we should execute - std::vector::iterator mp = std::min_element(cid_rate.begin(), cid_rate.end()); - double min_percent = *mp; - - if (percent > min_percent+2) { - pthread_mutex_unlock(lock); + auto check = g_wb_keys->find(string(Op.key)); + if (check != g_wb_keys->end()) { struct timeval tv; - tv.tv_sec = 1; - tv.tv_usec = 0; + double delay; + delay = last_rx + 0.00025 - now; + double_to_tv(delay,&tv); int good = 0; - if (!event_pending(timer, EV_TIMEOUT, NULL)) { - good = evtimer_add(timer, &tv); - } + //if (!event_pending(timer, EV_TIMEOUT, NULL)) { + good = evtimer_add(timer, &tv); + //} if (good != 0) { - fprintf(stderr,"eventimer is messed up!\n"); + fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op.key); return 2; } return 1; } - cid_rate[cid] = percent; - fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); - o_percent = percent; - } - auto check = g_wb_keys->find(Op.key); - if (check != g_wb_keys->end()) { - pthread_mutex_unlock(lock); - struct timeval tv; - tv.tv_sec = 1; - tv.tv_usec = 0; - int good = 0; - if (!event_pending(timer, EV_TIMEOUT, NULL)) { - good = evtimer_add(timer, &tv); + + + char *key = Op.key; + int vl = Op.valuelen; + + trace_queue->pop(); + + int issued = 0; + int incl = get_incl(vl,strlen(key)); + int cid = get_class(vl,strlen(key)); + int flags = 0; + int index = lrand48() % (1024 * 1024); + //int touch = 1; + SET_INCL(incl,flags); + + switch(Op.type) + { + case Operation::GET: + //if (nissued < options.depth-1) { + // issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); + // last_quiet1 = false; + //} else { + //} + issued = issue_get_with_len(key, vl, now, false, flags | LOG_OP | ITEM_L1); + last_quiet1 = false; + this->stats.gets++; + gets++; + this->stats.gets_cid[cid]++; + + break; + case Operation::SET: + if (last_quiet1) { + issue_noop(now,1); + } + if (incl == 1) { + issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); + } else if (incl == 2) { + issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + } + last_quiet1 = false; + this->stats.sets++; + this->stats.sets_cid[cid]++; + break; + case Operation::DELETE: + case Operation::TOUCH: + case Operation::NOOP: + case Operation::SASL: + fprintf(stderr,"invalid line: %s, vl: %d\n",key,vl); + break; + } - if (good != 0) { - fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op.key.c_str()); - return 2; + if (issued) { + nissued++; + total++; + } else { + fprintf(stderr,"failed to issue line: %s, vl: %d @T: XX \n",key,vl); } - return 1; - } else { - g_wb_keys->insert( {Op.key, cid} ); - //g_wb_keys->insert( {Op.key+"l2", cid} ); - } - pthread_mutex_unlock(lock); - - - - char key[256]; - memset(key,0,256); - strncpy(key, Op.key.c_str(),255); - int vl = Op.valuelen; - - trace_queue->pop(); - - int issued = 0; - int incl = get_incl(vl,strlen(key)); - int cid = get_class(vl,strlen(key)); - int flags = 0; - int index = lrand48() % (1024 * 1024); - //int touch = 1; - SET_INCL(incl,flags); - - switch(Op.type) - { - case Operation::GET: - //if (nissued < options.depth-1) { - // issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); - // last_quiet1 = false; - //} else { - //} - if (options.threshold > 0) { - if (Op.future) { - key_hist[key] = 1; - } - } - issued = issue_get_with_len(key, vl, now, false, flags | LOG_OP | ITEM_L1); - last_quiet1 = false; - this->stats.gets++; - gets++; - this->stats.gets_cid[cid]++; - - break; - case Operation::SET: - if (last_quiet1) { - issue_noop(now,1); - } - if (incl == 1) { - issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); - } else if (incl == 2) { - issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); - } - last_quiet1 = false; - this->stats.sets++; - this->stats.sets_cid[cid]++; - break; - case Operation::DELETE: - case Operation::TOUCH: - case Operation::NOOP: - case Operation::SASL: - fprintf(stderr,"invalid line: %s, vl: %d\n",key,vl); - break; - - } - if (issued) { - nissued++; - total++; } else { - fprintf(stderr,"failed to issue line: %s, vl: %d @T: XX \n",key,vl); + return 1; } - } else { - return 1; } if (last_quiet1) { issue_noop(now,1); @@ -635,7 +623,7 @@ int ConnectionMultiApprox::issue_get_with_len(const char* key, int valuelen, dou } #endif - pop->key = string(key); + strncpy(pop->key,key,255); pop->valuelen = valuelen; pop->type = Operation::GET; pop->opaque = opaque[level]++; @@ -709,7 +697,7 @@ int ConnectionMultiApprox::issue_touch(const char* key, int valuelen, double now } #endif - pop->key = string(key); + strncpy(pop->key,key,255); pop->valuelen = valuelen; pop->type = Operation::TOUCH; pop->opaque = opaque[level]++; @@ -784,7 +772,7 @@ int ConnectionMultiApprox::issue_delete(const char* key, double now, uint32_t fl } #endif - pop->key = string(key); + strncpy(pop->key,key,255); pop->type = Operation::DELETE; pop->opaque = opaque[level]++; pop->flags = flags; @@ -867,8 +855,7 @@ int ConnectionMultiApprox::issue_set(const char* key, const char* value, int len else pop->start_time = now; #endif - - pop->key = string(key); + strncpy(pop->key,key,255); pop->valuelen = length; pop->type = Operation::SET; pop->opaque = opaque[level]++; @@ -950,7 +937,7 @@ void ConnectionMultiApprox::finish_op(Operation *op, int was_hit) { op->end_time = now; #endif - if (options.successful_queries && was_hit) { + if (was_hit) { switch (op->type) { case Operation::GET: switch (OP_level(op)) { @@ -959,6 +946,10 @@ void ConnectionMultiApprox::finish_op(Operation *op, int was_hit) { break; case 2: stats.log_get_l2(*op); + if (op->l1 != NULL) { + op->l1->end_time = now; + stats.log_get(*(op->l1)); + } break; } break; @@ -976,54 +967,57 @@ void ConnectionMultiApprox::finish_op(Operation *op, int was_hit) { case Operation::TOUCH: break; default: DIE("Not implemented."); } - } else { - switch (op->type) { - case Operation::GET: - if (OP_log(op)) { - switch (OP_level(op)) { - case 1: - stats.log_get_l1(*op); - break; - case 2: - stats.log_get_l2(*op); - if (op->l1 != NULL) { - op->l1->end_time = now; - stats.log_get(*(op->l1)); - } - break; - } - } - break; - case Operation::SET: - if (OP_log(op)) { - switch (OP_level(op)) { - case 1: - stats.log_set_l1(*op); - break; - case 2: - stats.log_set_l2(*op); - break; - } - } - break; - case Operation::DELETE: break; - case Operation::TOUCH: break; - default: DIE("Not implemented."); - } } + //} else { + // switch (op->type) { + // case Operation::GET: + // if (OP_log(op)) { + // switch (OP_level(op)) { + // case 1: + // stats.log_get_l1(*op); + // break; + // case 2: + // stats.log_get_l2(*op); + // if (op->l1 != NULL) { + // op->l1->end_time = now; + // stats.log_get(*(op->l1)); + // } + // break; + // } + // } + // break; + // case Operation::SET: + // if (OP_log(op)) { + // switch (OP_level(op)) { + // case 1: + // stats.log_set_l1(*op); + // break; + // case 2: + // stats.log_set_l2(*op); + // break; + // } + // } + // break; + // case Operation::DELETE: break; + // case Operation::TOUCH: break; + // default: DIE("Not implemented."); + // } + //} last_rx = now; uint8_t level = OP_level(op); if (op->l1 != NULL) { - delete op_queue[1][op->l1->opaque]; + //delete op_queue[1][op->l1->opaque]; op_queue[1][op->l1->opaque] = 0; op_queue_size[1]--; + delete op->l1; } //op_queue[level].erase(op_queue[level].begin()+opopq); if (op == op_queue[level][op->opaque] && op->opaque == op_queue[level][op->opaque]->opaque) { - delete op_queue[level][op->opaque]; + //delete op_queue[level][op->opaque]; op_queue[level][op->opaque] = 0; + delete op; } else { fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", op,op_queue[level][op->opaque],op->opaque,op_queue[level][op->opaque]->opaque); @@ -1331,10 +1325,10 @@ void ConnectionMultiApprox::read_callback1() { write(2,out,strlen(out)); output_op(op,2,found); #endif - if (op->key.length() < 1) { + if (strlen(op->key) < 1) { #ifdef DEBUGMC char out2[128]; - sprintf(out2,"conn l1: %u, bad op: %s\n",cid,op->key.c_str()); + sprintf(out2,"conn l1: %u, bad op: %s\n",cid,op->key); write(2,out2,strlen(out2)); #endif if (evict->evictedKey) free(evict->evictedKey); @@ -1360,27 +1354,24 @@ void ConnectionMultiApprox::read_callback1() { switch (op->type) { case Operation::GET: if (done) { - char key[256]; - memset(key,0,256); - strncpy(key, op->key.c_str(),255); - //int touch = (rand() % 100); int vl = op->valuelen; if ( !found && (options.getset || options.getsetorset) ) { /* issue a get a l2 */ int flags = OP_clu(op); - issue_get_with_len(key,vl,now,false, flags | SRC_L1_M | ITEM_L2 | LOG_OP, op); + issue_get_with_len(op->key,vl,now,false, flags | SRC_L1_M | ITEM_L2 | LOG_OP, op); op->end_time = now; this->stats.log_get_l1(*op); //finish_op(op,0); } else { - if (OP_incl(op) && gets == gloc) { - issue_touch(key,vl,now, ITEM_L2 | SRC_L1_H); - gloc += rand()%(1000*2-1)+1; + if (OP_incl(op) && ghits >= gloc) { + issue_touch(op->key,vl,now, ITEM_L2 | SRC_L1_H); + gloc += rand()%(100*2-1)+1; } - del_wb_keys(op->key); - finish_op(op,found); + ghits++; + //del_wb_keys(op->key); + finish_op(op,1); } } else { char out[128]; @@ -1396,18 +1387,16 @@ void ConnectionMultiApprox::read_callback1() { if (evict->evicted) { string wb_key(evict->evictedKey); if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { - //wb_keys.push_back(wb_key); int ret = add_to_wb_keys(wb_key); if (ret == 1) { - issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_WB); + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_WB | ITEM_DIRTY); } - //fprintf(stderr,"incl writeback %s\n",evict->evictedKey); this->stats.incl_wbs++; } else if (evict->evictedFlags & ITEM_EXCL) { //fprintf(stderr,"excl writeback %s\n",evict->evictedKey); //strncpy(wb_key,evict->evictedKey,255); if ( (options.rand_admit && wb == 0) || - (options.threshold && (key_hist[wb_key] == 1)) || + (options.threshold && (g_key_hist[wb_key] == 1)) || (options.wb_all) ) { int ret = add_to_wb_keys(wb_key); if (ret == 1) { @@ -1439,14 +1428,14 @@ void ConnectionMultiApprox::read_callback1() { } } } - del_wb_keys(op->key); + //del_wb_keys(op->key); finish_op(op,1); break; case Operation::TOUCH: finish_op(op,1); break; default: - fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); DIE("not implemented"); } @@ -1520,10 +1509,10 @@ void ConnectionMultiApprox::read_callback2() { write(2,out,strlen(out)); output_op(op,2,found); #endif - if (op->key.length() < 1) { + if (strlen(op->key) < 1) { #ifdef DEBUGMC char out2[128]; - sprintf(out2,"conn l2: %u, bad op: %s\n",cid,op->key.c_str()); + sprintf(out2,"conn l2: %u, bad op: %s\n",cid,op->key); write(2,out2,strlen(out2)); #endif continue; @@ -1539,18 +1528,15 @@ void ConnectionMultiApprox::read_callback2() { if (done) { if ( !found && (options.getset || options.getsetorset) ) {// && //(options.twitter_trace != 1)) { - char key[256]; - memset(key,0,256); - strncpy(key, op->key.c_str(),255); int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); int flags = OP_clu(op) | SRC_L2_M | LOG_OP; - issue_set(key, &random_char[index], valuelen, now, flags | ITEM_L1); + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L1); //wb_keys.push_back(op->key); last_quiet1 = false; if (OP_incl(op)) { //wb_keys.push_back(op->key); - issue_set(key, &random_char[index], valuelen, now, flags | ITEM_L2); + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L2); last_quiet2 = false; } //pthread_mutex_lock(lock); @@ -1564,18 +1550,16 @@ void ConnectionMultiApprox::read_callback2() { } else { if (found) { - char key[256]; - memset(key,0,256); - strncpy(key, op->key.c_str(),255); int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; //found in l2, set in l1 //wb_keys.push_back(op->key); - issue_set(key, &random_char[index],valuelen, now, flags); + issue_set(op->key, &random_char[index],valuelen, now, flags); this->stats.copies_to_l1++; + //djb: this is automatically done in the L2 server //if (OP_excl(op)) { //djb: todo should we delete here for approx or just let it die a slow death? - // issue_delete(key,now, ITEM_L2 | SRC_L1_COPY ); + // issue_delete(op->key,now, ITEM_L2 | SRC_L1_COPY ); //} finish_op(op,1); @@ -1591,7 +1575,7 @@ void ConnectionMultiApprox::read_callback2() { break; case Operation::SET: if (OP_src(op) == SRC_WB) { - del_wb_keys(op->key); + del_wb_keys(string(op->key)); } finish_op(op,1); break; @@ -1611,7 +1595,7 @@ void ConnectionMultiApprox::read_callback2() { finish_op(op,1); break; default: - fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); DIE("not implemented"); } diff --git a/ConnectionOptions.h b/ConnectionOptions.h index a33940c..8f33388 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -6,6 +6,7 @@ typedef struct { int apps; int rand_admit; + bool ratelimit; int threshold; int wb_all; bool miss_through; diff --git a/Operation.h b/Operation.h index e2faf10..ceb0531 100644 --- a/Operation.h +++ b/Operation.h @@ -8,6 +8,20 @@ using namespace std; class Operation { public: + Operation() { + valuelen = 0; + opaque = 0; + flags = 0; + clsid = 0; + future = 0; + curr = 0; + l1 = NULL; + type = NOOP; + appid = 0; + start_time = 0; + end_time = 0; + memset(key,0,256); + } double start_time, end_time; enum type_enum { @@ -15,14 +29,14 @@ class Operation { }; type_enum type; - - int valuelen; + uint16_t appid; + uint32_t valuelen; uint32_t opaque; uint32_t flags; uint16_t clsid; - uint32_t future; - uint32_t curr; - string key; + uint8_t future; + uint8_t curr; + char key[256]; Operation *l1; double time() const { return (end_time - start_time) * 1000000; } diff --git a/SConstruct b/SConstruct index 65ffeba..3ba7049 100644 --- a/SConstruct +++ b/SConstruct @@ -44,10 +44,12 @@ if not conf.CheckFunc('pthread_barrier_init'): env = conf.Finish() -#env.Append(CFLAGS = ' -O0 -Wall -g') -#env.Append(CPPFLAGS = ' -O0 -Wall -g') -env.Append(CFLAGS = ' -O3 -Wall -g') -env.Append(CPPFLAGS = ' -O3 -Wall -g') +env.Append(CFLAGS = ' -O0 -Wall -g --std=c++17 -lstdc++fs') +env.Append(CPPFLAGS = ' -O0 -Wall -g --std=c++17 -lstdc++fs') +#env.Append(CFLAGS = ' -O3 -Wall -g --std=c++17 -lstdc++fs') +#env.Append(CPPFLAGS = ' -O3 -Wall -g --std=c++17 -lstdc++fs') +#env.Append(CFLAGS = ' -O3 -Wall -g') +#env.Append(CPPFLAGS = ' -O3 -Wall -g') #env.Append(LDFLAGS = '-fsantize=address') #env.Append(CFLAGS = ' -O3 -Wall -g -fsantize=address') #env.Append(CPPFLAGS = ' -O3 -Wall -g -fsanitize=address') @@ -60,7 +62,7 @@ env.Append(CPPFLAGS = ' -O3 -Wall -g') env.Command(['cmdline.cc', 'cmdline.h'], 'cmdline.ggo', 'gengetopt < $SOURCE') src = Split("""mutilate.cc cmdline.cc log.cc distributions.cc util.cc - Connection.cc ConnectionMulti.cc Protocol.cc Generator.cc""") + Connection.cc ConnectionMulti.cc ConnectionMultiApprox.cc Protocol.cc Generator.cc""") if not env['HAVE_POSIX_BARRIER']: # USE_POSIX_BARRIER: src += ['barrier.cc'] diff --git a/cmdline.ggo b/cmdline.ggo index 25f9a55..3fba6bb 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -10,6 +10,7 @@ option "quiet" - "Disable log messages." text "\nBasic options:" +option "ratelimit" - "limit conns from exceeding each other in requests" option "server" s "Memcached server hostname[:port]. \ Repeat to specify multiple servers." string multiple option "unix_socket" - "Use UNIX socket instead of TCP." diff --git a/mutilate.cc b/mutilate.cc index bfce272..a0b4c1e 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -17,6 +17,8 @@ #include #include #include +#include +namespace fs = std::filesystem; #include #include @@ -64,6 +66,8 @@ int reader_not_ready = 1; pthread_mutex_t *item_locks; int item_lock_hashpower = 13; + +map g_key_hist; gengetopt_args_info args; char random_char[4 * 1024 * 1024]; // Buffer used to generate random values. @@ -82,7 +86,7 @@ struct thread_data { #endif int id; //std::vector*> trace_queue; - std::vector*> *trace_queue; + std::vector*> *trace_queue; //std::vector *mutexes; pthread_mutex_t* g_lock; std::unordered_map *g_wb_keys; @@ -90,7 +94,7 @@ struct thread_data { struct reader_data { //std::vector*> trace_queue; - std::vector*> *trace_queue; + std::vector*> *trace_queue; std::vector *mutexes; string *trace_filename; int twitter_trace; @@ -116,7 +120,7 @@ void go(const vector &servers, options_t &options, //void do_mutilate(const vector &servers, options_t &options, // ConnectionStats &stats,std::vector*> trace_queue, bool master = true void do_mutilate(const vector &servers, options_t &options, - ConnectionStats &stats,std::vector*> *trace_queue, pthread_mutex_t *g_lock, unordered_map *g_wb_keys, bool master = true + ConnectionStats &stats,std::vector*> *trace_queue, pthread_mutex_t *g_lock, unordered_map *g_wb_keys, bool master = true #ifdef HAVE_LIBZMQ , zmq::socket_t* socketz = NULL #endif @@ -743,7 +747,7 @@ void go(const vector& servers, options_t& options, #endif //std::vector*> trace_queue; // = (ConcurrentQueue**)malloc(sizeof(ConcurrentQueue) - std::vector*> *trace_queue = new std::vector*>(); + std::vector*> *trace_queue = new std::vector*>(); // = (ConcurrentQueue**)malloc(sizeof(ConcurrentQueue) //std::vector *mutexes = new std::vector(); pthread_mutex_t *g_lock = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); @@ -756,7 +760,7 @@ void go(const vector& servers, options_t& options, // pthread_mutex_t *lock = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); // *lock = PTHREAD_MUTEX_INITIALIZER; // mutexes->push_back(lock); - trace_queue->push_back(new std::queue()); + trace_queue->push_back(new std::queue()); } pthread_mutex_init(&reader_l, NULL); pthread_cond_init(&reader_ready, NULL); @@ -989,12 +993,21 @@ static char *get_stream(ZSTD_DCtx* dctx, FILE *fin, size_t const buffInSize, voi void* reader_thread(void *arg) { struct reader_data *rdata = (struct reader_data *) arg; //std::vector*> trace_queue = (std::vector*>) rdata->trace_queue; - std::vector*> *trace_queue = (std::vector*>*) rdata->trace_queue; + std::vector*> *trace_queue = (std::vector*>*) rdata->trace_queue; // std::vector *mutexes = (std::vector*) rdata->mutexes; int twitter_trace = rdata->twitter_trace; string fn = *(rdata->trace_filename); srand(time(NULL)); if (hasEnding(fn,".zst")) { + string blobfile = fs::path( fn ).filename(); + blobfile.erase(blobfile.length()-4); + blobfile.insert(0,"/dev/shm/"); + blobfile.append(".data"); + int do_blob = 0; + int blob = 0; + if (do_blob) { + blob = open(blobfile.c_str(),O_CREAT | O_APPEND | O_RDWR, S_IRWXU); + } //init const char *filename = fn.c_str(); FILE* const fin = fopen_orDie(filename, "rb"); @@ -1003,7 +1016,7 @@ void* reader_thread(void *arg) { size_t const buffOutSize = ZSTD_DStreamOutSize()*1000; void* const buffOut = malloc_orDie(buffOutSize); - map key_hist; + map key_hist; ZSTD_DCtx* const dctx = ZSTD_createDCtx(); //CHECK(dctx != NULL, "ZSTD_createDCtx() failed!"); //char *leftover = malloc(buffOutSize); @@ -1011,6 +1024,7 @@ void* reader_thread(void *arg) { //char *trace = (char*)decompress(filename); uint64_t nwrites = 0; uint64_t nout = 1; + int batch = 0; int cappid = 1; fprintf(stderr,"%lu trace queues for connections\n",trace_queue->size()); char *trace = get_stream(dctx, fin, buffInSize, buffIn, buffOutSize, buffOut); @@ -1023,6 +1037,7 @@ void* reader_thread(void *arg) { string full_line(line); //check the appid int appid = 0; + int first = 1; if (full_line.length() > 10) { if (trace_queue->size() > 0) { @@ -1032,7 +1047,7 @@ void* reader_thread(void *arg) { string rKey; string rOp; string rvaluelen; - Operation Op; + Operation *Op = new Operation; if (twitter_trace == 1) { string rKeySize; size_t n = std::count(full_line.begin(), full_line.end(), ','); @@ -1044,21 +1059,23 @@ void* reader_thread(void *arg) { getline( ss, rApp, ',' ); getline( ss, rOp, ',' ); if (rOp.compare("get") == 0) { - Op.type = Operation::GET; + Op->type = Operation::GET; } else if (rOp.compare("set") == 0) { - Op.type = Operation::SET; + Op->type = Operation::SET; } if (rvaluelen.compare("") == 0 || rvaluelen.size() < 1 || rvaluelen.empty()) { continue; } - //appid = cappid; - //if (nout % 1000 == 0) { - // cappid++; - // cappid = cappid % trace_queue->size(); - // if (cappid == 0) cappid = 1; - //} - appid = stoi(rApp) % trace_queue->size(); + appid = cappid; + if (nout % 1000 == 0) { + cappid++; + cappid = cappid % trace_queue->size(); + if (cappid == 0) cappid = 1; + } + //appid = stoi(rApp) % trace_queue->size(); if (appid == 0) appid = 1; + //appid = (rand() % (trace_queue->size()-1)) + 1; + //if (appid == 0) appid = 1; } else { @@ -1077,10 +1094,10 @@ void* reader_thread(void *arg) { int ot = stoi(rOp); switch (ot) { case 1: - Op.type = Operation::GET; + Op->type = Operation::GET; break; case 2: - Op.type = Operation::SET; + Op->type = Operation::SET; break; } appid = (stoi(rApp)) % trace_queue->size(); @@ -1101,27 +1118,25 @@ void* reader_thread(void *arg) { int ot = stoi(rOp); switch (ot) { case 1: - Op.type = Operation::GET; + Op->type = Operation::GET; break; case 2: - Op.type = Operation::SET; + Op->type = Operation::SET; break; } - - //appid = (rand() % (trace_queue->size()-1)) + 1; - appid = cappid; - if (nout % 1000 == 0) { - cappid++; - cappid = cappid % trace_queue->size(); - if (cappid == 0) cappid = 1; + if (first) { + appid = (rand() % (trace_queue->size()-1)) + 1; + if (appid == 0) appid = 1; + first = 0; } + batch++; } else { continue; } } else if (twitter_trace == 4) { size_t n = std::count(full_line.begin(), full_line.end(), ','); - if (n == 3) { + if (n == 4) { getline( ss, rT, ','); getline( ss, rKey, ',' ); getline( ss, rOp, ',' ); @@ -1129,10 +1144,10 @@ void* reader_thread(void *arg) { int ot = stoi(rOp); switch (ot) { case 1: - Op.type = Operation::GET; + Op->type = Operation::GET; break; case 2: - Op.type = Operation::SET; + Op->type = Operation::SET; break; } if (rvaluelen == "0") { @@ -1140,33 +1155,39 @@ void* reader_thread(void *arg) { } appid = (rand() % (trace_queue->size()-1)) + 1; - //appid = cappid; - //if (nout % 1000 == 0) { - // cappid++; - // cappid = cappid % trace_queue->size(); - // if (cappid == 0) cappid = 1; - //} + if (appid == 0) appid = 1; } else { continue; } } int vl = stoi(rvaluelen); if (appid < (int)trace_queue->size() && vl < 524000 && vl > 1) { - Op.valuelen = vl; - Op.key = rKey; - if (Op.type == Operation::GET) { + Op->valuelen = vl; + strncpy(Op->key,rKey.c_str(),255);; + if (Op->type == Operation::GET) { //find when was last read - Operation last_op = key_hist[rKey]; - if (last_op.valuelen > 0) { - last_op.future = nout; //THE FUTURE IS NOW - Op.curr = nout; + Operation *last_op = key_hist[rKey]; + if (last_op != NULL) { + last_op->future = 1; //THE FUTURE IS NOW + Op->curr = 1; + Op->future = 0; key_hist[rKey] = Op; + g_key_hist[rKey] = 1; } else { //first ref + Op->curr = 1; + Op->future = 0; key_hist[rKey] = Op; + g_key_hist[rKey] = 0; } } + Op->appid = appid; trace_queue->at(appid)->push(Op); + if (twitter_trace == 3 && batch == 2) { + appid = (rand() % (trace_queue->size()-1)) + 1; + if (appid == 0) appid = 1; + batch = 0; + } } } else { fprintf(stderr,"big error!\n"); @@ -1180,12 +1201,6 @@ void* reader_thread(void *arg) { //} nout++; if (nout % 1000000 == 0) fprintf(stderr,"decompressed requests: %lu, waits: %lu\n",nout,nwrites); - //if (n > 100000000) { - // pthread_mutex_lock(&reader_l); - // reader_not_ready = 0; - // pthread_mutex_unlock(&reader_l); - // pthread_cond_signal(&reader_ready); - //} } free(line_p); @@ -1193,17 +1208,32 @@ void* reader_thread(void *arg) { trace = get_stream(dctx, fin, buffInSize, buffIn, buffOutSize, buffOut); } - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < 10; i++) { for (int j = 0; j < (int)trace_queue->size(); j++) { //trace_queue[j]->enqueue(eof); - Operation eof; - eof.type = Operation::SASL; + Operation *eof = new Operation; + eof->type = Operation::SASL; + eof->appid = j; trace_queue->at(j)->push(eof); if (i == 0) { fprintf(stderr,"appid %d, tq size: %ld\n",j,trace_queue->at(j)->size()); } } } + if (do_blob) { + for (int i = 0; i < (int)trace_queue->size(); i++) { + queue tmp = *(trace_queue->at(i)); + while (!tmp.empty()) { + Operation *Op = tmp.front(); + int br = write(blob,(void*)(Op),sizeof(Operation)); + if (br != sizeof(Operation)) { + fprintf(stderr,"error writing op!\n"); + } + tmp.pop(); + } + + } + } pthread_mutex_lock(&reader_l); if (reader_not_ready) { @@ -1220,7 +1250,41 @@ void* reader_thread(void *arg) { free(buffOut); - } //else { + } else if (hasEnding(fn,".data")) { + ifstream trace_file (fn, ios::in | ios::binary); + uint32_t treqs = 0; + char *ops = (char*)malloc(sizeof(Operation)*1000000); + Operation *optr = (Operation*)(ops); + while (trace_file.good()) { + trace_file.read((char*)ops,sizeof(Operation)*1000000); + int tbytes = trace_file.gcount(); + int tops = tbytes/sizeof(Operation); + for (int i = 0; i < tops; i++) { + Operation *op = (Operation*)optr; + string rKey = string(op->key); + g_key_hist[rKey] = 0; + if (op->future) { + g_key_hist[rKey] = 1; + } + trace_queue->at(op->appid)->push(op); + treqs++; + if (treqs % 1000000 == 0) fprintf(stderr,"loaded requests: %u\n",treqs); + optr++; + + } + optr = (Operation*)ops; + } + trace_file.close(); + + pthread_mutex_lock(&reader_l); + if (reader_not_ready) { + reader_not_ready = 0; + } + pthread_mutex_unlock(&reader_l); + pthread_cond_signal(&reader_ready); + + } + //else { //ifstream trace_file; //trace_file.open(rdata->trace_filename); @@ -1241,11 +1305,11 @@ void* reader_thread(void *arg) { void* thread_main(void *arg) { struct thread_data *td = (struct thread_data *) arg; int num_cores = sysconf(_SC_NPROCESSORS_ONLN); - int res = stick_this_thread_to_core(td->id % num_cores); - if (res != 0) { - DIE("pthread_attr_setaffinity_np(%d) failed: %s", - td->id, strerror(res)); - } + //int res = stick_this_thread_to_core(td->id % num_cores); + //if (res != 0) { + // DIE("pthread_attr_setaffinity_np(%d) failed: %s", + // td->id, strerror(res)); + //} ConnectionStats *cs = new ConnectionStats(); do_mutilate(*td->servers, *td->options, *cs, td->trace_queue, td->g_lock, td->g_wb_keys, td->master @@ -1258,7 +1322,7 @@ void* thread_main(void *arg) { } void do_mutilate(const vector& servers, options_t& options, - ConnectionStats& stats, vector*> *trace_queue, pthread_mutex_t* g_lock, unordered_map *g_wb_keys, bool master + ConnectionStats& stats, vector*> *trace_queue, pthread_mutex_t* g_lock, unordered_map *g_wb_keys, bool master #ifdef HAVE_LIBZMQ , zmq::socket_t* socketz #endif @@ -1627,7 +1691,7 @@ void do_mutilate(const vector& servers, options_t& options, if (connected) { fprintf(stderr,"cid %d gets l1 fd %d l2 fd %d\n",cid,fd1,fd2); - fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front().key.c_str()); + fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front()->key); if (g_lock != NULL) { conn->set_g_wbkeys(g_wb_keys); conn->set_lock(g_lock); @@ -1784,7 +1848,7 @@ void do_mutilate(const vector& servers, options_t& options, if (connected) { fprintf(stderr,"cid %d gets l1 fd %d l2 fd %d\n",cid,fd1,fd2); - fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front().key.c_str()); + fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front()->key); if (g_lock != NULL) { conn->set_g_wbkeys(g_wb_keys); conn->set_lock(g_lock); @@ -1876,6 +1940,7 @@ void args_to_options(options_t* options) { options->rand_admit = args.rand_admit_arg; options->threshold = args.threshold_arg; options->wb_all = args.wb_all_arg; + options->ratelimit = args.ratelimit_given; if (args.inclusives_given) { memset(options->inclusives,0,256); strncpy(options->inclusives,args.inclusives_arg,256); From aab49aa0f80af5a5dc19e5a097c6ffb4afae132c Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Fri, 11 Mar 2022 19:58:25 -0500 Subject: [PATCH 47/57] updated and fixed --- Connection.h | 2 + ConnectionMulti.backup | 1723 ++++++++++++++++++++++++++++++++++++++ ConnectionMulti.cc | 2 - ConnectionMultiApprox.cc | 178 +++- 4 files changed, 1885 insertions(+), 20 deletions(-) create mode 100644 ConnectionMulti.backup diff --git a/Connection.h b/Connection.h index 640ab65..f492b3d 100644 --- a/Connection.h +++ b/Connection.h @@ -449,7 +449,9 @@ class ConnectionMultiApprox { int issue_touch(const char* key, int valuelen, double now, int level); int issue_delete(const char* key, double now, uint32_t flags); int issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); + int issue_get_with_len(Operation *pop, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); int issue_set(const char* key, const char* value, int length, double now, uint32_t flags); + int issue_set(Operation *pop, const char* value, double now, uint32_t flags); // protocol fucntions int set_request_ascii(const char* key, const char* value, int length); diff --git a/ConnectionMulti.backup b/ConnectionMulti.backup new file mode 100644 index 0000000..688ad3c --- /dev/null +++ b/ConnectionMulti.backup @@ -0,0 +1,1723 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "Connection.h" +#include "distributions.h" +#include "Generator.h" +#include "mutilate.h" +#include "binary_protocol.h" +#include "util.h" +#include +#include +#include +#include +#include +#include "blockingconcurrentqueue.h" + +#define ITEM_L1 1 +#define ITEM_L2 2 +#define LOG_OP 4 +#define SRC_L1_M 8 +#define SRC_L1_H 16 +#define SRC_L2_M 32 +#define SRC_L2_H 64 +#define SRC_DIRECT_SET 128 +#define SRC_L1_COPY 256 +#define SRC_WB 512 + +#define ITEM_INCL 4096 +#define ITEM_EXCL 8192 +#define ITEM_DIRTY 16384 +#define ITEM_SIZE_CHANGE 131072 +#define ITEM_WAS_HIT 262144 + +#define LEVELS 2 +#define SET_INCL(incl,flags) \ + switch (incl) { \ + case 1: \ + flags |= ITEM_INCL; \ + break; \ + case 2: \ + flags |= ITEM_EXCL; \ + break; \ + \ + } \ + +#define GET_INCL(incl,flags) \ + if (flags & ITEM_INCL) incl = 1; \ + else if (flags & ITEM_EXCL) incl = 2; \ + +//#define OP_level(op) ( ((op)->flags & ITEM_L1) ? ITEM_L1 : ITEM_L2 ) +#define OP_level(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define FLAGS_level(flags) ( flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_clu(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_L1 | ITEM_L2 | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_src(op) ( (op)->flags & ~(ITEM_L1 | ITEM_L2 | LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY ) ) + +#define OP_log(op) ((op)->flags & LOG_OP) +#define OP_incl(op) ((op)->flags & ITEM_INCL) +#define OP_excl(op) ((op)->flags & ITEM_EXCL) +#define OP_set_flag(op,flag) ((op))->flags |= flag; + +//#define DEBUGMC +//#define DEBUGS + +using namespace moodycamel; + +pthread_mutex_t cid_lock_m = PTHREAD_MUTEX_INITIALIZER; +static uint32_t connids_m = 1; + +#define NCLASSES 40 +#define CHUNK_ALIGN_BYTES 8 +static int classes = 0; +static int sizes[NCLASSES+1]; +static int inclusives[NCLASSES+1]; + +typedef struct _evicted_type { + bool evicted; + uint32_t evictedFlags; + uint32_t serverFlags; + uint32_t clsid; + uint32_t evictedKeyLen; + uint32_t evictedLen; + char *evictedKey; + char *evictedData; +} evicted_t; + +static vector cid_rate; + +extern int max_n[3]; + +static void init_inclusives(char *inclusive_str) { + int j = 1; + for (int i = 0; i < (int)strlen(inclusive_str); i++) { + if (inclusive_str[i] == '-') { + continue; + } else { + inclusives[j] = inclusive_str[i] - '0'; + j++; + } + } +} + +static void init_classes() { + + double factor = 1.25; + unsigned int chunk_size = 48; + unsigned int item_size = 24; + unsigned int size = 96; //warning if you change this you die + unsigned int i = 0; + unsigned int chunk_size_max = 1048576/2; + while (++i < NCLASSES-1) { + if (size >= chunk_size_max / factor) { + break; + } + if (size % CHUNK_ALIGN_BYTES) + size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); + sizes[i] = size; + size *= factor; + } + sizes[i] = chunk_size_max; + classes = i; + +} + +static int get_class(int vl, uint32_t kl) { + //warning if you change this you die + int vsize = vl+kl+48+1+2; + int res = 1; + while (vsize > sizes[res]) + if (res++ == classes) { + //fprintf(stderr,"item larger than max class size. vsize: %d, class size: %d\n",vsize,sizes[res]); + return -1; + } + return res; +} + +static int get_incl(int vl, int kl) { + int clsid = get_class(vl,kl); + if (clsid) { + return inclusives[clsid]; + } else { + return -1; + } +} + +void ConnectionMulti::output_op(Operation *op, int type, bool found) { + char output[1024]; + char k[256]; + char a[256]; + char s[256]; + memset(k,0,256); + memset(a,0,256); + memset(s,0,256); + strcpy(k,op->key.c_str()); + switch (type) { + case 0: //get + sprintf(a,"issue_get"); + break; + case 1: //set + sprintf(a,"issue_set"); + break; + case 2: //resp + sprintf(a,"resp"); + break; + } + switch(read_state) { + case INIT_READ: + sprintf(s,"init"); + break; + case CONN_SETUP: + sprintf(s,"setup"); + break; + case LOADING: + sprintf(s,"load"); + break; + case IDLE: + sprintf(s,"idle"); + break; + case WAITING_FOR_GET: + sprintf(s,"waiting for get"); + break; + case WAITING_FOR_SET: + sprintf(s,"waiting for set"); + break; + case WAITING_FOR_DELETE: + sprintf(s,"waiting for del"); + break; + case MAX_READ_STATE: + sprintf(s,"max"); + break; + } + if (type == 2) { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, found: %d, type: %d\n",cid,a,k,op->opaque,found,op->type); + } else { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, type: %d\n",cid,a,k,op->opaque,op->type); + } + write(2,output,strlen(output)); +} + +/** + * Create a new connection to a server endpoint. + */ +ConnectionMulti::ConnectionMulti(struct event_base* _base, struct evdns_base* _evdns, + string _hostname1, string _hostname2, string _port, options_t _options, + bool sampling, int fd1, int fd2 ) : + start_time(0), stats(sampling), options(_options), + hostname1(_hostname1), hostname2(_hostname2), port(_port), base(_base), evdns(_evdns) +{ + pthread_mutex_lock(&cid_lock_m); + cid = connids_m++; + if (cid == 1) { + cid_rate.push_back(100); + cid_rate.push_back(0); + init_classes(); + init_inclusives(options.inclusives); + } else { + cid_rate.push_back(0); + } + + pthread_mutex_unlock(&cid_lock_m); + + valuesize = createGenerator(options.valuesize); + keysize = createGenerator(options.keysize); + srand(time(NULL)); + keygen = new KeyGenerator(keysize, options.records); + + total = 0; + eof = 0; + o_percent = 0; + + if (options.lambda <= 0) { + iagen = createGenerator("0"); + } else { + D("iagen = createGenerator(%s)", options.ia); + iagen = createGenerator(options.ia); + iagen->set_lambda(options.lambda); + } + + read_state = IDLE; + write_state = INIT_WRITE; + last_quiet1 = false; + last_quiet2 = false; + + last_tx = last_rx = 0.0; + + + op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + op_queue = (Operation***)malloc(sizeof(Operation**)*(LEVELS+1)); + + for (int i = 0; i <= LEVELS; i++) { + op_queue_size[i] = 0; + opaque[i] = 1; + //op_queue[i] = (Operation*)malloc(sizeof(int)*OPAQUE_MAX); + op_queue[i] = (Operation**)malloc(sizeof(Operation*)*(OPAQUE_MAX*2)); + + } + + bev1 = bufferevent_socket_new(base, fd1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev1, bev_read_cb1, bev_write_cb_m, bev_event_cb1, this); + bufferevent_enable(bev1, EV_READ | EV_WRITE); + + bev2 = bufferevent_socket_new(base, fd2, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev2, bev_read_cb2, bev_write_cb_m, bev_event_cb2, this); + bufferevent_enable(bev2, EV_READ | EV_WRITE); + + timer = evtimer_new(base, timer_cb_m, this); + + read_state = IDLE; +} + + +void ConnectionMulti::set_queue(queue* a_trace_queue) { + trace_queue = a_trace_queue; + trace_queue_n = a_trace_queue->size(); +} + +void ConnectionMulti::set_lock(pthread_mutex_t* a_lock) { + lock = a_lock; +} + +void ConnectionMulti::set_g_wbkeys(unordered_map *a_wb_keys) { + g_wb_keys = a_wb_keys; +} + +uint32_t ConnectionMulti::get_cid() { + return cid; +} + +int ConnectionMulti::add_to_wb_keys(string key) { + int ret = -1; + pthread_mutex_lock(lock); + auto pos = g_wb_keys->find(key); + if (pos == g_wb_keys->end()) { + g_wb_keys->insert( {key,cid }); + ret = 1; + //fprintf(stderr,"----set: %s----\n",Op.key.c_str()); + //for (auto iter = g_wb_keys->begin(); iter != g_wb_keys->end(); ++iter){ + // fprintf(stderr,"%s,%d\n",iter->first.c_str(),iter->second); + //} + //fprintf(stderr,"----%d----\n",cid); + } else { + ret = 2; + } + + pthread_mutex_unlock(lock); + return ret; +} + +void ConnectionMulti::del_wb_keys(string key) { + + pthread_mutex_lock(lock); + auto position = g_wb_keys->find(key); + if (position != g_wb_keys->end()) { + g_wb_keys->erase(position); + } else { + fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); + } + pthread_mutex_unlock(lock); +} + + +int ConnectionMulti::do_connect() { + + int connected = 0; + if (options.unix_socket) { + + + struct sockaddr_un sin1; + memset(&sin1, 0, sizeof(sin1)); + sin1.sun_family = AF_LOCAL; + strcpy(sin1.sun_path, hostname1.c_str()); + + int addrlen; + addrlen = sizeof(sin1); + + int err = bufferevent_socket_connect(bev1, (struct sockaddr*)&sin1, addrlen); + if (err == 0) { + connected = 1; + } else { + connected = 0; + err = errno; + fprintf(stderr,"l1 error %s\n",strerror(err)); + } + + struct sockaddr_un sin2; + memset(&sin2, 0, sizeof(sin2)); + sin2.sun_family = AF_LOCAL; + strcpy(sin2.sun_path, hostname2.c_str()); + + addrlen = sizeof(sin2); + err = bufferevent_socket_connect(bev2, (struct sockaddr*)&sin2, addrlen); + if (err == 0) { + connected = 1; + } else { + connected = 0; + err = errno; + fprintf(stderr,"l2 error %s\n",strerror(err)); + } + } + read_state = IDLE; + return connected; +} + +/** + * Destroy a connection, performing cleanup. + */ +ConnectionMulti::~ConnectionMulti() { + + + for (int i = 0; i <= LEVELS; i++) { + free(op_queue[i]); + + } + + free(op_queue_size); + free(opaque); + free(op_queue); + //event_free(timer); + //timer = NULL; + // FIXME: W("Drain op_q?"); + //bufferevent_free(bev1); + //bufferevent_free(bev2); + + delete iagen; + delete keygen; + delete keysize; + delete valuesize; +} + +/** + * Reset the connection back to an initial, fresh state. + */ +void ConnectionMulti::reset() { + // FIXME: Actually check the connection, drain all bufferevents, drain op_q. + //assert(op_queue.size() == 0); + //evtimer_del(timer); + read_state = IDLE; + write_state = INIT_WRITE; + stats = ConnectionStats(stats.sampling); +} + +/** + * Set our event processing priority. + */ +void ConnectionMulti::set_priority(int pri) { + if (bufferevent_priority_set(bev1, pri)) { + DIE("bufferevent_set_priority(bev, %d) failed", pri); + } +} + + + +/** + * Get/Set or Set Style + * If a GET command: Issue a get first, if not found then set + * If trace file (or prob. write) says to set, then set it + */ +int ConnectionMulti::issue_getsetorset(double now) { + + + + int ret = 0; + int nissued = 0; + //while (nissued < options.depth) { + + //pthread_mutex_lock(lock); + if (!trace_queue->empty()) { + Operation Op = trace_queue->front(); + if (Op.type == Operation::SASL) { + eof = 1; + cid_rate[cid] = 100; + fprintf(stderr,"cid %d done\n",cid); + string op_queue1; + string op_queue2; + for (int j = 0; j < 2; j++) { + for (int i = 0; i < OPAQUE_MAX; i++) { + if (op_queue[j+1][i] != NULL) { + if (j == 0) { + op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + } else { + op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + } + } + } + } + fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + return 1; + } + + + /* check if in global wb queue */ + pthread_mutex_lock(lock); + double percent = (double)total/((double)trace_queue_n) * 100; + if (percent > o_percent+1) { + //update the percentage table and see if we should execute + std::vector::iterator mp = std::min_element(cid_rate.begin(), cid_rate.end()); + double min_percent = *mp; + + if (percent > min_percent+2) { + pthread_mutex_unlock(lock); + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + int good = 0; + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + good = evtimer_add(timer, &tv); + } + if (good != 0) { + fprintf(stderr,"eventimer is messed up!\n"); + return 2; + } + return 1; + } + cid_rate[cid] = percent; + fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); + o_percent = percent; + } + auto check = g_wb_keys->find(Op.key); + if (check != g_wb_keys->end()) { + pthread_mutex_unlock(lock); + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + int good = 0; + if (!event_pending(timer, EV_TIMEOUT, NULL)) { + good = evtimer_add(timer, &tv); + } + if (good != 0) { + fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op.key.c_str()); + return 2; + } + return 1; + } else { + g_wb_keys->insert( {Op.key, cid} ); + //g_wb_keys->insert( {Op.key+"l2", cid} ); + } + pthread_mutex_unlock(lock); + + + + char key[256]; + memset(key,0,256); + strncpy(key, Op.key.c_str(),255); + int vl = Op.valuelen; + + trace_queue->pop(); + + int issued = 0; + int incl = get_incl(vl,strlen(key)); + int cid = get_class(vl,strlen(key)); + int flags = 0; + int touch = (rand() % 100); + int index = lrand48() % (1024 * 1024); + //int touch = 1; + SET_INCL(incl,flags); + + switch(Op.type) + { + case Operation::GET: + //if (nissued < options.depth-1) { + // issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); + // last_quiet1 = false; + //} else { + //} + if (options.threshold > 0) { + if (Op.future) { + key_hist[key] = 1; + } + } + issued = issue_get_with_len(key, vl, now, false, flags | LOG_OP | ITEM_L1); + if (touch == 1 && incl == 1) { + issue_touch(key,vl,now, ITEM_L2 | SRC_L1_H); + } + last_quiet1 = false; + this->stats.gets++; + this->stats.gets_cid[cid]++; + + break; + case Operation::SET: + if (last_quiet1) { + issue_noop(now,1); + } + if (incl == 1) { + issue_touch(key,vl,now, ITEM_L2 | SRC_DIRECT_SET); + } else if (incl == 2) { + issue_delete(key,now, ITEM_L2 | SRC_DIRECT_SET ); + } + issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + last_quiet1 = false; + this->stats.sets++; + this->stats.sets_cid[cid]++; + break; + case Operation::DELETE: + case Operation::TOUCH: + case Operation::NOOP: + case Operation::SASL: + fprintf(stderr,"invalid line: %s, vl: %d\n",key,vl); + break; + + } + if (issued) { + nissued++; + total++; + } else { + fprintf(stderr,"failed to issue line: %s, vl: %d @T: XX \n",key,vl); + } + } else { + return 1; + } + //} + if (last_quiet1) { + issue_noop(now,1); + last_quiet1 = false; + } + + return ret; + +} + +/** + * Issue a get request to the server. + */ +int ConnectionMulti::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1) { + + struct evbuffer *output = NULL; + int level = 0; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } + //Operation op; + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + pop->key = string(key); + pop->valuelen = valuelen; + pop->type = Operation::GET; + pop->opaque = opaque[level]++; + pop->flags = flags; + pop->clsid = get_class(valuelen,strlen(key)); + if (l1 != NULL) { + pop->l1 = l1; + } + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,valuelen,level,flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_GET, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + if (quiet) { + h.opcode = CMD_GETQ; + } + h.opaque = htonl(pop->opaque); + + evbuffer_add(output, &h, 24); + evbuffer_add(output, key, keylen); + + stats.tx_bytes += 24 + keylen; + return 1; +} + +/** + * Issue a get request to the server. + */ +int ConnectionMulti::issue_touch(const char* key, int valuelen, double now, int flags) { + struct evbuffer *output = NULL; + int level = 0; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + pop->key = string(key); + pop->valuelen = valuelen; + pop->type = Operation::TOUCH; + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + +#ifdef DEBUGS + fprintf(stderr,"issing touch: %s, size: %u, level %d, flags: %d, opaque: %d\n",key,valuelen,level,flags,pop->opaque); +#endif + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_TOUCH, htons(keylen), + 0x04, 0x00, htons(0), + htonl(keylen + 4) }; + h.opaque = htonl(pop->opaque); + + uint32_t exp = 0; + if (flags & ITEM_DIRTY) { + exp = htonl(flags); + } + evbuffer_add(output, &h, 24); + evbuffer_add(output, &exp, 4); + evbuffer_add(output, key, keylen); + + + stats.tx_bytes += 24 + keylen; + + //stats.log_access(op); + return 1; +} + +/** + * Issue a delete request to the server. + */ +int ConnectionMulti::issue_delete(const char* key, double now, uint32_t flags) { + struct evbuffer *output = NULL; + int level = 0; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } + //Operation op; + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + pop->key = string(key); + pop->type = Operation::DELETE; + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing delete: %s, level %d, flags: %d, opaque: %d\n",cid,key,level,flags,pop->opaque); +#endif + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_DELETE, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + h.opaque = htonl(pop->opaque); + + evbuffer_add(output, &h, 24); + evbuffer_add(output, key, keylen); + + stats.tx_bytes += 24 + keylen; + + //stats.log_access(op); + return 1; +} + +void ConnectionMulti::issue_noop(double now, int level) { + struct evbuffer *output = NULL; + switch (level) { + case 1: + output = bufferevent_get_output(bev1); + break; + case 2: + output = bufferevent_get_output(bev2); + break; + } + Operation op; + + if (now == 0.0) op.start_time = get_time(); + else op.start_time = now; + + binary_header_t h = { 0x80, CMD_NOOP, 0x0000, + 0x00, 0x00, htons(0), + 0x00 }; + + evbuffer_add(output, &h, 24); + +} + +/** + * Issue a set request to the server. + */ +int ConnectionMulti::issue_set(const char* key, const char* value, int length, double now, uint32_t flags) { + + struct evbuffer *output = NULL; + int level = 0; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } + //Operation op; + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) pop->start_time = get_time(); + else pop->start_time = now; +#endif + + + pop->key = string(key); + pop->valuelen = length; + pop->type = Operation::SET; + pop->opaque = opaque[level]++; + pop->flags = flags; + pop->clsid = get_class(length,strlen(key)); + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,length,level,flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_SET, htons(keylen), + 0x08, 0x00, htons(0), + htonl(keylen + 8 + length) }; + h.opaque = htonl(pop->opaque); + + uint32_t f = htonl(flags); + uint32_t exp = 0; + + evbuffer_add(output, &h, 24); + evbuffer_add(output, &f, 4); + evbuffer_add(output, &exp, 4); + evbuffer_add(output, key, keylen); + evbuffer_add(output, value, length); + + stats.tx_bytes += length + 32 + keylen; + return 1; +} + +/** + * Return the oldest live operation in progress. + */ +void ConnectionMulti::pop_op(Operation *op) { + + uint8_t level = OP_level(op); + //op_queue[level].erase(op); + op_queue_size[level]--; + + + if (read_state == LOADING) return; + read_state = IDLE; + + // Advance the read state machine. + //if (op_queue.size() > 0) { + // Operation& op = op_queue.front(); + // switch (op.type) { + // case Operation::GET: read_state = WAITING_FOR_GET; break; + // case Operation::SET: read_state = WAITING_FOR_SET; break; + // case Operation::DELETE: read_state = WAITING_FOR_DELETE; break; + // default: DIE("Not implemented."); + // } + //} +} + +/** + * Finish up (record stats) an operation that just returned from the + * server. + */ +void ConnectionMulti::finish_op(Operation *op, int was_hit) { + double now; +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); +#else + now = get_time(); +#endif +#if HAVE_CLOCK_GETTIME + op->end_time = get_time_accurate(); +#else + op->end_time = now; +#endif + + if (options.successful_queries && was_hit) { + switch (op->type) { + case Operation::GET: + switch (OP_level(op)) { + case 1: + stats.log_get_l1(*op); + break; + case 2: + stats.log_get_l2(*op); + break; + } + break; + case Operation::SET: + switch (OP_level(op)) { + case 1: + stats.log_set_l1(*op); + break; + case 2: + stats.log_set_l2(*op); + break; + } + break; + case Operation::DELETE: break; + case Operation::TOUCH: break; + default: DIE("Not implemented."); + } + } else { + switch (op->type) { + case Operation::GET: + if (OP_log(op)) { + switch (OP_level(op)) { + case 1: + stats.log_get_l1(*op); + break; + case 2: + stats.log_get_l2(*op); + if (op->l1 != NULL) { + op->l1->end_time = now; + stats.log_get(*(op->l1)); + } + break; + } + } + break; + case Operation::SET: + if (OP_log(op)) { + switch (OP_level(op)) { + case 1: + stats.log_set_l1(*op); + break; + case 2: + stats.log_set_l2(*op); + break; + } + } + break; + case Operation::DELETE: break; + case Operation::TOUCH: break; + default: DIE("Not implemented."); + } + } + + last_rx = now; + uint8_t level = OP_level(op); + if (op->l1 != NULL) { + delete op_queue[1][op->l1->opaque]; + op_queue[1][op->l1->opaque] = 0; + op_queue_size[1]--; + } + //op_queue[level].erase(op_queue[level].begin()+opopq); + if (op == op_queue[level][op->opaque] && + op->opaque == op_queue[level][op->opaque]->opaque) { + delete op_queue[level][op->opaque]; + op_queue[level][op->opaque] = 0; + } else { + fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", + op,op_queue[level][op->opaque],op->opaque,op_queue[level][op->opaque]->opaque); + } + op_queue_size[level]--; + read_state = IDLE; + + +} + + + +/** + * Check if our testing is done and we should exit. + */ +bool ConnectionMulti::check_exit_condition(double now) { + if (eof && op_queue_size[1] == 0 && op_queue_size[2] == 0) { + return true; + } + if (read_state == INIT_READ) return false; + + return false; +} + +/** + * Handle new connection and error events. + */ +void ConnectionMulti::event_callback1(short events) { + if (events & BEV_EVENT_CONNECTED) { + D("Connected to %s:%s.", hostname1.c_str(), port.c_str()); + int fd = bufferevent_getfd(bev1); + if (fd < 0) DIE("bufferevent_getfd"); + + if (!options.no_nodelay && !options.unix_socket) { + int one = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (void *) &one, sizeof(one)) < 0) + DIE("setsockopt()"); + } +#ifdef DEBUGMC + fprintf(stderr,"libevent connected %s, fd: %u\n",hostname1.c_str(),bufferevent_getfd(bev1)); +#endif + + + } else if (events & BEV_EVENT_ERROR) { + int err = bufferevent_socket_get_dns_error(bev1); + //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); + if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); + fprintf(stderr,"CID: %d - Got an error: %s\n",this->cid, + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); + + //DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + + } else if (events & BEV_EVENT_EOF) { + fprintf(stderr,"Unexpected EOF from server."); + return; + } +} + +/** + * Handle new connection and error events. + */ +void ConnectionMulti::event_callback2(short events) { + if (events & BEV_EVENT_CONNECTED) { + D("Connected to %s:%s.", hostname2.c_str(), port.c_str()); + int fd = bufferevent_getfd(bev2); + if (fd < 0) DIE("bufferevent_getfd"); + + if (!options.no_nodelay && !options.unix_socket) { + int one = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (void *) &one, sizeof(one)) < 0) + DIE("setsockopt()"); + } +#ifdef DEBUGMC + fprintf(stderr,"libevent connected %s, fd: %u\n",hostname2.c_str(),bufferevent_getfd(bev2)); +#endif + + + } else if (events & BEV_EVENT_ERROR) { + int err = bufferevent_socket_get_dns_error(bev2); + //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); + if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); + fprintf(stderr,"CID: %d - Got an error: %s\n",this->cid, + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); + + //DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + + + } else if (events & BEV_EVENT_EOF) { + fprintf(stderr,"Unexpected EOF from server."); + return; + } +} + +/** + * Request generation loop. Determines whether or not to issue a new command, + * based on timer events. + * + * Note that this function loops. Be wary of break vs. return. + */ +void ConnectionMulti::drive_write_machine(double now) { + if (now == 0.0) now = get_time(); + + double delay; + struct timeval tv; + + if (check_exit_condition(now)) { + return; + } + + while (1) { + switch (write_state) { + case INIT_WRITE: + delay = iagen->generate(); + next_time = now + delay; + double_to_tv(delay, &tv); + evtimer_add(timer, &tv); + write_state = ISSUING; + break; + + case ISSUING: + if ( (op_queue_size[1] >= (size_t) options.depth) || + (op_queue_size[2] >= (size_t) options.depth) ) { + write_state = WAITING_FOR_OPQ; + break; + } + + if (options.getsetorset) { + int ret = issue_getsetorset(now); + if (ret == 1) return; //if at EOF + } + + last_tx = now; + for (int i = 1; i <= 2; i++) { + stats.log_op(op_queue_size[i]); + } + break; + + case WAITING_FOR_TIME: + write_state = ISSUING; + break; + + case WAITING_FOR_OPQ: + if ( (op_queue_size[1] >= (size_t) options.depth) || + (op_queue_size[2] >= (size_t) options.depth) ) { + //double delay = 0.01; + //struct timeval tv; + //double_to_tv(delay, &tv); + //evtimer_add(timer, &tv); + return; + } else { + write_state = ISSUING; + break; + } + + default: DIE("Not implemented"); + } + } +} + + + +/** + * Tries to consume a binary response (in its entirety) from an evbuffer. + * + * @param input evBuffer to read response from + * @return true if consumed, false if not enough data in buffer. + */ +static bool handle_response(ConnectionMulti *conn, evbuffer *input, bool &done, bool &found, int &opcode, uint32_t &opaque, evicted_t *evict, int level) { + // Read the first 24 bytes as a header + int length = evbuffer_get_length(input); + if (length < 24) return false; + binary_header_t* h = + reinterpret_cast(evbuffer_pullup(input, 24)); + //assert(h); + + uint32_t bl = ntohl(h->body_len); + uint16_t kl = ntohs(h->key_len); + uint8_t el = h->extra_len; + // Not whole response + int targetLen = 24 + bl; + if (length < targetLen) { + return false; + } + + opcode = h->opcode; + opaque = ntohl(h->opaque); + uint16_t status = ntohs(h->status); +#ifdef DEBUGMC + fprintf(stderr,"cid: %d handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",conn->get_cid(),level, + h->opcode,ntohl(h->opaque),ntohs(h->key_len),h->extra_len, + ntohl(h->body_len),ntohs(h->status)); +#endif + + + // If something other than success, count it as a miss + if (opcode == CMD_GET && status == RESP_NOT_FOUND) { + switch(level) { + case 1: + conn->stats.get_misses_l1++; + break; + case 2: + conn->stats.get_misses_l2++; + conn->stats.get_misses++; + conn->stats.window_get_misses++; + break; + + } + found = false; + evbuffer_drain(input, targetLen); + + } else if (opcode == CMD_SET && kl > 0) { + //first data is extras: clsid, flags, eflags + if (evict) { + evbuffer_drain(input,24); + unsigned char *buf = evbuffer_pullup(input,bl); + + + evict->clsid = *((uint32_t*)buf); + evict->clsid = ntohl(evict->clsid); + buf += 4; + + evict->serverFlags = *((uint32_t*)buf); + evict->serverFlags = ntohl(evict->serverFlags); + buf += 4; + + evict->evictedFlags = *((uint32_t*)buf); + evict->evictedFlags = ntohl(evict->evictedFlags); + buf += 4; + + + evict->evictedKeyLen = kl; + evict->evictedKey = (char*)malloc(kl+1); + memset(evict->evictedKey,0,kl+1); + memcpy(evict->evictedKey,buf,kl); + buf += kl; + + + evict->evictedLen = bl - kl - el; + evict->evictedData = (char*)malloc(evict->evictedLen); + memcpy(evict->evictedData,buf,evict->evictedLen); + evict->evicted = true; + //fprintf(stderr,"class: %u, serverFlags: %u, evictedFlags: %u\n",evict->clsid,evict->serverFlags,evict->evictedFlags); + evbuffer_drain(input,bl); + } else { + evbuffer_drain(input, targetLen); + } + } else if (opcode == CMD_TOUCH && status == RESP_NOT_FOUND) { + found = false; + evbuffer_drain(input, targetLen); + } else if (opcode == CMD_DELETE && status == RESP_NOT_FOUND) { + found = false; + evbuffer_drain(input, targetLen); + } else { + evbuffer_drain(input, targetLen); + } + + conn->stats.rx_bytes += targetLen; + done = true; + return true; +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMulti::read_callback1() { + struct evbuffer *input = bufferevent_get_input(bev1); + + Operation *op = NULL; + bool done, found; + + //initially assume found (for sets that may come through here) + //is this correct? do we want to assume true in case that + //GET was found, but wrong value size (i.e. update value) + found = true; + + //if (op_queue.size() == 0) V("Spurious read callback."); + bool full_read = true; + while (full_read) { + + + int opcode; + uint32_t opaque; + evicted_t *evict = (evicted_t*)malloc(sizeof(evicted_t)); + memset(evict,0,sizeof(evicted_t)); + + full_read = handle_response(this,input, done, found, opcode, opaque, evict,1); + if (full_read) { + if (opcode == CMD_NOOP) { +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + continue; + } + op = op_queue[1][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + if (op->key.length() < 1) { +#ifdef DEBUGMC + char out2[128]; + sprintf(out2,"conn l1: %u, bad op: %s\n",cid,op->key.c_str()); + write(2,out2,strlen(out2)); +#endif + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + continue; + } + } else { + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + break; + } + + + double now = get_time(); + int wb = 0; + if (options.rand_admit) { + wb = (rand() % options.rand_admit); + } + switch (op->type) { + case Operation::GET: + if (done) { + if ( !found && (options.getset || options.getsetorset) ) { + /* issue a get a l2 */ + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + int vl = op->valuelen; + int flags = OP_clu(op); + issue_get_with_len(key,vl,now,false, flags | SRC_L1_M | ITEM_L2 | LOG_OP, op); + op->end_time = now; + this->stats.log_get_l1(*op); + //finish_op(op,0); + + } else { + del_wb_keys(op->key); + finish_op(op,found); + } + } else { + char out[128]; + sprintf(out,"conn l1: %u, not done reading, should do something",cid); + write(2,out,strlen(out)); + } + break; + case Operation::SET: + //if (OP_src(op) == SRC_L1_COPY || + // OP_src(op) == SRC_DIRECT_SET || + // OP_src(op) == SRC_L2_M ) { + //} + if (evict->evicted) { + string wb_key(evict->evictedKey); + if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { + //wb_keys.push_back(wb_key); + int ret = add_to_wb_keys(wb_key); + if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_WB); + } + //fprintf(stderr,"incl writeback %s\n",evict->evictedKey); + this->stats.incl_wbs++; + } else if (evict->evictedFlags & ITEM_EXCL) { + //fprintf(stderr,"excl writeback %s\n",evict->evictedKey); + //strncpy(wb_key,evict->evictedKey,255); + if ( (options.rand_admit && wb == 0) || + (options.threshold && (key_hist[wb_key] == 1)) || + (options.wb_all) ) { + int ret = add_to_wb_keys(wb_key); + if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + } + this->stats.excl_wbs++; + } + } + /* + if (evict->serverFlags & ITEM_SIZE_CHANGE && OP_src(op) == SRC_DIRECT_SET) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + if (evict->serverFlags & ITEM_INCL) { + int index = lrand48() % (1024 * 1024); + int valuelen = op->valuelen; + //the item's size was changed, issue a SET to L2 as a new command + issue_set(key, &random_char[index], valuelen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_L2_M); + } + } + */ + if (OP_src(op) == SRC_DIRECT_SET) { + if ( (evict->serverFlags & ITEM_SIZE_CHANGE) || ((evict->serverFlags & ITEM_WAS_HIT) == 0)) { + this->stats.set_misses_l1++; + } else if (OP_excl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_excl_hits_l1++; + } else if (OP_incl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_incl_hits_l1++; + } + } + } + del_wb_keys(op->key); + finish_op(op,1); + break; + case Operation::TOUCH: + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + DIE("not implemented"); + } + + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + + } + + + double now = get_time(); + if (check_exit_condition(now)) { + return; + } + + last_tx = now; + stats.log_op(op_queue_size[1]); + stats.log_op(op_queue_size[2]); + //for (int i = 1; i <= 2; i++) { + // fprintf(stderr,"max issue buf n[%d]: %u\n",i,max_n[i]); + //} + drive_write_machine(); + + // update events + //if (bev != NULL) { + // // no pending response (nothing to read) and output buffer empty (nothing to write) + // if ((op_queue.size() == 0) && (evbuffer_get_length(bufferevent_get_output(bev)) == 0)) { + // bufferevent_disable(bev, EV_WRITE|EV_READ); + // } + //} +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMulti::read_callback2() { + struct evbuffer *input = bufferevent_get_input(bev2); + + Operation *op = NULL; + bool done, found; + + //initially assume found (for sets that may come through here) + //is this correct? do we want to assume true in case that + //GET was found, but wrong value size (i.e. update value) + found = true; + + + //if (op_queue.size() == 0) V("Spurious read callback."); + bool full_read = true; + while (full_read) { + + + int opcode; + uint32_t opaque; + full_read = handle_response(this,input, done, found, opcode, opaque, NULL,2); + if (full_read) { + if (opcode == CMD_NOOP) { +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif + continue; + } + op = op_queue[2][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + if (op->key.length() < 1) { +#ifdef DEBUGMC + char out2[128]; + sprintf(out2,"conn l2: %u, bad op: %s\n",cid,op->key.c_str()); + write(2,out2,strlen(out2)); +#endif + continue; + } + } else { + break; + } + + + double now = get_time(); + switch (op->type) { + case Operation::GET: + if (done) { + if ( !found && (options.getset || options.getsetorset) ) {// && + //(options.twitter_trace != 1)) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | SRC_L2_M | LOG_OP; + issue_set(key, &random_char[index], valuelen, now, flags | ITEM_L1); + //wb_keys.push_back(op->key); + last_quiet1 = false; + if (OP_incl(op)) { + //wb_keys.push_back(op->key); + issue_set(key, &random_char[index], valuelen, now, flags | ITEM_L2); + last_quiet2 = false; + } + //pthread_mutex_lock(lock); + //fprintf(stderr,"----miss: %s----\n",key); + //for (auto iter = g_wb_keys->begin(); iter != g_wb_keys->end(); ++iter){ + // fprintf(stderr,"%s,%d\n",iter->first.c_str(),iter->second); + //} + //fprintf(stderr,"----%d----\n",cid); + //pthread_mutex_unlock(lock); + finish_op(op,0); // sets read_state = IDLE + + } else { + if (found) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; + //found in l2, set in l1 + //wb_keys.push_back(op->key); + issue_set(key, &random_char[index],valuelen, now, flags); + this->stats.copies_to_l1++; + //if (OP_excl(op)) { + // issue_delete(key,now, ITEM_L2 | SRC_L1_COPY ); + //} + finish_op(op,1); + + } else { + finish_op(op,0); + } + } + } else { + char out[128]; + sprintf(out,"conn l2: %u, not done reading, should do something",cid); + write(2,out,strlen(out)); + } + break; + case Operation::SET: + if (OP_src(op) == SRC_WB) { + del_wb_keys(op->key); + } + finish_op(op,1); + break; + case Operation::TOUCH: + if (OP_src(op) == SRC_DIRECT_SET) { + char key[256]; + memset(key,0,256); + strncpy(key, op->key.c_str(),255); + int valuelen = op->valuelen; + if (!found) { + int index = lrand48() % (1024 * 1024); + //int ret = add_to_wb_keys(op->key+"l2"); + //if (ret == 1) { + issue_set(key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP | SRC_L2_M); + //} + this->stats.set_misses_l2++; + } else { + issue_touch(key,valuelen,now, ITEM_L1 | SRC_L2_H | ITEM_DIRTY); + } + } + //if (!found) { + // //int incl = op->incl; + // //int flags = 0; + // //SET_INCL(incl,flags); + // //// not found in l2, set in l2 + // char key[256]; + // memset(key,0,256); + // strncpy(key, op->key.c_str(),255); + // int valuelen = op->valuelen; + // int index = lrand48() % (1024 * 1024); + // if (OP_src(op) == SRC_DIRECT_SET) { + // issue_set(key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP); + // this->stats.set_misses_l2++; + // } + // //if (OP_src(op) == SRC_L1_H) { + // // fprintf(stderr,"expected op in l2: %s\n",key); + // //} + // finish_op(op,0); + //} else { + // finish_op(op,1); + //} + finish_op(op,0); + break; + case Operation::DELETE: + //check to see if it was a hit + //fprintf(stderr," del %s -- %d from %d\n",op->key.c_str(),found,OP_src(op)); + if (OP_src(op) == SRC_DIRECT_SET) { + if (found) { + this->stats.delete_hits_l2++; + } else { + this->stats.delete_misses_l2++; + } + } + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key.c_str(),op->opaque); + DIE("not implemented"); + } + + } + + double now = get_time(); + if (check_exit_condition(now)) { + return; + } + + last_tx = now; + stats.log_op(op_queue_size[2]); + stats.log_op(op_queue_size[1]); + drive_write_machine(); + + // update events + //if (bev != NULL) { + // // no pending response (nothing to read) and output buffer empty (nothing to write) + // if ((op_queue.size() == 0) && (evbuffer_get_length(bufferevent_get_output(bev)) == 0)) { + // bufferevent_disable(bev, EV_WRITE|EV_READ); + // } + //} +} + +/** + * Callback called when write requests finish. + */ +void ConnectionMulti::write_callback() { + + //fprintf(stderr,"loaded evbuffer with ops: %u\n",op_queue.size()); +} + +/** + * Callback for timer timeouts. + */ +void ConnectionMulti::timer_callback() { + //fprintf(stderr,"timer up: %d\n",cid); + drive_write_machine(); +} + + +/* The follow are C trampolines for libevent callbacks. */ +void bev_event_cb1(struct bufferevent *bev, short events, void *ptr) { + + ConnectionMulti* conn = (ConnectionMulti*) ptr; + conn->event_callback1(events); +} + +/* The follow are C trampolines for libevent callbacks. */ +void bev_event_cb2(struct bufferevent *bev, short events, void *ptr) { + + ConnectionMulti* conn = (ConnectionMulti*) ptr; + conn->event_callback2(events); +} + +void bev_read_cb1(struct bufferevent *bev, void *ptr) { + ConnectionMulti* conn = (ConnectionMulti*) ptr; + conn->read_callback1(); +} + + +void bev_read_cb2(struct bufferevent *bev, void *ptr) { + ConnectionMulti* conn = (ConnectionMulti*) ptr; + conn->read_callback2(); +} + +void bev_write_cb_m(struct bufferevent *bev, void *ptr) { +} + +void timer_cb_m(evutil_socket_t fd, short what, void *ptr) { + ConnectionMulti* conn = (ConnectionMulti*) ptr; + conn->timer_callback(); +} + diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 6aa6d83..b306cb9 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -128,8 +128,6 @@ static void init_inclusives(char *inclusive_str) { static void init_classes() { double factor = 1.25; - unsigned int chunk_size = 48; - unsigned int item_size = 24; unsigned int size = 96; //warning if you change this you die unsigned int i = 0; unsigned int chunk_size_max = 1048576/2; diff --git a/ConnectionMultiApprox.cc b/ConnectionMultiApprox.cc index 0eef921..5cd56bd 100644 --- a/ConnectionMultiApprox.cc +++ b/ConnectionMultiApprox.cc @@ -128,8 +128,8 @@ static void init_inclusives(char *inclusive_str) { static void init_classes() { double factor = 1.25; - unsigned int chunk_size = 48; - unsigned int item_size = 24; + //unsigned int chunk_size = 48; + //unsigned int item_size = 24; unsigned int size = 96; //warning if you change this you die unsigned int i = 0; unsigned int chunk_size_max = 1048576/2; @@ -279,9 +279,13 @@ ConnectionMultiApprox::ConnectionMultiApprox(struct event_base* _base, struct ev op_queue_size[i] = 0; opaque[i] = 1; //op_queue[i] = (Operation*)malloc(sizeof(int)*OPAQUE_MAX); - op_queue[i] = (Operation**)malloc(sizeof(Operation*)*(OPAQUE_MAX*2)); + op_queue[i] = (Operation**)malloc(sizeof(Operation*)*(OPAQUE_MAX+1)); + for (int j = 0; j <= OPAQUE_MAX; j++) { + op_queue[i][j] = NULL; + } } + bev1 = bufferevent_socket_new(base, fd1, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev1, bev_read_cb1_approx, bev_write_cb_m_approx, bev_event_cb1_approx, this); @@ -453,8 +457,8 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { //pthread_mutex_lock(lock); if (!trace_queue->empty()) { - Operation Op = *(trace_queue->front()); - if (Op.type == Operation::SASL) { + Operation *Op = (trace_queue->front()); + if (Op->type == Operation::SASL) { eof = 1; cid_rate[cid] = 100; fprintf(stderr,"cid %d done\n",cid); @@ -478,8 +482,9 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { /* check if in global wb queue */ double percent = (double)total/((double)trace_queue_n) * 100; - if (percent > o_percent+1) { + if (percent > o_percent+2) { //update the percentage table and see if we should execute + pthread_mutex_lock(lock); std::vector::iterator mp = std::min_element(cid_rate.begin(), cid_rate.end()); double min_percent = *mp; @@ -492,17 +497,23 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { good = evtimer_add(timer, &tv); } if (good != 0) { + pthread_mutex_unlock(lock); fprintf(stderr,"eventimer is messed up!\n"); return 2; } + pthread_mutex_unlock(lock); return 1; } cid_rate[cid] = percent; + pthread_mutex_unlock(lock); //fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); o_percent = percent; } - auto check = g_wb_keys->find(string(Op.key)); + + pthread_mutex_lock(lock); + auto check = g_wb_keys->find(string(Op->key)); if (check != g_wb_keys->end()) { + pthread_mutex_unlock(lock); struct timeval tv; double delay; delay = last_rx + 0.00025 - now; @@ -512,27 +523,28 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { good = evtimer_add(timer, &tv); //} if (good != 0) { - fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op.key); + fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op->key); return 2; } return 1; + } else { + pthread_mutex_unlock(lock); } - char *key = Op.key; - int vl = Op.valuelen; trace_queue->pop(); int issued = 0; - int incl = get_incl(vl,strlen(key)); - int cid = get_class(vl,strlen(key)); + int incl = get_incl(Op->valuelen,strlen(Op->key)); + int cid = get_class(Op->valuelen,strlen(Op->key)); + Op->clsid = cid; int flags = 0; int index = lrand48() % (1024 * 1024); //int touch = 1; SET_INCL(incl,flags); - switch(Op.type) + switch(Op->type) { case Operation::GET: //if (nissued < options.depth-1) { @@ -540,7 +552,7 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { // last_quiet1 = false; //} else { //} - issued = issue_get_with_len(key, vl, now, false, flags | LOG_OP | ITEM_L1); + issued = issue_get_with_len(Op, now, false, flags | LOG_OP | ITEM_L1); last_quiet1 = false; this->stats.gets++; gets++; @@ -552,9 +564,9 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { issue_noop(now,1); } if (incl == 1) { - issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); } else if (incl == 2) { - issued = issue_set(key, &random_char[index], vl, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); } last_quiet1 = false; this->stats.sets++; @@ -564,7 +576,7 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { case Operation::TOUCH: case Operation::NOOP: case Operation::SASL: - fprintf(stderr,"invalid line: %s, vl: %d\n",key,vl); + fprintf(stderr,"invalid line: %s, vl: %d\n",Op->key,Op->valuelen); break; } @@ -572,7 +584,7 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { nissued++; total++; } else { - fprintf(stderr,"failed to issue line: %s, vl: %d @T: XX \n",key,vl); + fprintf(stderr,"failed to issue line: %s, vl: %d\n",Op->key,Op->valuelen); } } else { return 1; @@ -587,6 +599,76 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { } +/** + * Issue a get request to the server. + */ +int ConnectionMultiApprox::issue_get_with_len(Operation *pop, double now, bool quiet, uint32_t flags, Operation *l1) { + + struct evbuffer *output = NULL; + int level = 0; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + pop->opaque = opaque[level]++; + pop->flags = flags; + if (l1 != NULL) { + pop->l1 = l1; + } + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,valuelen,level,flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(pop->key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_GET, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + if (quiet) { + h.opcode = CMD_GETQ; + } + h.opaque = htonl(pop->opaque); + + evbuffer_add(output, &h, 24); + evbuffer_add(output, pop->key, keylen); + + stats.tx_bytes += 24 + keylen; + return 1; +} + /** * Issue a get request to the server. */ @@ -828,6 +910,66 @@ void ConnectionMultiApprox::issue_noop(double now, int level) { } +/** + * Issue a set request to the server. + */ +int ConnectionMultiApprox::issue_set(Operation *pop, const char* value, double now, uint32_t flags) { + + struct evbuffer *output = NULL; + int level = 0; + int length = pop->valuelen; + switch (FLAGS_level(flags)) { + case 1: + level = 1; + output = bufferevent_get_output(bev1); + break; + case 2: + level = 2; + output = bufferevent_get_output(bev2); + break; + } + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) pop->start_time = get_time(); + else pop->start_time = now; +#endif + + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,length,level,flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + uint16_t keylen = strlen(pop->key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_SET, htons(keylen), + 0x08, 0x00, htons(0), + htonl(keylen + 8 + length) }; + h.opaque = htonl(pop->opaque); + + uint32_t f = htonl(flags); + uint32_t exp = 0; + + evbuffer_add(output, &h, 24); + evbuffer_add(output, &f, 4); + evbuffer_add(output, &exp, 4); + evbuffer_add(output, pop->key, keylen); + evbuffer_add(output, value, length); + + stats.tx_bytes += length + 32 + keylen; + return 1; +} + /** * Issue a set request to the server. */ From c9b4a2d6c7daef3b1fe5917c3fb3ce95f7ea4a14 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 16 Mar 2022 08:02:20 -0400 Subject: [PATCH 48/57] updates --- Connection.h | 11 ++- ConnectionMulti.cc | 6 +- ConnectionMultiApprox.cc | 179 +++++++++++++++++++++++---------------- mutilate.cc | 8 +- 4 files changed, 120 insertions(+), 84 deletions(-) diff --git a/Connection.h b/Connection.h index f492b3d..4f2c437 100644 --- a/Connection.h +++ b/Connection.h @@ -222,7 +222,7 @@ class ConnectionMulti { //void set_queue(ConcurrentQueue *a_trace_queue); int add_to_wb_keys(string wb_key); void del_wb_keys(string wb_key); - void set_g_wbkeys(unordered_map *a_wb_keys); + void set_g_wbkeys(unordered_map> *a_wb_keys); void set_queue(queue *a_trace_queue); void set_lock(pthread_mutex_t* a_lock); @@ -290,7 +290,7 @@ class ConnectionMulti { KeyGenerator *keygen; Generator *iagen; pthread_mutex_t* lock; - unordered_map *g_wb_keys; + unordered_map> *g_wb_keys; queue *trace_queue; // state machine functions / event processing @@ -362,7 +362,7 @@ class ConnectionMultiApprox { //void set_queue(ConcurrentQueue *a_trace_queue); int add_to_wb_keys(string wb_key); void del_wb_keys(string wb_key); - void set_g_wbkeys(unordered_map *a_wb_keys); + void set_g_wbkeys(unordered_map> *a_wb_keys); void set_queue(queue *a_trace_queue); void set_lock(pthread_mutex_t* a_lock); @@ -421,6 +421,8 @@ class ConnectionMultiApprox { uint32_t gets; uint32_t gloc; uint32_t ghits; + uint32_t sloc; + uint32_t esets; //std::vector> op_queue; Operation ***op_queue; @@ -432,7 +434,7 @@ class ConnectionMultiApprox { KeyGenerator *keygen; Generator *iagen; pthread_mutex_t* lock; - unordered_map *g_wb_keys; + unordered_map> *g_wb_keys; queue *trace_queue; // state machine functions / event processing @@ -445,6 +447,7 @@ class ConnectionMultiApprox { // request functions void issue_sasl(); + int issue_op(Operation* op); void issue_noop(double now = 0.0, int level = 1); int issue_touch(const char* key, int valuelen, double now, int level); int issue_delete(const char* key, double now, uint32_t flags); diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index b306cb9..4c36fac 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -302,7 +302,7 @@ void ConnectionMulti::set_lock(pthread_mutex_t* a_lock) { lock = a_lock; } -void ConnectionMulti::set_g_wbkeys(unordered_map *a_wb_keys) { +void ConnectionMulti::set_g_wbkeys(unordered_map> *a_wb_keys) { g_wb_keys = a_wb_keys; } @@ -315,7 +315,7 @@ int ConnectionMulti::add_to_wb_keys(string key) { pthread_mutex_lock(lock); auto pos = g_wb_keys->find(key); if (pos == g_wb_keys->end()) { - g_wb_keys->insert( {key,cid }); + g_wb_keys->insert( {key, vector() }); ret = 1; //fprintf(stderr,"----set: %s----\n",Op.key.c_str()); //for (auto iter = g_wb_keys->begin(); iter != g_wb_keys->end(); ++iter){ @@ -515,7 +515,7 @@ int ConnectionMulti::issue_getsetorset(double now) { } return 1; } else { - g_wb_keys->insert( {Op.key, cid} ); + //g_wb_keys->insert( {Op.key, cid} ); //g_wb_keys->insert( {Op.key+"l2", cid} ); } pthread_mutex_unlock(lock); diff --git a/ConnectionMultiApprox.cc b/ConnectionMultiApprox.cc index 5cd56bd..bc45326 100644 --- a/ConnectionMultiApprox.cc +++ b/ConnectionMultiApprox.cc @@ -269,7 +269,9 @@ ConnectionMultiApprox::ConnectionMultiApprox(struct event_base* _base, struct ev last_tx = last_rx = 0.0; gets = 0; ghits = 0; + esets = 0; gloc = rand() % (100*2-1)+1; + sloc = rand() % (100*2-1)+1; op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); @@ -310,7 +312,7 @@ void ConnectionMultiApprox::set_lock(pthread_mutex_t* a_lock) { lock = a_lock; } -void ConnectionMultiApprox::set_g_wbkeys(unordered_map *a_wb_keys) { +void ConnectionMultiApprox::set_g_wbkeys(unordered_map> *a_wb_keys) { g_wb_keys = a_wb_keys; } @@ -323,7 +325,7 @@ int ConnectionMultiApprox::add_to_wb_keys(string key) { pthread_mutex_lock(lock); auto pos = g_wb_keys->find(key); if (pos == g_wb_keys->end()) { - g_wb_keys->insert( {key,cid }); + g_wb_keys->insert( {key, vector() }); ret = 1; //fprintf(stderr,"----set: %s----\n",Op.key.c_str()); //for (auto iter = g_wb_keys->begin(); iter != g_wb_keys->end(); ++iter){ @@ -338,11 +340,80 @@ int ConnectionMultiApprox::add_to_wb_keys(string key) { return ret; } +int ConnectionMultiApprox::add_to_copy_keys(string key, const char *data) { + int ret = -1; + auto pos = copy_kes->find(key); + if (pos == copy_keys->end()) { + copy_keys->insert( {key, vector() }); + return 1; + } + return 2; +} + +int ConnectionMultiApprox::issue_op(Operation *Op) { + double now = get_time(); + int issued = 0; + int incl = get_incl(Op->valuelen,strlen(Op->key)); + int cid = get_class(Op->valuelen,strlen(Op->key)); + Op->clsid = cid; + int flags = 0; + int index = lrand48() % (1024 * 1024); + //int touch = 1; + SET_INCL(incl,flags); + + switch(Op->type) + { + case Operation::GET: + //if (nissued < options.depth-1) { + // issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); + // last_quiet1 = false; + //} else { + //} + issued = issue_get_with_len(Op, now, false, flags | LOG_OP | ITEM_L1); + last_quiet1 = false; + this->stats.gets++; + gets++; + this->stats.gets_cid[cid]++; + + break; + case Operation::SET: + if (last_quiet1) { + issue_noop(now,1); + } + if (incl == 1) { + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); + } else if (incl == 2) { + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + if (esets >= sloc) { + issue_delete(Op->key,now,ITEM_L2 | SRC_DIRECT_SET); + sloc += rand()%(100*2-1)+1; + } + esets++; + } + last_quiet1 = false; + this->stats.sets++; + this->stats.sets_cid[cid]++; + break; + case Operation::DELETE: + case Operation::TOUCH: + case Operation::NOOP: + case Operation::SASL: + fprintf(stderr,"invalid line: %s, vl: %d\n",Op->key,Op->valuelen); + break; + + } + return issued; +} + void ConnectionMultiApprox::del_wb_keys(string key) { pthread_mutex_lock(lock); auto position = g_wb_keys->find(key); if (position != g_wb_keys->end()) { + vector op_list = position->second; + for (auto it = op_list.begin(); it != op_list.end(); ++it) { + issue_op(*it); + } g_wb_keys->erase(position); } else { fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); @@ -490,8 +561,8 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { if (options.ratelimit && percent > min_percent+2) { struct timeval tv; - tv.tv_sec = 1; - tv.tv_usec = 0; + tv.tv_sec = 0; + tv.tv_usec = 100; int good = 0; if (!event_pending(timer, EV_TIMEOUT, NULL)) { good = evtimer_add(timer, &tv); @@ -509,83 +580,39 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { //fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); o_percent = percent; } + + trace_queue->pop(); pthread_mutex_lock(lock); auto check = g_wb_keys->find(string(Op->key)); if (check != g_wb_keys->end()) { + check->second.push_back(Op); pthread_mutex_unlock(lock); - struct timeval tv; - double delay; - delay = last_rx + 0.00025 - now; - double_to_tv(delay,&tv); - int good = 0; - //if (!event_pending(timer, EV_TIMEOUT, NULL)) { - good = evtimer_add(timer, &tv); + //pthread_mutex_unlock(lock); + //struct timeval tv; + //double delay; + //delay = last_rx + 0.00025 - now; + //double_to_tv(delay,&tv); + //int good = 0; + ////if (!event_pending(timer, EV_TIMEOUT, NULL)) { + //good = evtimer_add(timer, &tv); + ////} + //if (good != 0) { + // fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op->key); + // return 2; //} - if (good != 0) { - fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op->key); - return 2; - } - return 1; + //return 1; } else { pthread_mutex_unlock(lock); + int issued = issue_op(Op); + if (issued) { + nissued++; + total++; + } else { + fprintf(stderr,"failed to issue line: %s, vl: %d\n",Op->key,Op->valuelen); + } } - - - trace_queue->pop(); - - int issued = 0; - int incl = get_incl(Op->valuelen,strlen(Op->key)); - int cid = get_class(Op->valuelen,strlen(Op->key)); - Op->clsid = cid; - int flags = 0; - int index = lrand48() % (1024 * 1024); - //int touch = 1; - SET_INCL(incl,flags); - - switch(Op->type) - { - case Operation::GET: - //if (nissued < options.depth-1) { - // issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); - // last_quiet1 = false; - //} else { - //} - issued = issue_get_with_len(Op, now, false, flags | LOG_OP | ITEM_L1); - last_quiet1 = false; - this->stats.gets++; - gets++; - this->stats.gets_cid[cid]++; - - break; - case Operation::SET: - if (last_quiet1) { - issue_noop(now,1); - } - if (incl == 1) { - issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); - } else if (incl == 2) { - issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); - } - last_quiet1 = false; - this->stats.sets++; - this->stats.sets_cid[cid]++; - break; - case Operation::DELETE: - case Operation::TOUCH: - case Operation::NOOP: - case Operation::SASL: - fprintf(stderr,"invalid line: %s, vl: %d\n",Op->key,Op->valuelen); - break; - - } - if (issued) { - nissued++; - total++; - } else { - fprintf(stderr,"failed to issue line: %s, vl: %d\n",Op->key,Op->valuelen); - } } else { return 1; } @@ -1262,8 +1289,8 @@ void ConnectionMultiApprox::event_callback2(short events) { * Note that this function loops. Be wary of break vs. return. */ void ConnectionMultiApprox::drive_write_machine(double now) { - if (now == 0.0) now = get_time(); + if (now == 0.0) now = get_time(); double delay; struct timeval tv; @@ -1697,7 +1724,13 @@ void ConnectionMultiApprox::read_callback2() { int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; //found in l2, set in l1 //wb_keys.push_back(op->key); - issue_set(op->key, &random_char[index],valuelen, now, flags); + //add this key to the list of keys currently being copied + string key = string(op->key); + const char *data = &random_char[index]; + int ret = add_to_copy_keys(string(op->key)); + if (ret == 1) { + issue_set(op->key,data,valuelen, now, flags); + } this->stats.copies_to_l1++; //djb: this is automatically done in the L2 server //if (OP_excl(op)) { //djb: todo should we delete here for approx or just let it die a slow death? diff --git a/mutilate.cc b/mutilate.cc index a0b4c1e..296cc19 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -89,7 +89,7 @@ struct thread_data { std::vector*> *trace_queue; //std::vector *mutexes; pthread_mutex_t* g_lock; - std::unordered_map *g_wb_keys; + std::unordered_map> *g_wb_keys; }; struct reader_data { @@ -120,7 +120,7 @@ void go(const vector &servers, options_t &options, //void do_mutilate(const vector &servers, options_t &options, // ConnectionStats &stats,std::vector*> trace_queue, bool master = true void do_mutilate(const vector &servers, options_t &options, - ConnectionStats &stats,std::vector*> *trace_queue, pthread_mutex_t *g_lock, unordered_map *g_wb_keys, bool master = true + ConnectionStats &stats,std::vector*> *trace_queue, pthread_mutex_t *g_lock, unordered_map> *g_wb_keys, bool master = true #ifdef HAVE_LIBZMQ , zmq::socket_t* socketz = NULL #endif @@ -753,7 +753,7 @@ void go(const vector& servers, options_t& options, pthread_mutex_t *g_lock = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); *g_lock = PTHREAD_MUTEX_INITIALIZER; - unordered_map *g_wb_keys = new unordered_map(); + unordered_map> *g_wb_keys = new unordered_map>(); for (int i = 0; i <= options.apps; i++) { // //trace_queue.push_back(new ConcurrentQueue(2000000)); @@ -1322,7 +1322,7 @@ void* thread_main(void *arg) { } void do_mutilate(const vector& servers, options_t& options, - ConnectionStats& stats, vector*> *trace_queue, pthread_mutex_t* g_lock, unordered_map *g_wb_keys, bool master + ConnectionStats& stats, vector*> *trace_queue, pthread_mutex_t* g_lock, unordered_map> *g_wb_keys, bool master #ifdef HAVE_LIBZMQ , zmq::socket_t* socketz #endif From b1091dfc7e2ed4add40a2e6eeb0057a8a97fc2ee Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 16 Mar 2022 13:29:20 -0400 Subject: [PATCH 49/57] updated --- Connection.h | 5 ++ ConnectionMultiApprox.cc | 119 ++++++++++++++++++++------------------- 2 files changed, 65 insertions(+), 59 deletions(-) diff --git a/Connection.h b/Connection.h index 4f2c437..2f6b200 100644 --- a/Connection.h +++ b/Connection.h @@ -221,7 +221,9 @@ class ConnectionMulti { uint32_t get_cid(); //void set_queue(ConcurrentQueue *a_trace_queue); int add_to_wb_keys(string wb_key); + int add_to_copy_keys(string key); void del_wb_keys(string wb_key); + void del_copy_keys(string key); void set_g_wbkeys(unordered_map> *a_wb_keys); void set_queue(queue *a_trace_queue); void set_lock(pthread_mutex_t* a_lock); @@ -361,7 +363,9 @@ class ConnectionMultiApprox { uint32_t get_cid(); //void set_queue(ConcurrentQueue *a_trace_queue); int add_to_wb_keys(string wb_key); + int add_to_copy_keys(string key); void del_wb_keys(string wb_key); + void del_copy_keys(string key); void set_g_wbkeys(unordered_map> *a_wb_keys); void set_queue(queue *a_trace_queue); void set_lock(pthread_mutex_t* a_lock); @@ -435,6 +439,7 @@ class ConnectionMultiApprox { Generator *iagen; pthread_mutex_t* lock; unordered_map> *g_wb_keys; + unordered_map> copy_keys; queue *trace_queue; // state machine functions / event processing diff --git a/ConnectionMultiApprox.cc b/ConnectionMultiApprox.cc index bc45326..8308013 100644 --- a/ConnectionMultiApprox.cc +++ b/ConnectionMultiApprox.cc @@ -340,16 +340,30 @@ int ConnectionMultiApprox::add_to_wb_keys(string key) { return ret; } -int ConnectionMultiApprox::add_to_copy_keys(string key, const char *data) { - int ret = -1; - auto pos = copy_kes->find(key); - if (pos == copy_keys->end()) { - copy_keys->insert( {key, vector() }); +int ConnectionMultiApprox::add_to_copy_keys(string key) { + auto pos = copy_keys.find(key); + if (pos == copy_keys.end()) { + copy_keys.insert( {key, vector() }); return 1; } return 2; } + +void ConnectionMultiApprox::del_copy_keys(string key) { + + auto position = copy_keys.find(key); + if (position != copy_keys.end()) { + vector op_list = position->second; + for (auto it = op_list.begin(); it != op_list.end(); ++it) { + issue_op(*it); + } + copy_keys.erase(position); + } else { + fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); + } +} + int ConnectionMultiApprox::issue_op(Operation *Op) { double now = get_time(); int issued = 0; @@ -583,11 +597,11 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { trace_queue->pop(); - pthread_mutex_lock(lock); - auto check = g_wb_keys->find(string(Op->key)); - if (check != g_wb_keys->end()) { - check->second.push_back(Op); - pthread_mutex_unlock(lock); + //pthread_mutex_lock(lock); + //auto check = g_wb_keys->find(string(Op->key)); + //if (check != g_wb_keys->end()) { + // check->second.push_back(Op); + // pthread_mutex_unlock(lock); //pthread_mutex_unlock(lock); //struct timeval tv; //double delay; @@ -602,16 +616,16 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { // return 2; //} //return 1; + //} else { + //pthread_mutex_unlock(lock); + int issued = issue_op(Op); + if (issued) { + nissued++; + total++; } else { - pthread_mutex_unlock(lock); - int issued = issue_op(Op); - if (issued) { - nissued++; - total++; - } else { - fprintf(stderr,"failed to issue line: %s, vl: %d\n",Op->key,Op->valuelen); - } + fprintf(stderr,"failed to issue line: %s, vl: %d\n",Op->key,Op->valuelen); } + //} } else { return 1; @@ -680,6 +694,13 @@ int ConnectionMultiApprox::issue_get_with_len(Operation *pop, double now, bool q //if (read_state == IDLE) read_state = WAITING_FOR_GET; uint16_t keylen = strlen(pop->key); + //check if op is in copy_keys (currently going to L1) + auto pos = copy_keys.find(string(pop->key)); + if (pos != copy_keys.end()) { + pos->second.push_back(pop); + return 1; + } + // each line is 4-bytes binary_header_t h = { 0x80, CMD_GET, htons(keylen), 0x00, 0x00, htons(0), @@ -688,7 +709,8 @@ int ConnectionMultiApprox::issue_get_with_len(Operation *pop, double now, bool q h.opcode = CMD_GETQ; } h.opaque = htonl(pop->opaque); - + + evbuffer_add(output, &h, 24); evbuffer_add(output, pop->key, keylen); @@ -977,6 +999,13 @@ int ConnectionMultiApprox::issue_set(Operation *pop, const char* value, double n } uint16_t keylen = strlen(pop->key); + + //check if op is in copy_keys (currently going to L1) + auto pos = copy_keys.find(string(pop->key)); + if (pos != copy_keys.end()) { + pos->second.push_back(pop); + return 1; + } // each line is 4-bytes binary_header_t h = { 0x80, CMD_SET, htons(keylen), @@ -1539,7 +1568,6 @@ void ConnectionMultiApprox::read_callback1() { gloc += rand()%(100*2-1)+1; } ghits++; - //del_wb_keys(op->key); finish_op(op,1); } } else { @@ -1549,17 +1577,16 @@ void ConnectionMultiApprox::read_callback1() { } break; case Operation::SET: - //if (OP_src(op) == SRC_L1_COPY || - // OP_src(op) == SRC_DIRECT_SET || - // OP_src(op) == SRC_L2_M ) { - //} + if (OP_src(op) == SRC_L1_COPY) { + del_copy_keys(string(op->key)); + } if (evict->evicted) { string wb_key(evict->evictedKey); if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { - int ret = add_to_wb_keys(wb_key); - if (ret == 1) { + //int ret = add_to_wb_keys(wb_key); + //if (ret == 1) { issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_WB | ITEM_DIRTY); - } + //} this->stats.incl_wbs++; } else if (evict->evictedFlags & ITEM_EXCL) { //fprintf(stderr,"excl writeback %s\n",evict->evictedKey); @@ -1567,26 +1594,13 @@ void ConnectionMultiApprox::read_callback1() { if ( (options.rand_admit && wb == 0) || (options.threshold && (g_key_hist[wb_key] == 1)) || (options.wb_all) ) { - int ret = add_to_wb_keys(wb_key); - if (ret == 1) { + //int ret = add_to_wb_keys(wb_key); + //if (ret == 1) { issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); - } + //} this->stats.excl_wbs++; } } - /* - if (evict->serverFlags & ITEM_SIZE_CHANGE && OP_src(op) == SRC_DIRECT_SET) { - char key[256]; - memset(key,0,256); - strncpy(key, op->key.c_str(),255); - if (evict->serverFlags & ITEM_INCL) { - int index = lrand48() % (1024 * 1024); - int valuelen = op->valuelen; - //the item's size was changed, issue a SET to L2 as a new command - issue_set(key, &random_char[index], valuelen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_L2_M); - } - } - */ if (OP_src(op) == SRC_DIRECT_SET) { if ( (evict->serverFlags & ITEM_SIZE_CHANGE) || ((evict->serverFlags & ITEM_WAS_HIT) == 0)) { this->stats.set_misses_l1++; @@ -1597,7 +1611,6 @@ void ConnectionMultiApprox::read_callback1() { } } } - //del_wb_keys(op->key); finish_op(op,1); break; case Operation::TOUCH: @@ -1701,20 +1714,11 @@ void ConnectionMultiApprox::read_callback2() { int index = lrand48() % (1024 * 1024); int flags = OP_clu(op) | SRC_L2_M | LOG_OP; issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L1); - //wb_keys.push_back(op->key); last_quiet1 = false; if (OP_incl(op)) { - //wb_keys.push_back(op->key); issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L2); last_quiet2 = false; } - //pthread_mutex_lock(lock); - //fprintf(stderr,"----miss: %s----\n",key); - //for (auto iter = g_wb_keys->begin(); iter != g_wb_keys->end(); ++iter){ - // fprintf(stderr,"%s,%d\n",iter->first.c_str(),iter->second); - //} - //fprintf(stderr,"----%d----\n",cid); - //pthread_mutex_unlock(lock); finish_op(op,0); // sets read_state = IDLE } else { @@ -1722,9 +1726,6 @@ void ConnectionMultiApprox::read_callback2() { int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; - //found in l2, set in l1 - //wb_keys.push_back(op->key); - //add this key to the list of keys currently being copied string key = string(op->key); const char *data = &random_char[index]; int ret = add_to_copy_keys(string(op->key)); @@ -1749,9 +1750,9 @@ void ConnectionMultiApprox::read_callback2() { } break; case Operation::SET: - if (OP_src(op) == SRC_WB) { - del_wb_keys(string(op->key)); - } + //if (OP_src(op) == SRC_WB) { + // del_wb_keys(string(op->key)); + //} finish_op(op,1); break; case Operation::TOUCH: From 89a0153a0aa05de8feba885c2ef9c66481299171 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Fri, 18 Mar 2022 19:59:48 -0400 Subject: [PATCH 50/57] before concurrent hashmap --- Connection.h | 9 +++-- ConnectionMultiApprox.cc | 76 +++++++++++++++++++++++++++++++++++----- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/Connection.h b/Connection.h index 2f6b200..e8987f3 100644 --- a/Connection.h +++ b/Connection.h @@ -364,8 +364,10 @@ class ConnectionMultiApprox { //void set_queue(ConcurrentQueue *a_trace_queue); int add_to_wb_keys(string wb_key); int add_to_copy_keys(string key); - void del_wb_keys(string wb_key); - void del_copy_keys(string key); + int add_to_touch_keys(string key); + void del_wb_keys(string wb_key); + void del_copy_keys(string key); + void del_touch_keys(string key); void set_g_wbkeys(unordered_map> *a_wb_keys); void set_queue(queue *a_trace_queue); void set_lock(pthread_mutex_t* a_lock); @@ -427,6 +429,8 @@ class ConnectionMultiApprox { uint32_t ghits; uint32_t sloc; uint32_t esets; + uint32_t isets; + uint32_t iloc; //std::vector> op_queue; Operation ***op_queue; @@ -440,6 +444,7 @@ class ConnectionMultiApprox { pthread_mutex_t* lock; unordered_map> *g_wb_keys; unordered_map> copy_keys; + unordered_map> touch_keys; queue *trace_queue; // state machine functions / event processing diff --git a/ConnectionMultiApprox.cc b/ConnectionMultiApprox.cc index 8308013..557f639 100644 --- a/ConnectionMultiApprox.cc +++ b/ConnectionMultiApprox.cc @@ -270,8 +270,10 @@ ConnectionMultiApprox::ConnectionMultiApprox(struct event_base* _base, struct ev gets = 0; ghits = 0; esets = 0; + isets = 0; gloc = rand() % (100*2-1)+1; sloc = rand() % (100*2-1)+1; + iloc = rand() % (2*2-1)+1; op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); @@ -364,6 +366,30 @@ void ConnectionMultiApprox::del_copy_keys(string key) { } } +int ConnectionMultiApprox::add_to_touch_keys(string key) { + auto pos = touch_keys.find(key); + if (pos == touch_keys.end()) { + touch_keys.insert( {key, vector() }); + return 1; + } + return 2; +} + + +void ConnectionMultiApprox::del_touch_keys(string key) { + + auto position = touch_keys.find(key); + if (position != touch_keys.end()) { + vector op_list = position->second; + for (auto it = op_list.begin(); it != op_list.end(); ++it) { + issue_op(*it); + } + touch_keys.erase(position); + } else { + fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); + } +} + int ConnectionMultiApprox::issue_op(Operation *Op) { double now = get_time(); int issued = 0; @@ -395,7 +421,19 @@ int ConnectionMultiApprox::issue_op(Operation *Op) { issue_noop(now,1); } if (incl == 1) { - issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); + //if (isets >= iloc) { + if (1) { + const char *data = &random_char[index]; + issued = issue_set(Op, data, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + int ret = add_to_touch_keys(string(Op->key)); + if (ret == 1) { + issue_touch(Op->key,Op->valuelen,now, ITEM_L2 | SRC_DIRECT_SET); + } + iloc += rand()%(2*2-1)+1; + } else { + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); + } + isets++; } else if (incl == 2) { issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); if (esets >= sloc) { @@ -1563,8 +1601,11 @@ void ConnectionMultiApprox::read_callback1() { //finish_op(op,0); } else { - if (OP_incl(op) && ghits >= gloc) { - issue_touch(op->key,vl,now, ITEM_L2 | SRC_L1_H); + if (OP_incl(op)) { // && ghits >= gloc) { + int ret = add_to_touch_keys(string(op->key)); + if (ret == 1) { + issue_touch(op->key,vl,now, ITEM_L2 | SRC_L1_H); + } gloc += rand()%(100*2-1)+1; } ghits++; @@ -1577,7 +1618,8 @@ void ConnectionMultiApprox::read_callback1() { } break; case Operation::SET: - if (OP_src(op) == SRC_L1_COPY) { + if (OP_src(op) == SRC_L1_COPY || + OP_src(op) == SRC_L2_M) { del_copy_keys(string(op->key)); } if (evict->evicted) { @@ -1713,12 +1755,15 @@ void ConnectionMultiApprox::read_callback2() { int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); int flags = OP_clu(op) | SRC_L2_M | LOG_OP; - issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L1); - last_quiet1 = false; - if (OP_incl(op)) { - issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L2); - last_quiet2 = false; + int ret = add_to_copy_keys(string(op->key)); + if (ret == 1) { + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L1); + if (OP_incl(op)) { + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L2); + last_quiet2 = false; + } } + last_quiet1 = false; finish_op(op,0); // sets read_state = IDLE } else { @@ -1756,6 +1801,19 @@ void ConnectionMultiApprox::read_callback2() { finish_op(op,1); break; case Operation::TOUCH: + if (OP_src(op) == SRC_DIRECT_SET || SRC_L1_H) { + int valuelen = op->valuelen; + if (!found) { + int index = lrand48() % (1024 * 1024); + issue_set(op->key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP | SRC_L2_M); + this->stats.set_misses_l2++; + } else { + if (OP_src(op) == SRC_DIRECT_SET) { + issue_touch(op->key,valuelen,now, ITEM_L1 | SRC_L2_H | ITEM_DIRTY); + } + } + del_touch_keys(string(op->key)); + } finish_op(op,0); break; case Operation::DELETE: From 0ac74f172c52268ee33977d1b95d0a91411300bf Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 23 Mar 2022 14:21:10 -0400 Subject: [PATCH 51/57] fixed --- Connection.h | 3 - ConnectionMulti.cc | 4 +- ConnectionMultiApprox.cc | 295 +++++++++++++++++++++++---------------- mutilate.cc | 44 ++++-- 4 files changed, 205 insertions(+), 141 deletions(-) diff --git a/Connection.h b/Connection.h index e8987f3..08f6b16 100644 --- a/Connection.h +++ b/Connection.h @@ -389,7 +389,6 @@ class ConnectionMultiApprox { double last_rx; // Used to moderate transmission rate. double last_tx; - vector wb_keys; enum read_state_enum { INIT_READ, CONN_SETUP, @@ -443,8 +442,6 @@ class ConnectionMultiApprox { Generator *iagen; pthread_mutex_t* lock; unordered_map> *g_wb_keys; - unordered_map> copy_keys; - unordered_map> touch_keys; queue *trace_queue; // state machine functions / event processing diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 4c36fac..527df1d 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -27,6 +27,8 @@ #include #include "blockingconcurrentqueue.h" +//#include + #define ITEM_L1 1 #define ITEM_L2 2 #define LOG_OP 4 @@ -86,7 +88,7 @@ //#define DEBUGMC //#define DEBUGS - +//using namespace folly; using namespace moodycamel; pthread_mutex_t cid_lock_m = PTHREAD_MUTEX_INITIALIZER; diff --git a/ConnectionMultiApprox.cc b/ConnectionMultiApprox.cc index 557f639..01b5443 100644 --- a/ConnectionMultiApprox.cc +++ b/ConnectionMultiApprox.cc @@ -12,6 +12,7 @@ #include #include + #include "config.h" #include "Connection.h" @@ -27,6 +28,9 @@ #include #include "blockingconcurrentqueue.h" +//#include +//#include + #define ITEM_L1 1 #define ITEM_L2 2 #define LOG_OP 4 @@ -86,8 +90,45 @@ //#define DEBUGMC //#define DEBUGS - +//using namespace folly; using namespace moodycamel; +//using namespace fmt; + +//struct node { +// long long addr,label; +// node *nxt; +// node(long long _addr = 0, long long _label = 0, node *_nxt = NULL) +// : addr(_addr),label(_label),nxt(_nxt) {} +//}; +// +//struct tnode { +// long long tm,offset; int size; +//};//trace file data structure +// +//long long find(long long addr) { +// int t = addr%MAXH; +// node *tmp = hash[t],*pre = NULL; +// while (tmp) { +// if (tmp->addr == addr) { +// long long tlabel = tmp->label; +// if (pre == NULL) hash[t] = tmp->nxt; +// else pre->nxt = tmp->nxt; +// delete tmp; +// return tlabel; +// } +// pre = tmp; +// tmp = tmp->nxt; +// } +// return 0; +//} +// +//void insert(long long addr ) { +// int t = addr%MAXH; +// node *tmp = new node(addr,n,hash[t]); +// hash[t] = tmp; +//} + + pthread_mutex_t cid_lock_m_approx = PTHREAD_MUTEX_INITIALIZER; static uint32_t connids_m = 1; @@ -109,9 +150,6 @@ typedef struct _evicted_type { char *evictedData; } evicted_t; -static vector cid_rate; -extern map g_key_hist; -extern int max_n[3]; static void init_inclusives(char *inclusive_str) { int j = 1; @@ -222,6 +260,19 @@ void ConnectionMultiApprox::output_op(Operation *op, int type, bool found) { write(2,output,strlen(output)); } +//extern USPMCQueue g_trace_queue; +//static vector cid_rate; +//extern ConcurrentHashMap cid_rate; +extern unordered_map cid_rate; +//extern ConcurrentHashMap> copy_keys; +extern unordered_map> copy_keys; +extern unordered_map touch_keys; +extern unordered_map> wb_keys; +//extern ConcurrentHashMap> wb_keys; + +extern map g_key_hist; +extern int max_n[3]; + /** * Create a new connection to a server endpoint. */ @@ -234,13 +285,10 @@ ConnectionMultiApprox::ConnectionMultiApprox(struct event_base* _base, struct ev pthread_mutex_lock(&cid_lock_m_approx); cid = connids_m++; if (cid == 1) { - cid_rate.push_back(100); - cid_rate.push_back(0); init_classes(); init_inclusives(options.inclusives); - } else { - cid_rate.push_back(0); } + cid_rate.insert( { cid, 0 } ); pthread_mutex_unlock(&cid_lock_m_approx); @@ -271,9 +319,9 @@ ConnectionMultiApprox::ConnectionMultiApprox(struct event_base* _base, struct ev ghits = 0; esets = 0; isets = 0; - gloc = rand() % (100*2-1)+1; - sloc = rand() % (100*2-1)+1; - iloc = rand() % (2*2-1)+1; + gloc = rand() % (10*2-1)+1; + sloc = rand() % (10*2-1)+1; + iloc = rand() % (10*2-1)+1; op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); @@ -323,23 +371,12 @@ uint32_t ConnectionMultiApprox::get_cid() { } int ConnectionMultiApprox::add_to_wb_keys(string key) { - int ret = -1; - pthread_mutex_lock(lock); - auto pos = g_wb_keys->find(key); - if (pos == g_wb_keys->end()) { - g_wb_keys->insert( {key, vector() }); - ret = 1; - //fprintf(stderr,"----set: %s----\n",Op.key.c_str()); - //for (auto iter = g_wb_keys->begin(); iter != g_wb_keys->end(); ++iter){ - // fprintf(stderr,"%s,%d\n",iter->first.c_str(),iter->second); - //} - //fprintf(stderr,"----%d----\n",cid); - } else { - ret = 2; + auto pos = wb_keys.find(key); + if (pos == wb_keys.end()) { + wb_keys.insert( {key, vector() }); + return 1; } - - pthread_mutex_unlock(lock); - return ret; + return 2; } int ConnectionMultiApprox::add_to_copy_keys(string key) { @@ -356,20 +393,21 @@ void ConnectionMultiApprox::del_copy_keys(string key) { auto position = copy_keys.find(key); if (position != copy_keys.end()) { - vector op_list = position->second; + vector op_list = vector(position->second); + copy_keys.erase(position); for (auto it = op_list.begin(); it != op_list.end(); ++it) { issue_op(*it); } - copy_keys.erase(position); } else { fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); } } int ConnectionMultiApprox::add_to_touch_keys(string key) { + //return touch_keys.assign_if_equal( key, NULL, cid ) != NULL ? 1 : 2; auto pos = touch_keys.find(key); if (pos == touch_keys.end()) { - touch_keys.insert( {key, vector() }); + touch_keys.insert( {key, cid }); return 1; } return 2; @@ -377,13 +415,9 @@ int ConnectionMultiApprox::add_to_touch_keys(string key) { void ConnectionMultiApprox::del_touch_keys(string key) { - + //touch_keys.erase(key); auto position = touch_keys.find(key); if (position != touch_keys.end()) { - vector op_list = position->second; - for (auto it = op_list.begin(); it != op_list.end(); ++it) { - issue_op(*it); - } touch_keys.erase(position); } else { fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); @@ -421,15 +455,15 @@ int ConnectionMultiApprox::issue_op(Operation *Op) { issue_noop(now,1); } if (incl == 1) { - //if (isets >= iloc) { - if (1) { + if (isets >= iloc) { + //if (1) { const char *data = &random_char[index]; issued = issue_set(Op, data, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); - int ret = add_to_touch_keys(string(Op->key)); - if (ret == 1) { + //int ret = add_to_touch_keys(string(Op->key)); + //if (ret == 1) { issue_touch(Op->key,Op->valuelen,now, ITEM_L2 | SRC_DIRECT_SET); - } - iloc += rand()%(2*2-1)+1; + //} + iloc += rand()%(10*2-1)+1; } else { issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); } @@ -438,7 +472,7 @@ int ConnectionMultiApprox::issue_op(Operation *Op) { issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); if (esets >= sloc) { issue_delete(Op->key,now,ITEM_L2 | SRC_DIRECT_SET); - sloc += rand()%(100*2-1)+1; + sloc += rand()%(10*2-1)+1; } esets++; } @@ -459,18 +493,16 @@ int ConnectionMultiApprox::issue_op(Operation *Op) { void ConnectionMultiApprox::del_wb_keys(string key) { - pthread_mutex_lock(lock); - auto position = g_wb_keys->find(key); - if (position != g_wb_keys->end()) { - vector op_list = position->second; + auto position = wb_keys.find(key); + if (position != wb_keys.end()) { + vector op_list = vector(position->second); + wb_keys.erase(position); for (auto it = op_list.begin(); it != op_list.end(); ++it) { issue_op(*it); } - g_wb_keys->erase(position); } else { fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); } - pthread_mutex_unlock(lock); } @@ -576,14 +608,54 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { int ret = 0; int nissued = 0; - while (nissued < 2) { + + //while (nissued < 1) { //pthread_mutex_lock(lock); - if (!trace_queue->empty()) { - Operation *Op = (trace_queue->front()); + //if (!trace_queue->empty()) { + + /* check if in global wb queue */ + //double percent = (double)total/((double)trace_queue_n) * 100; + //if (percent > o_percent+2) { + // //update the percentage table and see if we should execute + // if (options.ratelimit) { + // double min_percent = 1000; + // auto it = cid_rate.begin(); + // while (it != cid_rate.end()) { + // if (it->second < min_percent) { + // min_percent = it->second; + // } + // ++it; + // } + + // if (percent > min_percent+2) { + // struct timeval tv; + // tv.tv_sec = 0; + // tv.tv_usec = 100; + // int good = 0; + // if (!event_pending(timer, EV_TIMEOUT, NULL)) { + // good = evtimer_add(timer, &tv); + // } + // if (good != 0) { + // fprintf(stderr,"eventimer is messed up!\n"); + // return 2; + // } + // return 1; + // } + // } + // cid_rate.insert( {cid, percent}); + // fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); + // o_percent = percent; + //} + // + + Operation *Op = trace_queue->front(); + trace_queue->pop(); + //Operation *Op = g_trace_queue.dequeue(); + if (Op->type == Operation::SASL) { eof = 1; - cid_rate[cid] = 100; + cid_rate.insert( {cid, 100 } ); fprintf(stderr,"cid %d done\n",cid); string op_queue1; string op_queue2; @@ -603,43 +675,16 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { } - /* check if in global wb queue */ - double percent = (double)total/((double)trace_queue_n) * 100; - if (percent > o_percent+2) { - //update the percentage table and see if we should execute - pthread_mutex_lock(lock); - std::vector::iterator mp = std::min_element(cid_rate.begin(), cid_rate.end()); - double min_percent = *mp; - - if (options.ratelimit && percent > min_percent+2) { - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 100; - int good = 0; - if (!event_pending(timer, EV_TIMEOUT, NULL)) { - good = evtimer_add(timer, &tv); - } - if (good != 0) { - pthread_mutex_unlock(lock); - fprintf(stderr,"eventimer is messed up!\n"); - return 2; - } - pthread_mutex_unlock(lock); - return 1; - } - cid_rate[cid] = percent; - pthread_mutex_unlock(lock); - //fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); - o_percent = percent; - } - trace_queue->pop(); + //trace_queue->pop(); //pthread_mutex_lock(lock); - //auto check = g_wb_keys->find(string(Op->key)); - //if (check != g_wb_keys->end()) { + //auto check = wb_keys.find(string(Op->key)); + //if (check != wb_keys.end()) { // check->second.push_back(Op); - // pthread_mutex_unlock(lock); + // return 0; + //} + //pthread_mutex_unlock(lock); //pthread_mutex_unlock(lock); //struct timeval tv; //double delay; @@ -665,14 +710,14 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { } //} - } else { - return 1; - } - } - if (last_quiet1) { - issue_noop(now,1); - last_quiet1 = false; - } + //} else { + // return 1; + //} + //} + //if (last_quiet1) { + // issue_noop(now,1); + // last_quiet1 = false; + //} return ret; @@ -682,6 +727,13 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { * Issue a get request to the server. */ int ConnectionMultiApprox::issue_get_with_len(Operation *pop, double now, bool quiet, uint32_t flags, Operation *l1) { + + //check if op is in copy_keys (currently going to L1) + //auto check = copy_keys.find(string(pop->key)); + //if (check != copy_keys.end()) { + // check->second.push_back(pop); + // return 1; + //} struct evbuffer *output = NULL; int level = 0; @@ -717,12 +769,12 @@ int ConnectionMultiApprox::issue_get_with_len(Operation *pop, double now, bool q if (l1 != NULL) { pop->l1 = l1; } + op_queue[level][pop->opaque] = pop; //op_queue[level].push(op); op_queue_size[level]++; - #ifdef DEBUGS - fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,valuelen,level,flags,pop->opaque); + fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,pop->key,pop->valuelen,level,flags,pop->opaque); #endif if (opaque[level] > OPAQUE_MAX) { @@ -732,12 +784,6 @@ int ConnectionMultiApprox::issue_get_with_len(Operation *pop, double now, bool q //if (read_state == IDLE) read_state = WAITING_FOR_GET; uint16_t keylen = strlen(pop->key); - //check if op is in copy_keys (currently going to L1) - auto pos = copy_keys.find(string(pop->key)); - if (pos != copy_keys.end()) { - pos->second.push_back(pop); - return 1; - } // each line is 4-bytes binary_header_t h = { 0x80, CMD_GET, htons(keylen), @@ -1002,6 +1048,13 @@ void ConnectionMultiApprox::issue_noop(double now, int level) { */ int ConnectionMultiApprox::issue_set(Operation *pop, const char* value, double now, uint32_t flags) { + //check if op is in copy_keys (currently going to L1) + //auto check = copy_keys.find(string(pop->key)); + //if (check != copy_keys.end()) { + // check->second.push_back(pop); + // return 1; + //} + struct evbuffer *output = NULL; int level = 0; int length = pop->valuelen; @@ -1029,7 +1082,7 @@ int ConnectionMultiApprox::issue_set(Operation *pop, const char* value, double n //op_queue[level].push(op); op_queue_size[level]++; #ifdef DEBUGS - fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,length,level,flags,pop->opaque); + fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,pop->key,length,level,flags,pop->opaque); #endif if (opaque[level] > OPAQUE_MAX) { @@ -1038,12 +1091,6 @@ int ConnectionMultiApprox::issue_set(Operation *pop, const char* value, double n uint16_t keylen = strlen(pop->key); - //check if op is in copy_keys (currently going to L1) - auto pos = copy_keys.find(string(pop->key)); - if (pos != copy_keys.end()) { - pos->second.push_back(pop); - return 1; - } // each line is 4-bytes binary_header_t h = { 0x80, CMD_SET, htons(keylen), @@ -1601,12 +1648,12 @@ void ConnectionMultiApprox::read_callback1() { //finish_op(op,0); } else { - if (OP_incl(op)) { // && ghits >= gloc) { - int ret = add_to_touch_keys(string(op->key)); - if (ret == 1) { + if (OP_incl(op) && ghits >= gloc) { + //int ret = add_to_touch_keys(string(op->key)); + //if (ret == 1) { issue_touch(op->key,vl,now, ITEM_L2 | SRC_L1_H); - } - gloc += rand()%(100*2-1)+1; + //} + gloc += rand()%(10*2-1)+1; } ghits++; finish_op(op,1); @@ -1618,10 +1665,10 @@ void ConnectionMultiApprox::read_callback1() { } break; case Operation::SET: - if (OP_src(op) == SRC_L1_COPY || - OP_src(op) == SRC_L2_M) { - del_copy_keys(string(op->key)); - } + //if (OP_src(op) == SRC_L1_COPY || + // OP_src(op) == SRC_L2_M) { + // del_copy_keys(string(op->key)); + //} if (evict->evicted) { string wb_key(evict->evictedKey); if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { @@ -1755,14 +1802,14 @@ void ConnectionMultiApprox::read_callback2() { int valuelen = op->valuelen; int index = lrand48() % (1024 * 1024); int flags = OP_clu(op) | SRC_L2_M | LOG_OP; - int ret = add_to_copy_keys(string(op->key)); - if (ret == 1) { + //int ret = add_to_copy_keys(string(op->key)); + //if (ret == 1) { issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L1); if (OP_incl(op)) { issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L2); last_quiet2 = false; } - } + //} last_quiet1 = false; finish_op(op,0); // sets read_state = IDLE @@ -1773,10 +1820,10 @@ void ConnectionMultiApprox::read_callback2() { int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; string key = string(op->key); const char *data = &random_char[index]; - int ret = add_to_copy_keys(string(op->key)); - if (ret == 1) { + //int ret = add_to_copy_keys(string(op->key)); + //if (ret == 1) { issue_set(op->key,data,valuelen, now, flags); - } + //} this->stats.copies_to_l1++; //djb: this is automatically done in the L2 server //if (OP_excl(op)) { //djb: todo should we delete here for approx or just let it die a slow death? @@ -1812,7 +1859,7 @@ void ConnectionMultiApprox::read_callback2() { issue_touch(op->key,valuelen,now, ITEM_L1 | SRC_L2_H | ITEM_DIRTY); } } - del_touch_keys(string(op->key)); + //del_touch_keys(string(op->key)); } finish_op(op,0); break; diff --git a/mutilate.cc b/mutilate.cc index 296cc19..7ae5982 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -49,12 +49,15 @@ namespace fs = std::filesystem; #include "mutilate.h" #include "util.h" #include "blockingconcurrentqueue.h" +//#include +//#include #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define hashsize(n) ((unsigned long int)1<<(n)) using namespace std; using namespace moodycamel; +//using namespace folly; int max_n[3] = {0,0,0}; ifstream kvfile; @@ -65,10 +68,21 @@ pthread_cond_t reader_ready; int reader_not_ready = 1; pthread_mutex_t *item_locks; -int item_lock_hashpower = 13; +int item_lock_hashpower = 14; map g_key_hist; +//USPMCQueue g_trace_queue; + +//ConcurrentHashMap cid_rate; +unordered_map cid_rate; +//ConcurrentHashMap> copy_keys; +unordered_map> copy_keys; +unordered_map> wb_keys; +//ConcurrentHashMap> touch_keys; +unordered_map touch_keys; +//ConcurrentHashMap> wb_keys; + gengetopt_args_info args; char random_char[4 * 1024 * 1024]; // Buffer used to generate random values. @@ -1124,12 +1138,14 @@ void* reader_thread(void *arg) { Op->type = Operation::SET; break; } - if (first) { - appid = (rand() % (trace_queue->size()-1)) + 1; - if (appid == 0) appid = 1; - first = 0; - } - batch++; + //if (first) { + // appid = (rand() % (trace_queue->size()-1)) + 1; + // if (appid == 0) appid = 1; + // first = 0; + //} + //batch++; + appid = (rand() % (trace_queue->size()-1)) + 1; + if (appid == 0) appid = 1; } else { continue; } @@ -1183,11 +1199,12 @@ void* reader_thread(void *arg) { } Op->appid = appid; trace_queue->at(appid)->push(Op); - if (twitter_trace == 3 && batch == 2) { - appid = (rand() % (trace_queue->size()-1)) + 1; - if (appid == 0) appid = 1; - batch = 0; - } + //g_trace_queue.enqueue(Op); + //if (twitter_trace == 3) { // && batch == 2) { + // appid = (rand() % (trace_queue->size()-1)) + 1; + // if (appid == 0) appid = 1; + // batch = 0; + //} } } else { fprintf(stderr,"big error!\n"); @@ -1214,7 +1231,8 @@ void* reader_thread(void *arg) { Operation *eof = new Operation; eof->type = Operation::SASL; eof->appid = j; - trace_queue->at(j)->push(eof); + trace_queue->at(j)->push(eof); + //g_trace_queue.enqueue(eof); if (i == 0) { fprintf(stderr,"appid %d, tq size: %ld\n",j,trace_queue->at(j)->size()); } From f6b17ef8c565985425e656c5528d5f0c144939fe Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Mon, 11 Apr 2022 20:27:03 -0400 Subject: [PATCH 52/57] batching mode --- Connection.h | 200 +++- ConnectionMulti.cc | 10 - ConnectionMultiApprox.cc | 10 - ConnectionMultiApproxBatch.cc | 1643 +++++++++++++++++++++++++++++++++ cmdline.ggo | 1 + mutilate.cc | 162 +++- 6 files changed, 2003 insertions(+), 23 deletions(-) create mode 100644 ConnectionMultiApproxBatch.cc diff --git a/Connection.h b/Connection.h index 08f6b16..81ba7b5 100644 --- a/Connection.h +++ b/Connection.h @@ -23,31 +23,58 @@ #include "blockingconcurrentqueue.h" #include "Protocol.h" -#define OPAQUE_MAX 16384 +#define OPAQUE_MAX 64000 #define hashsize(n) ((unsigned long int)1<<(n)) #define hashmask(n) (hashsize(n)-1) #define MAX_BUFFER_SIZE 10*1024*1024 +#define MAX_LEVELS 2+1 using namespace std; using namespace moodycamel; + +typedef struct _evicted_type { + bool evicted; + uint32_t evictedFlags; + uint32_t serverFlags; + uint32_t clsid; + uint32_t evictedKeyLen; + uint32_t evictedLen; + char *evictedKey; + char *evictedData; +} evicted_t; + +typedef struct resp { + uint32_t opaque; + int opcode; + bool found; + evicted_t* evict; +} resp_t; + + void bev_event_cb(struct bufferevent *bev, short events, void *ptr); void bev_read_cb(struct bufferevent *bev, void *ptr); void bev_event_cb1(struct bufferevent *bev, short events, void *ptr); void bev_event_cb1_approx(struct bufferevent *bev, short events, void *ptr); +void bev_event_cb1_approx_batch(struct bufferevent *bev, short events, void *ptr); void bev_read_cb1(struct bufferevent *bev, void *ptr); void bev_read_cb1_approx(struct bufferevent *bev, void *ptr); +void bev_read_cb1_approx_batch(struct bufferevent *bev, void *ptr); void bev_event_cb2(struct bufferevent *bev, short events, void *ptr); void bev_event_cb2_approx(struct bufferevent *bev, short events, void *ptr); +void bev_event_cb2_approx_batch(struct bufferevent *bev, short events, void *ptr); void bev_read_cb2(struct bufferevent *bev, void *ptr); void bev_read_cb2_approx(struct bufferevent *bev, void *ptr); +void bev_read_cb2_approx_batch(struct bufferevent *bev, void *ptr); void bev_write_cb(struct bufferevent *bev, void *ptr); void bev_write_cb_m(struct bufferevent *bev, void *ptr); void bev_write_cb_m_approx(struct bufferevent *bev, void *ptr); +void bev_write_cb_m_approx_batch(struct bufferevent *bev, void *ptr); void timer_cb(evutil_socket_t fd, short what, void *ptr); void timer_cb_m(evutil_socket_t fd, short what, void *ptr); void timer_cb_m_approx(evutil_socket_t fd, short what, void *ptr); +void timer_cb_m_approx_batch(evutil_socket_t fd, short what, void *ptr); class Protocol; @@ -477,4 +504,175 @@ class ConnectionMultiApprox { bool consume_resp_line(evbuffer *input, bool &done); }; +class ConnectionMultiApproxBatch { +public: + ConnectionMultiApproxBatch(struct event_base* _base, struct evdns_base* _evdns, + string _hostname1, string _hostname2, string _port, options_t options, + bool sampling = true, int fd1 = -1, int fd2 = -1); + + ~ConnectionMultiApproxBatch(); + + int do_connect(); + + double start_time; // Time when this connection began operations. + ConnectionStats stats; + options_t options; + + bool is_ready() { return read_state == IDLE; } + void set_priority(int pri); + + // state commands + void start() { + //fprintf(stderr,"connid: %d starting...\n",cid); + drive_write_machine(); + } + void start_loading(); + void reset(); + bool check_exit_condition(double now = 0.0); + + void event_callback1(short events); + void event_callback2(short events); + void read_callback1(); + void read_callback2(); + // event callbacks + void write_callback(); + void timer_callback(); + + int eof; + uint32_t get_cid(); + //void set_queue(ConcurrentQueue *a_trace_queue); + int add_to_wb_keys(string wb_key); + int add_to_copy_keys(string key); + int add_to_touch_keys(string key); + void del_wb_keys(string wb_key); + void del_copy_keys(string key); + void del_touch_keys(string key); + void set_g_wbkeys(unordered_map> *a_wb_keys); + void set_queue(queue *a_trace_queue); + void set_lock(pthread_mutex_t* a_lock); + int send_write_buffer(int level); + int add_get_op_to_queue(Operation *pop, int level); + int add_set_to_queue(Operation *pop, int level, const char *value); + size_t handle_response_batch(unsigned char *rbuf_pos, resp_t *resp, + size_t read_bytes, size_t consumed_bytes, + int level, int extra); + +private: + string hostname1; + string hostname2; + string port; + + double o_percent; + int trace_queue_n; + struct event_base *base; + struct evdns_base *evdns; + struct bufferevent *bev1; + struct bufferevent *bev2; + + struct event *timer; // Used to control inter-transmission time. + double next_time; // Inter-transmission time parameters. + double last_rx; // Used to moderate transmission rate. + double last_tx; + + enum read_state_enum { + INIT_READ, + CONN_SETUP, + LOADING, + IDLE, + WAITING_FOR_GET, + WAITING_FOR_SET, + WAITING_FOR_DELETE, + MAX_READ_STATE, + }; + + enum write_state_enum { + INIT_WRITE, + ISSUING, + WAITING_FOR_TIME, + WAITING_FOR_OPQ, + MAX_WRITE_STATE, + }; + + read_state_enum read_state; + write_state_enum write_state; + + // Parameters to track progress of the data loader. + int loader_issued, loader_completed; + + uint32_t *opaque; + int *issue_buf_size; + int *issue_buf_n; + unsigned char **issue_buf_pos; + unsigned char **issue_buf; + bool last_quiet1; + bool last_quiet2; + uint32_t total; + uint32_t cid; + uint32_t gets; + uint32_t gloc; + uint32_t ghits; + uint32_t sloc; + uint32_t esets; + uint32_t isets; + uint32_t iloc; + + uint32_t clsid_; + uint32_t incl_; + uint32_t buffer_size_; + unsigned char* buffer_write[MAX_LEVELS]; + unsigned char* buffer_read[MAX_LEVELS]; + unsigned char* buffer_write_pos[MAX_LEVELS]; + unsigned char* buffer_read_pos[MAX_LEVELS]; + uint32_t buffer_read_n[MAX_LEVELS]; + uint32_t buffer_write_n[MAX_LEVELS]; + uint32_t buffer_read_nbytes[MAX_LEVELS]; + uint32_t buffer_write_nbytes[MAX_LEVELS]; + + + //std::vector> op_queue; + Operation ***op_queue; + uint32_t *op_queue_size; + + + Generator *valuesize; + Generator *keysize; + KeyGenerator *keygen; + Generator *iagen; + pthread_mutex_t* lock; + unordered_map> *g_wb_keys; + queue *trace_queue; + + // state machine functions / event processing + void pop_op(Operation *op); + void output_op(Operation *op, int type, bool was_found); + //void finish_op(Operation *op); + void finish_op(Operation *op,int was_hit); + int issue_getsetorset(double now = 0.0); + void drive_write_machine(double now = 0.0); + + // request functions + void issue_sasl(); + int issue_op(Operation* op); + int issue_noop(int level = 1); + int issue_touch(const char* key, int valuelen, double now, int level); + int issue_delete(const char* key, double now, uint32_t flags); + int issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); + int issue_get_with_len(Operation *pop, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); + int issue_set(const char* key, const char* value, int length, double now, uint32_t flags); + int issue_set(Operation *pop, const char* value, double now, uint32_t flags); + + // protocol fucntions + int set_request_ascii(const char* key, const char* value, int length); + int set_request_binary(const char* key, const char* value, int length); + int set_request_resp(const char* key, const char* value, int length); + + int get_request_ascii(const char* key); + int get_request_binary(const char* key); + int get_request_resp(const char* key); + + bool consume_binary_response(evbuffer *input); + bool consume_ascii_line(evbuffer *input, bool &done); + bool consume_resp_line(evbuffer *input, bool &done); +}; + #endif diff --git a/ConnectionMulti.cc b/ConnectionMulti.cc index 527df1d..81a6cda 100644 --- a/ConnectionMulti.cc +++ b/ConnectionMulti.cc @@ -100,16 +100,6 @@ static int classes = 0; static int sizes[NCLASSES+1]; static int inclusives[NCLASSES+1]; -typedef struct _evicted_type { - bool evicted; - uint32_t evictedFlags; - uint32_t serverFlags; - uint32_t clsid; - uint32_t evictedKeyLen; - uint32_t evictedLen; - char *evictedKey; - char *evictedData; -} evicted_t; static vector cid_rate; diff --git a/ConnectionMultiApprox.cc b/ConnectionMultiApprox.cc index 01b5443..40b071f 100644 --- a/ConnectionMultiApprox.cc +++ b/ConnectionMultiApprox.cc @@ -139,16 +139,6 @@ static int classes = 0; static int sizes[NCLASSES+1]; static int inclusives[NCLASSES+1]; -typedef struct _evicted_type { - bool evicted; - uint32_t evictedFlags; - uint32_t serverFlags; - uint32_t clsid; - uint32_t evictedKeyLen; - uint32_t evictedLen; - char *evictedKey; - char *evictedData; -} evicted_t; static void init_inclusives(char *inclusive_str) { diff --git a/ConnectionMultiApproxBatch.cc b/ConnectionMultiApproxBatch.cc new file mode 100644 index 0000000..c26c406 --- /dev/null +++ b/ConnectionMultiApproxBatch.cc @@ -0,0 +1,1643 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#include "config.h" + +#include "Connection.h" +#include "distributions.h" +#include "Generator.h" +#include "mutilate.h" +#include "binary_protocol.h" +#include "util.h" +#include +#include +#include +#include +#include +#include "blockingconcurrentqueue.h" + +//#include +//#include + +#define ITEM_L1 1 +#define ITEM_L2 2 +#define LOG_OP 4 +#define SRC_L1_M 8 +#define SRC_L1_H 16 +#define SRC_L2_M 32 +#define SRC_L2_H 64 +#define SRC_DIRECT_SET 128 +#define SRC_L1_COPY 256 +#define SRC_WB 512 + +#define ITEM_INCL 4096 +#define ITEM_EXCL 8192 +#define ITEM_DIRTY 16384 +#define ITEM_SIZE_CHANGE 131072 +#define ITEM_WAS_HIT 262144 + +#define LEVELS 2 +#define SET_INCL(incl,flags) \ + switch (incl) { \ + case 1: \ + flags |= ITEM_INCL; \ + break; \ + case 2: \ + flags |= ITEM_EXCL; \ + break; \ + \ + } \ + +#define GET_INCL(incl,flags) \ + if (flags & ITEM_INCL) incl = 1; \ + else if (flags & ITEM_EXCL) incl = 2; \ + +//#define OP_level(op) ( ((op)->flags & ITEM_L1) ? ITEM_L1 : ITEM_L2 ) +#define OP_level(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define FLAGS_level(flags) ( flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_clu(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_L1 | ITEM_L2 | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_src(op) ( (op)->flags & ~(ITEM_L1 | ITEM_L2 | LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY ) ) + +#define OP_log(op) ((op)->flags & LOG_OP) +#define OP_incl(op) ((op)->flags & ITEM_INCL) +#define OP_excl(op) ((op)->flags & ITEM_EXCL) +#define OP_set_flag(op,flag) ((op))->flags |= flag; + +//#define DEBUGMC +//#define DEBUGS + + + +pthread_mutex_t cid_lock_m_approx_batch = PTHREAD_MUTEX_INITIALIZER; +static uint32_t connids_m = 1; + +#define NCLASSES 40 +#define CHUNK_ALIGN_BYTES 8 +static int classes = 0; +static int sizes[NCLASSES+1]; +static int inclusives[NCLASSES+1]; + + + +static void init_inclusives(char *inclusive_str) { + int j = 1; + for (int i = 0; i < (int)strlen(inclusive_str); i++) { + if (inclusive_str[i] == '-') { + continue; + } else { + inclusives[j] = inclusive_str[i] - '0'; + j++; + } + } +} + +static void init_classes() { + + double factor = 1.25; + //unsigned int chunk_size = 48; + //unsigned int item_size = 24; + unsigned int size = 96; //warning if you change this you die + unsigned int i = 0; + unsigned int chunk_size_max = 1048576/2; + while (++i < NCLASSES-1) { + if (size >= chunk_size_max / factor) { + break; + } + if (size % CHUNK_ALIGN_BYTES) + size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); + sizes[i] = size; + size *= factor; + } + sizes[i] = chunk_size_max; + classes = i; + +} + +static int get_class(int vl, uint32_t kl) { + //warning if you change this you die + int vsize = vl+kl+48+1+2; + int res = 1; + while (vsize > sizes[res]) + if (res++ == classes) { + //fprintf(stderr,"item larger than max class size. vsize: %d, class size: %d\n",vsize,sizes[res]); + return -1; + } + return res; +} + +static int get_incl(int vl, int kl) { + int clsid = get_class(vl,kl); + if (clsid) { + return inclusives[clsid]; + } else { + return -1; + } +} + + +void ConnectionMultiApproxBatch::output_op(Operation *op, int type, bool found) { + char output[1024]; + char k[256]; + char a[256]; + char s[256]; + memset(k,0,256); + memset(a,0,256); + memset(s,0,256); + strncpy(k,op->key,255); + switch (type) { + case 0: //get + sprintf(a,"issue_get"); + break; + case 1: //set + sprintf(a,"issue_set"); + break; + case 2: //resp + sprintf(a,"resp"); + break; + } + switch(read_state) { + case INIT_READ: + sprintf(s,"init"); + break; + case CONN_SETUP: + sprintf(s,"setup"); + break; + case LOADING: + sprintf(s,"load"); + break; + case IDLE: + sprintf(s,"idle"); + break; + case WAITING_FOR_GET: + sprintf(s,"waiting for get"); + break; + case WAITING_FOR_SET: + sprintf(s,"waiting for set"); + break; + case WAITING_FOR_DELETE: + sprintf(s,"waiting for del"); + break; + case MAX_READ_STATE: + sprintf(s,"max"); + break; + } + if (type == 2) { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, found: %d, type: %d\n",cid,a,k,op->opaque,found,op->type); + } else { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, type: %d\n",cid,a,k,op->opaque,op->type); + } + size_t res = write(2,output,strlen(output)); + if (res != strlen(output)) { + fprintf(stderr,"error outputingiii\n"); + } +} + +extern unordered_map cid_rate; +extern unordered_map> copy_keys; +extern unordered_map touch_keys; +extern unordered_map> wb_keys; + +extern map g_key_hist; +extern int max_n[3]; + +/** + * Create a new connection to a server endpoint. + */ +ConnectionMultiApproxBatch::ConnectionMultiApproxBatch(struct event_base* _base, struct evdns_base* _evdns, + string _hostname1, string _hostname2, string _port, options_t _options, + bool sampling, int fd1, int fd2 ) : + start_time(0), stats(sampling), options(_options), + hostname1(_hostname1), hostname2(_hostname2), port(_port), base(_base), evdns(_evdns) +{ + pthread_mutex_lock(&cid_lock_m_approx_batch); + cid = connids_m++; + if (cid == 1) { + init_classes(); + init_inclusives(options.inclusives); + } + cid_rate.insert( { cid, 0 } ); + + pthread_mutex_unlock(&cid_lock_m_approx_batch); + + valuesize = createGenerator(options.valuesize); + keysize = createGenerator(options.keysize); + srand(time(NULL)); + keygen = new KeyGenerator(keysize, options.records); + + total = 0; + eof = 0; + o_percent = 0; + + if (options.lambda <= 0) { + iagen = createGenerator("0"); + } else { + D("iagen = createGenerator(%s)", options.ia); + iagen = createGenerator(options.ia); + iagen->set_lambda(options.lambda); + } + + read_state = IDLE; + write_state = INIT_WRITE; + last_quiet1 = false; + last_quiet2 = false; + + last_tx = last_rx = 0.0; + gets = 0; + ghits = 0; + esets = 0; + isets = 0; + gloc = rand() % (10*2-1)+1; + sloc = rand() % (10*2-1)+1; + iloc = rand() % (10*2-1)+1; + + op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + op_queue = (Operation***)malloc(sizeof(Operation**)*(LEVELS+1)); + + for (int i = 0; i <= LEVELS; i++) { + op_queue_size[i] = 0; + opaque[i] = 1; + //op_queue[i] = (Operation*)malloc(sizeof(int)*OPAQUE_MAX); + op_queue[i] = (Operation**)malloc(sizeof(Operation*)*(OPAQUE_MAX+1)); + for (int j = 0; j <= OPAQUE_MAX; j++) { + op_queue[i][j] = NULL; + } + + } + + + bev1 = bufferevent_socket_new(base, fd1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev1, bev_read_cb1_approx_batch, bev_write_cb_m_approx_batch, bev_event_cb1_approx_batch, this); + bufferevent_enable(bev1, EV_READ | EV_WRITE); + //bufferevent_setwatermark(bev1, EV_READ, 4*1024, 0); + + bev2 = bufferevent_socket_new(base, fd2, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev2, bev_read_cb2_approx_batch, bev_write_cb_m_approx_batch, bev_event_cb2_approx_batch, this); + bufferevent_enable(bev2, EV_READ | EV_WRITE); + //bufferevent_setwatermark(bev2, EV_READ, 4*1024, 0); + + timer = evtimer_new(base, timer_cb_m_approx_batch, this); + + read_state = IDLE; +} + + +void ConnectionMultiApproxBatch::set_queue(queue* a_trace_queue) { + trace_queue = a_trace_queue; + trace_queue_n = a_trace_queue->size(); + Operation *Op = trace_queue->front(); + incl_ = get_incl(Op->valuelen,strlen(Op->key)); + clsid_ = get_class(Op->valuelen,strlen(Op->key)); + + buffer_size_ = (1024*512)*options.depth; + //setup the buffers + //max is (valuelen + 256 + 24 + 4 + 4 ) * depth + for (int i = 1; i <= LEVELS; i++) { + buffer_write[i] = (unsigned char*)malloc(buffer_size_); + buffer_read[i] = (unsigned char*)malloc(buffer_size_); + buffer_write_n[i] = 0; + buffer_read_n[i] = 0; + buffer_write_nbytes[i] = 0; + buffer_read_nbytes[i] = 0; + buffer_write_pos[i] = buffer_write[i]; + buffer_read_pos[i] = buffer_read[i]; + } + +} + +void ConnectionMultiApproxBatch::set_lock(pthread_mutex_t* a_lock) { + lock = a_lock; +} + +void ConnectionMultiApproxBatch::set_g_wbkeys(unordered_map> *a_wb_keys) { + g_wb_keys = a_wb_keys; +} + +uint32_t ConnectionMultiApproxBatch::get_cid() { + return cid; +} + +int ConnectionMultiApproxBatch::add_to_wb_keys(string key) { + auto pos = wb_keys.find(key); + if (pos == wb_keys.end()) { + wb_keys.insert( {key, vector() }); + return 1; + } + return 2; +} + +void ConnectionMultiApproxBatch::del_wb_keys(string key) { + + auto position = wb_keys.find(key); + if (position != wb_keys.end()) { + vector op_list = vector(position->second); + wb_keys.erase(position); + for (auto it = op_list.begin(); it != op_list.end(); ++it) { + issue_op(*it); + } + } else { + fprintf(stderr,"expected wb %s, got nuthin\n",key.c_str()); + } +} + +int ConnectionMultiApproxBatch::add_to_copy_keys(string key) { + auto pos = copy_keys.find(key); + if (pos == copy_keys.end()) { + copy_keys.insert( {key, vector() }); + return 1; + } + return 2; +} + + +void ConnectionMultiApproxBatch::del_copy_keys(string key) { + + auto position = copy_keys.find(key); + if (position != copy_keys.end()) { + vector op_list = vector(position->second); + copy_keys.erase(position); + for (auto it = op_list.begin(); it != op_list.end(); ++it) { + issue_op(*it); + } + } else { + fprintf(stderr,"expected copy %s, got nuthin\n",key.c_str()); + } +} + +int ConnectionMultiApproxBatch::add_to_touch_keys(string key) { + //return touch_keys.assign_if_equal( key, NULL, cid ) != NULL ? 1 : 2; + auto pos = touch_keys.find(key); + if (pos == touch_keys.end()) { + touch_keys.insert( {key, cid }); + return 1; + } + return 2; +} + + +void ConnectionMultiApproxBatch::del_touch_keys(string key) { + //touch_keys.erase(key); + auto position = touch_keys.find(key); + if (position != touch_keys.end()) { + touch_keys.erase(position); + } else { + fprintf(stderr,"expected touch %s, got nuthin\n",key.c_str()); + } +} + +int ConnectionMultiApproxBatch::issue_op(Operation *Op) { + double now = get_time(); + int issued = 0; + Op->clsid = get_class(Op->valuelen,strlen(Op->key)); + int flags = 0; + int index = lrand48() % (1024 * 1024); + int incl = inclusives[Op->clsid]; + SET_INCL(incl,flags); + + switch(Op->type) { + + case Operation::GET: + issued = issue_get_with_len(Op, now, false, flags | LOG_OP | ITEM_L1); + this->stats.gets++; + gets++; + this->stats.gets_cid[cid]++; + break; + case Operation::SET: + if (incl == 1) { + if (isets >= iloc) { + const char *data = &random_char[index]; + issued = issue_set(Op, data, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + issued = issue_touch(Op->key,Op->valuelen,now, ITEM_L2 | SRC_DIRECT_SET); + iloc += rand()%(10*2-1)+1; + } else { + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); + } + isets++; + } else if (incl == 2) { + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + if (esets >= sloc) { + issued = issue_delete(Op->key,now,ITEM_L2 | SRC_DIRECT_SET); + sloc += rand()%(10*2-1)+1; + } + esets++; + } + this->stats.sets++; + this->stats.sets_cid[cid]++; + break; + case Operation::DELETE: + case Operation::TOUCH: + case Operation::NOOP: + case Operation::SASL: + fprintf(stderr,"invalid line: %s, vl: %d\n",Op->key,Op->valuelen); + break; + + } + return issued; +} + + +int ConnectionMultiApproxBatch::do_connect() { + + int connected = 0; + if (options.unix_socket) { + + + struct sockaddr_un sin1; + memset(&sin1, 0, sizeof(sin1)); + sin1.sun_family = AF_LOCAL; + strcpy(sin1.sun_path, hostname1.c_str()); + + int addrlen; + addrlen = sizeof(sin1); + + int err = bufferevent_socket_connect(bev1, (struct sockaddr*)&sin1, addrlen); + if (err == 0) { + connected = 1; + } else { + connected = 0; + err = errno; + fprintf(stderr,"l1 error %s\n",strerror(err)); + } + + struct sockaddr_un sin2; + memset(&sin2, 0, sizeof(sin2)); + sin2.sun_family = AF_LOCAL; + strcpy(sin2.sun_path, hostname2.c_str()); + + addrlen = sizeof(sin2); + err = bufferevent_socket_connect(bev2, (struct sockaddr*)&sin2, addrlen); + if (err == 0) { + connected = 1; + } else { + connected = 0; + err = errno; + fprintf(stderr,"l2 error %s\n",strerror(err)); + } + } + read_state = IDLE; + return connected; +} + +/** + * Destroy a connection, performing cleanup. + */ +ConnectionMultiApproxBatch::~ConnectionMultiApproxBatch() { + + + for (int i = 0; i <= LEVELS; i++) { + free(op_queue[i]); + free(buffer_write[i]); + free(buffer_read[i]); + + } + + free(op_queue_size); + free(opaque); + free(op_queue); + //event_free(timer); + //timer = NULL; + // FIXME: W("Drain op_q?"); + bufferevent_free(bev1); + bufferevent_free(bev2); + + delete iagen; + delete keygen; + delete keysize; + delete valuesize; +} + +/** + * Reset the connection back to an initial, fresh state. + */ +void ConnectionMultiApproxBatch::reset() { + // FIXME: Actually check the connection, drain all bufferevents, drain op_q. + //assert(op_queue.size() == 0); + //evtimer_del(timer); + read_state = IDLE; + write_state = INIT_WRITE; + stats = ConnectionStats(stats.sampling); +} + +/** + * Set our event processing priority. + */ +void ConnectionMultiApproxBatch::set_priority(int pri) { + if (bufferevent_priority_set(bev1, pri)) { + DIE("bufferevent_set_priority(bev, %d) failed", pri); + } +} + + + +/** + * Get/Set or Set Style + * If a GET command: Issue a get first, if not found then set + * If trace file (or prob. write) says to set, then set it + */ +int ConnectionMultiApproxBatch::issue_getsetorset(double now) { + + Operation *Op = trace_queue->front(); + if (Op->type == Operation::SASL) { + eof = 1; + cid_rate.insert( {cid, 100 } ); + fprintf(stderr,"cid %d done\n",cid); + string op_queue1; + string op_queue2; + for (int j = 0; j < 2; j++) { + for (int i = 0; i < OPAQUE_MAX; i++) { + if (op_queue[j+1][i] != NULL) { + if (j == 0) { + op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + } else { + op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + } + } + } + } + send_write_buffer(1); + send_write_buffer(2); + fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + return 1; + } + + while (buffer_write_n[1] < (uint32_t)options.depth) { + int issued = issue_op(Op); + if (issued) { + trace_queue->pop(); + Op = trace_queue->front(); + + if (Op->type == Operation::SASL) { + eof = 1; + cid_rate.insert( {cid, 100 } ); + fprintf(stderr,"cid %d done\n",cid); + string op_queue1; + string op_queue2; + for (int j = 0; j < 2; j++) { + for (int i = 0; i < OPAQUE_MAX; i++) { + if (op_queue[j+1][i] != NULL) { + if (j == 0) { + op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + } else { + op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + } + } + } + } + fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + send_write_buffer(1); + send_write_buffer(2); + return 0; + } + total++; + if (issued == 2) { + return 0; + } + } else { + fprintf(stderr,"failed to issue line: %s, vl: %d\n",Op->key,Op->valuelen); + } + } + + return 0; +} + +int ConnectionMultiApproxBatch::send_write_buffer(int level) { + struct bufferevent *bev = NULL; + switch (level) { + case 1: + bev = bev1; + break; + case 2: + bev = bev2; + break; + default: + bev = bev1; + break; + } + int ret = bufferevent_write(bev,buffer_write[level],buffer_write_nbytes[level]); + if (ret != 0) { + fprintf(stderr,"error writing buffer! level %d, size %d\n",level,buffer_write_nbytes[level]); + } + //fprintf(stderr,"l%d write: %u\n",level,buffer_write_nbytes[level]); + buffer_write_n[level] = 0; + buffer_write_pos[level] = buffer_write[level]; + memset(buffer_write_pos[level],0,buffer_write_nbytes[level]); + stats.tx_bytes += buffer_write_nbytes[level]; + buffer_write_nbytes[level] = 0; + return 2; +} + +int ConnectionMultiApproxBatch::add_get_op_to_queue(Operation *pop, int level) { + + op_queue[level][pop->opaque] = pop; + op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,pop->key,pop->valuelen,level,pop->flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + uint16_t keylen = strlen(pop->key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_GET, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + //if (quiet) { + // h.opcode = CMD_GETQ; + //} + h.opaque = htonl(pop->opaque); + + memcpy(buffer_write_pos[level], &h, 24); + buffer_write_pos[level] += 24; + memcpy(buffer_write_pos[level], pop->key, keylen); + buffer_write_pos[level] += keylen; + buffer_write_n[level]++; + buffer_write_nbytes[level] += 24 + keylen; + + int res = 1; + if (buffer_write_n[level] == (uint32_t)options.depth) { + res = send_write_buffer(level); + } + return res; +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApproxBatch::issue_get_with_len(Operation *pop, double now, bool quiet, uint32_t flags, Operation *l1) { + + int level = FLAGS_level(flags); + + //initialize op for sending +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + pop->opaque = opaque[level]++; + pop->flags = flags; + if (l1 != NULL) { + pop->l1 = l1; + } + + //put op into queue + return add_get_op_to_queue(pop,level); +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApproxBatch::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1) { + + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + strncpy(pop->key,key,255); + pop->valuelen = valuelen; + pop->type = Operation::GET; + pop->opaque = opaque[level]++; + pop->flags = flags; + pop->clsid = get_class(valuelen,strlen(key)); + + if (l1 != NULL) { + pop->l1 = l1; + } + + return add_get_op_to_queue(pop,level); + +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApproxBatch::issue_touch(const char* key, int valuelen, double now, int flags) { + + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + strncpy(pop->key,key,255); + pop->valuelen = valuelen; + pop->type = Operation::TOUCH; + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + +#ifdef DEBUGS + fprintf(stderr,"issing touch: %s, size: %u, level %d, flags: %d, opaque: %d\n",key,valuelen,level,flags,pop->opaque); +#endif + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_TOUCH, htons(keylen), + 0x04, 0x00, htons(0), + htonl(keylen + 4) }; + h.opaque = htonl(pop->opaque); + + uint32_t exp = 0; + if (flags & ITEM_DIRTY) { + exp = htonl(flags); + } + + memcpy(buffer_write_pos[level], &h, 24); + buffer_write_pos[level] += 24; + memcpy(buffer_write_pos[level], &exp, 4); + buffer_write_pos[level] += 4; + memcpy(buffer_write_pos[level], pop->key, keylen); + buffer_write_pos[level] += keylen; + buffer_write_nbytes[level] += 24 + keylen + 4; + buffer_write_n[level]++; + + int ret = 1; + if (buffer_write_n[level] == (uint32_t)options.depth) { + ret = send_write_buffer(level); + } + + return ret; +} + +/** + * Issue a delete request to the server. + */ +int ConnectionMultiApproxBatch::issue_delete(const char* key, double now, uint32_t flags) { + + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + strncpy(pop->key,key,255); + pop->type = Operation::DELETE; + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing delete: %s, level %d, flags: %d, opaque: %d\n",cid,key,level,flags,pop->opaque); +#endif + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_DELETE, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + h.opaque = htonl(pop->opaque); + + memcpy(buffer_write_pos[level], &h, 24); + buffer_write_pos[level] += 24; + memcpy(buffer_write_pos[level], pop->key, keylen); + buffer_write_pos[level] += keylen; + buffer_write_n[level]++; + buffer_write_nbytes[level] += 24 + keylen; + + int ret = 1; + if (buffer_write_n[level] == (uint32_t)options.depth) { + ret = send_write_buffer(level); + } + + return ret; +} + +int ConnectionMultiApproxBatch::issue_noop(int level) { + + binary_header_t h = { 0x80, CMD_NOOP, 0x0000, + 0x00, 0x00, htons(0), + 0x00 }; + memcpy(buffer_write_pos[level], &h, 24); + buffer_write_pos[level] += 24; + + buffer_write_n[level]++; + buffer_write_nbytes[level] += 24; + + int ret = 1; + if (buffer_write_n[level] == (uint32_t)options.depth) { + ret = send_write_buffer(level); + } + + return ret; +} + +int ConnectionMultiApproxBatch::add_set_to_queue(Operation *pop, int level, const char* value) { + int length = pop->valuelen; + + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,pop->key,length,level,pop->flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + uint16_t keylen = strlen(pop->key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_SET, htons(keylen), + 0x08, 0x00, htons(0), + htonl(keylen + 8 + length) }; + h.opaque = htonl(pop->opaque); + + uint32_t f = htonl(pop->flags); + uint32_t exp = 0; + + memcpy(buffer_write_pos[level], &h, 24); + buffer_write_pos[level] += 24; + memcpy(buffer_write_pos[level], &f, 4); + buffer_write_pos[level] += 4; + memcpy(buffer_write_pos[level], &exp, 4); + buffer_write_pos[level] += 4; + memcpy(buffer_write_pos[level], pop->key, keylen); + buffer_write_pos[level] += keylen; + memcpy(buffer_write_pos[level], value, length); + buffer_write_pos[level] += length; + buffer_write_n[level]++; + buffer_write_nbytes[level] += length + 32 + keylen; + + int ret = 1; + if (buffer_write_n[level] == (uint32_t)options.depth) { + ret = send_write_buffer(level); + } + return ret; + +} + +/** + * Issue a set request to the server. + */ +int ConnectionMultiApproxBatch::issue_set(Operation *pop, const char* value, double now, uint32_t flags) { + + int level = FLAGS_level(flags); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) pop->start_time = get_time(); + else pop->start_time = now; +#endif + + pop->opaque = opaque[level]++; + pop->flags = flags; + return add_set_to_queue(pop,level,value); + +} + +/** + * Issue a set request to the server. + */ +int ConnectionMultiApproxBatch::issue_set(const char* key, const char* value, int length, double now, uint32_t flags) { + + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) pop->start_time = get_time(); + else pop->start_time = now; +#endif + + strncpy(pop->key,key,255); + pop->valuelen = length; + pop->type = Operation::SET; + pop->opaque = opaque[level]++; + pop->flags = flags; + pop->clsid = get_class(length,strlen(key)); + + return add_set_to_queue(pop,level,value); + +} + + +/** + * Finish up (record stats) an operation that just returned from the + * server. + */ +void ConnectionMultiApproxBatch::finish_op(Operation *op, int was_hit) { + double now; +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); +#else + now = get_time(); +#endif +#if HAVE_CLOCK_GETTIME + op->end_time = get_time_accurate(); +#else + op->end_time = now; +#endif + + if (was_hit) { + switch (op->type) { + case Operation::GET: + switch (OP_level(op)) { + case 1: + stats.log_get_l1(*op); + break; + case 2: + stats.log_get_l2(*op); + if (op->l1 != NULL) { + op->l1->end_time = now; + stats.log_get(*(op->l1)); + } + break; + } + break; + case Operation::SET: + switch (OP_level(op)) { + case 1: + stats.log_set_l1(*op); + break; + case 2: + stats.log_set_l2(*op); + break; + } + break; + case Operation::DELETE: break; + case Operation::TOUCH: break; + default: DIE("Not implemented."); + } + } + + last_rx = now; + uint8_t level = OP_level(op); + if (op->l1 != NULL) { + //delete op_queue[1][op->l1->opaque]; + op_queue[1][op->l1->opaque] = 0; + op_queue_size[1]--; + delete op->l1; + } + //op_queue[level].erase(op_queue[level].begin()+opopq); + if (op == op_queue[level][op->opaque] && + op->opaque == op_queue[level][op->opaque]->opaque) { + //delete op_queue[level][op->opaque]; + op_queue[level][op->opaque] = 0; + delete op; + } else { + fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", + op,op_queue[level][op->opaque],op->opaque,op_queue[level][op->opaque]->opaque); + } + op_queue_size[level]--; + read_state = IDLE; + +} + + + +/** + * Check if our testing is done and we should exit. + */ +bool ConnectionMultiApproxBatch::check_exit_condition(double now) { + if (eof && op_queue_size[1] == 0 && op_queue_size[2] == 0) { + return true; + } + if (read_state == INIT_READ) return false; + + return false; +} + +/** + * Handle new connection and error events. + */ +void ConnectionMultiApproxBatch::event_callback1(short events) { + if (events & BEV_EVENT_CONNECTED) { + D("Connected to %s:%s.", hostname1.c_str(), port.c_str()); + int fd = bufferevent_getfd(bev1); + if (fd < 0) DIE("bufferevent_getfd"); + + if (!options.no_nodelay && !options.unix_socket) { + int one = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (void *) &one, sizeof(one)) < 0) + DIE("setsockopt()"); + } +#ifdef DEBUGMC + fprintf(stderr,"libevent connected %s, fd: %u\n",hostname1.c_str(),bufferevent_getfd(bev1)); +#endif + + + } else if (events & BEV_EVENT_ERROR) { + int err = bufferevent_socket_get_dns_error(bev1); + //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); + if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); + fprintf(stderr,"CID: %d - Got an error: %s\n",this->cid, + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); + + //DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + + } else if (events & BEV_EVENT_EOF) { + fprintf(stderr,"Unexpected EOF from server."); + return; + } +} + +/** + * Handle new connection and error events. + */ +void ConnectionMultiApproxBatch::event_callback2(short events) { + if (events & BEV_EVENT_CONNECTED) { + D("Connected to %s:%s.", hostname2.c_str(), port.c_str()); + int fd = bufferevent_getfd(bev2); + if (fd < 0) DIE("bufferevent_getfd"); + + if (!options.no_nodelay && !options.unix_socket) { + int one = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (void *) &one, sizeof(one)) < 0) + DIE("setsockopt()"); + } +#ifdef DEBUGMC + fprintf(stderr,"libevent connected %s, fd: %u\n",hostname2.c_str(),bufferevent_getfd(bev2)); +#endif + + + } else if (events & BEV_EVENT_ERROR) { + int err = bufferevent_socket_get_dns_error(bev2); + //if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); + if (err) fprintf(stderr,"DNS error: %s", evutil_gai_strerror(err)); + fprintf(stderr,"CID: %d - Got an error: %s\n",this->cid, + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); + + //DIE("BEV_EVENT_ERROR: %s", strerror(errno)); + + + } else if (events & BEV_EVENT_EOF) { + fprintf(stderr,"Unexpected EOF from server."); + return; + } +} + +/** + * Request generation loop. Determines whether or not to issue a new command, + * based on timer events. + * + * Note that this function loops. Be wary of break vs. return. + */ +void ConnectionMultiApproxBatch::drive_write_machine(double now) { + + if (now == 0.0) now = get_time(); + double delay; + struct timeval tv; + + int max_depth = (int)options.depth*2; + if (check_exit_condition(now)) { + return; + } + + while (1) { + switch (write_state) { + case INIT_WRITE: + delay = iagen->generate(); + next_time = now + delay; + double_to_tv(delay, &tv); + evtimer_add(timer, &tv); + write_state = ISSUING; + break; + + case ISSUING: + if ( (op_queue_size[1] >= (size_t) max_depth) || + (op_queue_size[2] >= (size_t) max_depth) ) { + write_state = WAITING_FOR_OPQ; + break; + } + + if (options.getsetorset) { + int ret = issue_getsetorset(now); + if (ret == 1) return; //if at EOF + } + + last_tx = now; + for (int i = 1; i <= 2; i++) { + stats.log_op(op_queue_size[i]); + } + break; + + case WAITING_FOR_TIME: + write_state = ISSUING; + break; + + case WAITING_FOR_OPQ: + if ( (op_queue_size[1] >= (size_t) max_depth) || + (op_queue_size[2] >= (size_t) max_depth) ) { + double delay = 0.01; + struct timeval tv; + double_to_tv(delay, &tv); + evtimer_add(timer, &tv); + return; + } else { + write_state = ISSUING; + break; + } + + default: DIE("Not implemented"); + } + } +} + +size_t ConnectionMultiApproxBatch::handle_response_batch(unsigned char *rbuf_pos, resp_t *resp, + size_t read_bytes, size_t consumed_bytes, + int level, int extra) { + if (rbuf_pos[0] != 129) { + fprintf(stderr,"we don't have a valid header %u\n",rbuf_pos[0]); + //buffer_read_pos[level] = rbuf_pos; + //buffer_read_n[level] = 1; + return 0; + } + binary_header_t* h = reinterpret_cast(rbuf_pos); + uint32_t bl = ntohl(h->body_len); + uint16_t kl = ntohs(h->key_len); + uint8_t el = h->extra_len; + int targetLen = 24 + bl; + if (consumed_bytes + targetLen > (read_bytes+extra)) { + size_t need = (consumed_bytes+targetLen) - (read_bytes+extra); + size_t have = (read_bytes+extra) - (consumed_bytes); + //fprintf(stderr,"we don't have enough data, need %lu more bytes (targetLen: %d)\n",need,targetLen); + buffer_read_pos[level] = buffer_read[level]; //reset to begining of buffer + memcpy(buffer_read_pos[level],rbuf_pos,have); + buffer_read_n[level] = have; + return 0; + } + + resp->opcode = h->opcode; + resp->opaque = ntohl(h->opaque); + uint16_t status = ntohs(h->status); +#ifdef DEBUGMC + fprintf(stderr,"cid: %d handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",cid,level, + h->opcode,ntohl(h->opaque),ntohs(h->key_len),h->extra_len, + ntohl(h->body_len),ntohs(h->status)); +#endif + // If something other than success, count it as a miss + if (resp->opcode == CMD_GET && status == RESP_NOT_FOUND) { + switch(level) { + case 1: + stats.get_misses_l1++; + break; + case 2: + stats.get_misses_l2++; + stats.get_misses++; + stats.window_get_misses++; + break; + } + resp->found = false; + } else if (resp->opcode == CMD_SET && kl > 0) { + //first data is extras: clsid, flags, eflags + if (resp->evict) { + unsigned char *buf = rbuf_pos + 24; + resp->evict->clsid = *((uint32_t*)buf); + resp->evict->clsid = ntohl(resp->evict->clsid); + buf += 4; + + resp->evict->serverFlags = *((uint32_t*)buf); + resp->evict->serverFlags = ntohl(resp->evict->serverFlags); + buf += 4; + + resp->evict->evictedFlags = *((uint32_t*)buf); + resp->evict->evictedFlags = ntohl(resp->evict->evictedFlags); + buf += 4; + + resp->evict->evictedKeyLen = kl; + resp->evict->evictedKey = (char*)malloc(kl+1); + memset(resp->evict->evictedKey,0,kl+1); + memcpy(resp->evict->evictedKey,buf,kl); + buf += kl; + + resp->evict->evictedLen = bl - kl - el; + resp->evict->evictedData = (char*)malloc(resp->evict->evictedLen); + memcpy(resp->evict->evictedData,buf,resp->evict->evictedLen); + resp->evict->evicted = true; + } + } else if ( (resp->opcode == CMD_DELETE || resp->opcode == CMD_TOUCH) && + status == RESP_NOT_FOUND) { + resp->found = false; + } + this->stats.rx_bytes += targetLen; + return targetLen; +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMultiApproxBatch::read_callback1() { + + int level = 1; + + int extra = buffer_read_n[level]; + buffer_read_n[level] = 0; + + unsigned char* rbuf_pos = buffer_read[level]; + + size_t read_bytes = bufferevent_read(bev1, rbuf_pos+extra, buffer_size_); + //fprintf(stderr,"l1 read: %lu\n",read_bytes); + size_t consumed_bytes = 0; + size_t batch = options.depth; + //we have at least some data to read + size_t nread_ops = 0; + while (1) { + evicted_t *evict = (evicted_t*)malloc(sizeof(evicted_t)); + memset(evict,0,sizeof(evicted_t)); + resp_t mc_resp; + mc_resp.found = true; + mc_resp.evict = evict; + size_t cbytes = handle_response_batch(rbuf_pos,&mc_resp,read_bytes,consumed_bytes,level,extra); + if (cbytes == 0) { + break; + } + rbuf_pos = rbuf_pos + cbytes; + consumed_bytes += cbytes; + uint32_t opaque = mc_resp.opaque; + bool found = mc_resp.found; + + Operation *op = op_queue[level][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + + double now = get_time(); + int wb = 0; + if (options.rand_admit) { + wb = (rand() % options.rand_admit); + } + int vl = op->valuelen; + int flags = OP_clu(op); + switch (op->type) { + case Operation::GET: + if ( !found && (options.getset || options.getsetorset) ) { + /* issue a get a l2 */ + issue_get_with_len(op->key,vl,now,false, flags | SRC_L1_M | ITEM_L2 | LOG_OP, op); + op->end_time = now; + this->stats.log_get_l1(*op); + + } else { + if (OP_incl(op) && ghits >= gloc) { + issue_touch(op->key,vl,now, ITEM_L2 | SRC_L1_H); + gloc += rand()%(10*2-1)+1; + } + ghits++; + finish_op(op,1); + } + break; + case Operation::SET: + //if (OP_src(op) == SRC_L1_COPY || + // OP_src(op) == SRC_L2_M) { + // del_copy_keys(string(op->key)); + //} + if (evict->evicted) { + string wb_key(evict->evictedKey); + if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { + //int ret = add_to_wb_keys(wb_key); + //if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_WB | ITEM_DIRTY); + //} + this->stats.incl_wbs++; + } else if (evict->evictedFlags & ITEM_EXCL) { + //fprintf(stderr,"excl writeback %s\n",evict->evictedKey); + //strncpy(wb_key,evict->evictedKey,255); + if ( (options.rand_admit && wb == 0) || + (options.threshold && (g_key_hist[wb_key] == 1)) || + (options.wb_all) ) { + //int ret = add_to_wb_keys(wb_key); + //if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + //} + this->stats.excl_wbs++; + } + } + if (OP_src(op) == SRC_DIRECT_SET) { + if ( (evict->serverFlags & ITEM_SIZE_CHANGE) || ((evict->serverFlags & ITEM_WAS_HIT) == 0)) { + this->stats.set_misses_l1++; + } else if (OP_excl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_excl_hits_l1++; + } else if (OP_incl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_incl_hits_l1++; + } + } + } + finish_op(op,1); + break; + case Operation::TOUCH: + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); + DIE("not implemented"); + } + + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + nread_ops++; + if (rbuf_pos[0] != 129) { + break; + } + } + //if (buffer_read_n[level] == 0) { + // memset(buffer_read[level],0,read_bytes); + //} + if (nread_ops == 0) { + fprintf(stderr,"ugh only got: %lu ops expected %lu, read %lu, cid %u\n",nread_ops,batch,read_bytes,cid); + } + + + double now = get_time(); + if (check_exit_condition(now)) { + return; + } + + last_tx = now; + stats.log_op(op_queue_size[1]); + stats.log_op(op_queue_size[2]); + drive_write_machine(); + +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMultiApproxBatch::read_callback2() { + int level = 2; + + int extra = buffer_read_n[level]; + buffer_read_n[level] = 0; + unsigned char* rbuf_pos = buffer_read[level]; + + + size_t read_bytes = bufferevent_read(bev2, rbuf_pos+extra,buffer_size_); + //fprintf(stderr,"l2 read: %lu\n",read_bytes); + size_t consumed_bytes = 0; + size_t batch = options.depth; + size_t nread_ops = 0; + while (1) { + evicted_t *evict = NULL; + resp_t mc_resp; + mc_resp.found = true; + mc_resp.evict = evict; + size_t cbytes = handle_response_batch(rbuf_pos,&mc_resp,read_bytes,consumed_bytes,level,extra); + if (cbytes == 0) { + break; + } + rbuf_pos = rbuf_pos + cbytes; + consumed_bytes += cbytes; + uint32_t opaque = mc_resp.opaque; + bool found = mc_resp.found; + + Operation *op = op_queue[level][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + double now = get_time(); + switch (op->type) { + case Operation::GET: + if ( !found && (options.getset || options.getsetorset) ) { + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | SRC_L2_M | LOG_OP; + //int ret = add_to_copy_keys(string(op->key)); + //if (ret == 1) { + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L1); + if (OP_incl(op)) { + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L2); + } + //} + finish_op(op,0); // sets read_state = IDLE + } else { + if (found) { + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; + string key = string(op->key); + const char *data = &random_char[index]; + //int ret = add_to_copy_keys(string(op->key)); + //if (ret == 1) { + issue_set(op->key,data,valuelen, now, flags); + //} + this->stats.copies_to_l1++; + //djb: this is automatically done in the L2 server + //if (OP_excl(op)) { //djb: todo should we delete here for approx or just let it die a slow death? + // issue_delete(op->key,now, ITEM_L2 | SRC_L1_COPY ); + //} + finish_op(op,1); + + } else { + finish_op(op,0); + } + } + break; + case Operation::SET: + //if (OP_src(op) == SRC_WB) { + // del_wb_keys(string(op->key)); + //} + finish_op(op,1); + break; + case Operation::TOUCH: + if (OP_src(op) == SRC_DIRECT_SET || SRC_L1_H) { + int valuelen = op->valuelen; + if (!found) { + int index = lrand48() % (1024 * 1024); + issue_set(op->key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP | SRC_L2_M); + this->stats.set_misses_l2++; + } else { + if (OP_src(op) == SRC_DIRECT_SET) { + issue_touch(op->key,valuelen,now, ITEM_L1 | SRC_L2_H | ITEM_DIRTY); + } + } + //del_touch_keys(string(op->key)); + } + finish_op(op,0); + break; + case Operation::DELETE: + //check to see if it was a hit + //fprintf(stderr," del %s -- %d from %d\n",op->key.c_str(),found,OP_src(op)); + if (OP_src(op) == SRC_DIRECT_SET) { + if (found) { + this->stats.delete_hits_l2++; + } else { + this->stats.delete_misses_l2++; + } + } + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); + DIE("not implemented"); + } + nread_ops++; + if (rbuf_pos[0] != 129) { + break; + } + } + //if (buffer_read_n[level] == 0) { + // memset(buffer_read[level],0,read_bytes); + //} + if (nread_ops == 0) { + fprintf(stderr,"ugh l2 only got: %lu ops expected %lu\n",nread_ops,batch); + } + + + double now = get_time(); + if (check_exit_condition(now)) { + return; + } + + last_tx = now; + stats.log_op(op_queue_size[2]); + stats.log_op(op_queue_size[1]); + drive_write_machine(); + +} + +/** + * Callback called when write requests finish. + */ +void ConnectionMultiApproxBatch::write_callback() { + + //fprintf(stderr,"loaded evbuffer with ops: %u\n",op_queue.size()); +} + +/** + * Callback for timer timeouts. + */ +void ConnectionMultiApproxBatch::timer_callback() { + //fprintf(stderr,"timer up: %d\n",cid); + drive_write_machine(); +} + + +/* The follow are C trampolines for libevent callbacks. */ +void bev_event_cb1_approx_batch(struct bufferevent *bev, short events, void *ptr) { + + ConnectionMultiApproxBatch* conn = (ConnectionMultiApproxBatch*) ptr; + conn->event_callback1(events); +} + +/* The follow are C trampolines for libevent callbacks. */ +void bev_event_cb2_approx_batch(struct bufferevent *bev, short events, void *ptr) { + + ConnectionMultiApproxBatch* conn = (ConnectionMultiApproxBatch*) ptr; + conn->event_callback2(events); +} + +void bev_read_cb1_approx_batch(struct bufferevent *bev, void *ptr) { + ConnectionMultiApproxBatch* conn = (ConnectionMultiApproxBatch*) ptr; + conn->read_callback1(); +} + + +void bev_read_cb2_approx_batch(struct bufferevent *bev, void *ptr) { + ConnectionMultiApproxBatch* conn = (ConnectionMultiApproxBatch*) ptr; + conn->read_callback2(); +} + +void bev_write_cb_m_approx_batch(struct bufferevent *bev, void *ptr) { +} + +void timer_cb_m_approx_batch(evutil_socket_t fd, short what, void *ptr) { + ConnectionMultiApproxBatch* conn = (ConnectionMultiApproxBatch*) ptr; + conn->timer_callback(); +} + diff --git a/cmdline.ggo b/cmdline.ggo index 3fba6bb..9580e00 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -15,6 +15,7 @@ option "server" s "Memcached server hostname[:port]. \ Repeat to specify multiple servers." string multiple option "unix_socket" - "Use UNIX socket instead of TCP." option "approx" - "approximate two level caching with inclusive/exclusive" +option "approx_batch" - "approximate two level caching with inclusive/exclusive and batching of reqs" option "inclusives" - "give a list of 1 == inclusive, 2 == exclusives for each class" string default="" option "binary" - "Use binary memcached protocol instead of ASCII." option "redis" - "Use Redis RESP protocol instead of memchached." diff --git a/mutilate.cc b/mutilate.cc index 7ae5982..053eafd 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -1629,7 +1629,7 @@ void do_mutilate(const vector& servers, options_t& options, event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); - } else if (servers.size() == 2 && !args.approx_given) { + } else if (servers.size() == 2 && ! ( args.approx_given || args.approx_batch_given)) { vector connections; vector server_lead; @@ -1786,7 +1786,7 @@ void do_mutilate(const vector& servers, options_t& options, event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); - } else if (servers.size() == 2 && args.approx_given) { + } else if (servers.size() == 2 && args.approx_given && !args.approx_batch_given) { vector connections; vector server_lead; @@ -1940,6 +1940,164 @@ void do_mutilate(const vector& servers, options_t& options, stats.start = start; stats.stop = now; + event_config_free(config); + evdns_base_free(evdns, 0); + event_base_free(base); + + } else if (servers.size() == 2 && args.approx_batch_given) { + vector connections; + vector server_lead; + + string hostname1 = servers[0]; + string hostname2 = servers[1]; + string port = "11211"; + + int conns = args.measure_connections_given ? args.measure_connections_arg : + options.connections; + + srand(time(NULL)); + for (int c = 0; c < conns; c++) { + + int fd1 = -1; + + if ( (fd1 = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket error"); + exit(-1); + } + + struct sockaddr_un sin1; + memset(&sin1, 0, sizeof(sin1)); + sin1.sun_family = AF_LOCAL; + strcpy(sin1.sun_path, hostname1.c_str()); + + fcntl(fd1, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state */ + int addrlen; + addrlen = sizeof(sin1); + + int max_tries = 50; + int n_tries = 0; + int s = 10; + while (connect(fd1, (struct sockaddr*)&sin1, addrlen) == -1) { + perror("l1 connect error"); + if (n_tries++ > max_tries) { + fprintf(stderr,"conn l1 %d unable to connect after sleep for %d\n",c+1,s); + exit(-1); + } + int d = s + rand() % 100; + usleep(d); + s = (int)((double)s*1.25); + } + + int fd2 = -1; + if ( (fd2 = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("l2 socket error"); + exit(-1); + } + struct sockaddr_un sin2; + memset(&sin2, 0, sizeof(sin2)); + sin2.sun_family = AF_LOCAL; + strcpy(sin2.sun_path, hostname2.c_str()); + fcntl(fd2, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state */ + addrlen = sizeof(sin2); + n_tries = 0; + s = 10; + while (connect(fd2, (struct sockaddr*)&sin2, addrlen) == -1) { + perror("l2 connect error"); + if (n_tries++ > max_tries) { + fprintf(stderr,"conn l2 %d unable to connect after sleep for %d\n",c+1,s); + exit(-1); + } + int d = s + rand() % 100; + usleep(d); + s = (int)((double)s*1.25); + } + + + ConnectionMultiApproxBatch* conn = new ConnectionMultiApproxBatch(base, evdns, + hostname1, hostname2, port, options,args.agentmode_given ? false : true, fd1, fd2); + + int connected = 0; + if (conn) { + connected = 1; + } + int cid = conn->get_cid(); + + if (connected) { + fprintf(stderr,"cid %d gets l1 fd %d l2 fd %d\n",cid,fd1,fd2); + fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front()->key); + if (g_lock != NULL) { + conn->set_g_wbkeys(g_wb_keys); + conn->set_lock(g_lock); + } + conn->set_queue(trace_queue->at(cid)); + connections.push_back(conn); + } else { + fprintf(stderr,"conn multi: %d, not connected!!\n",c); + + } + } + + // wait for all threads to reach here + pthread_barrier_wait(&barrier); + + fprintf(stderr,"thread %ld gtg\n",pthread_self()); + // Wait for all Connections to become IDLE. + while (1) { + // FIXME: If all connections become ready before event_base_loop + // is called, this will deadlock. + event_base_loop(base, EVLOOP_ONCE); + + bool restart = false; + for (ConnectionMultiApproxBatch *conn: connections) + if (!conn->is_ready()) restart = true; + + if (restart) continue; + else break; + } + + + + double start = get_time(); + double now = start; + for (ConnectionMultiApproxBatch *conn: connections) { + conn->start_time = start; + conn->start(); // Kick the Connection into motion. + } + //fprintf(stderr,"Start = %f\n", start); + + // Main event loop. + while (1) { + event_base_loop(base, loop_flag); + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); + + bool restart = false; + for (ConnectionMultiApproxBatch *conn: connections) { + if (!conn->check_exit_condition(now)) { + restart = true; + } + } + if (restart) continue; + else break; + + } + + + // V("Start = %f", start); + + if (master && !args.scan_given && !args.search_given) + V("stopped at %f options.time = %d", get_time(), options.time); + + // Tear-down and accumulate stats. + for (ConnectionMultiApproxBatch *conn: connections) { + stats.accumulate(conn->stats); + delete conn; + } + + stats.start = start; + stats.stop = now; + event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); From 9e7fa3d1e94038b2e012de6b9bbf5955e2b6bcd2 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 25 May 2022 18:43:34 -0400 Subject: [PATCH 53/57] shm --- Connection.h | 163 ++++ ConnectionMultiApprox.cc | 8 +- ConnectionMultiApproxBatch.cc | 780 ++++++++++++--- ConnectionMultiApproxShm.cc | 1726 +++++++++++++++++++++++++++++++++ ConnectionOptions.h | 1 + SConstruct | 6 +- binary_protocol.h | 3 + bipbuffer.cc | 180 ++++ bipbuffer.h | 92 ++ cmdline.ggo | 2 + mutilate.cc | 65 +- 11 files changed, 2899 insertions(+), 127 deletions(-) create mode 100644 ConnectionMultiApproxShm.cc create mode 100644 bipbuffer.cc create mode 100644 bipbuffer.h diff --git a/Connection.h b/Connection.h index 81ba7b5..f06fd13 100644 --- a/Connection.h +++ b/Connection.h @@ -13,6 +13,7 @@ #include #include +#include "bipbuffer.h" #include "AdaptiveSampler.h" #include "cmdline.h" #include "ConnectionOptions.h" @@ -534,6 +535,8 @@ class ConnectionMultiApproxBatch { void event_callback2(short events); void read_callback1(); void read_callback2(); + void read_callback1_v1(); + void read_callback2_v1(); // event callbacks void write_callback(); void timer_callback(); @@ -623,6 +626,8 @@ class ConnectionMultiApproxBatch { unsigned char* buffer_read[MAX_LEVELS]; unsigned char* buffer_write_pos[MAX_LEVELS]; unsigned char* buffer_read_pos[MAX_LEVELS]; + unsigned char* buffer_lasthdr[MAX_LEVELS]; + unsigned char* buffer_leftover[MAX_LEVELS]; uint32_t buffer_read_n[MAX_LEVELS]; uint32_t buffer_write_n[MAX_LEVELS]; uint32_t buffer_read_nbytes[MAX_LEVELS]; @@ -654,6 +659,7 @@ class ConnectionMultiApproxBatch { void issue_sasl(); int issue_op(Operation* op); int issue_noop(int level = 1); + size_t fill_read_buffer(int level, int *extra); int issue_touch(const char* key, int valuelen, double now, int level); int issue_delete(const char* key, double now, uint32_t flags); int issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); @@ -675,4 +681,161 @@ class ConnectionMultiApproxBatch { bool consume_resp_line(evbuffer *input, bool &done); }; +class ConnectionMultiApproxShm { +public: + ConnectionMultiApproxShm(options_t options, bool sampling = true); + + ~ConnectionMultiApproxShm(); + + int do_connect(); + + double start_time; // Time when this connection began operations. + ConnectionStats stats; + options_t options; + + bool is_ready() { return read_state == IDLE; } + void set_priority(int pri); + + void start_loading(); + void reset(); + bool check_exit_condition(double now = 0.0); + + void event_callback1(short events); + void event_callback2(short events); + void read_callback1(); + void read_callback2(); + void read_callback1_v1(); + void read_callback2_v1(); + // event callbacks + void write_callback(); + void timer_callback(); + + int eof; + uint32_t get_cid(); + //void set_queue(ConcurrentQueue *a_trace_queue); + int add_to_wb_keys(string wb_key); + int add_to_copy_keys(string key); + int add_to_touch_keys(string key); + void del_wb_keys(string wb_key); + void del_copy_keys(string key); + void del_touch_keys(string key); + void set_g_wbkeys(unordered_map> *a_wb_keys); + void set_queue(queue *a_trace_queue); + void set_lock(pthread_mutex_t* a_lock); + int send_write_buffer(int level); + int add_get_op_to_queue(Operation *pop, int level); + int add_set_to_queue(Operation *pop, int level, const char *value); + size_t handle_response_batch(unsigned char *rbuf_pos, resp_t *resp, + size_t read_bytes, size_t consumed_bytes, + int level, int extra); + void drive_write_machine_shm(double now = 0.0); + bipbuf_t* bipbuf_in[3]; + bipbuf_t* bipbuf_out[3]; + pthread_mutex_t* lock_in[3]; + pthread_mutex_t* lock_out[3]; + +private: + string hostname1; + string hostname2; + string port; + + double o_percent; + int trace_queue_n; + + struct event *timer; // Used to control inter-transmission time. + double next_time; // Inter-transmission time parameters. + double last_rx; // Used to moderate transmission rate. + double last_tx; + + enum read_state_enum { + INIT_READ, + CONN_SETUP, + LOADING, + IDLE, + WAITING_FOR_GET, + WAITING_FOR_SET, + WAITING_FOR_DELETE, + MAX_READ_STATE, + }; + + enum write_state_enum { + INIT_WRITE, + ISSUING, + WAITING_FOR_TIME, + WAITING_FOR_OPQ, + MAX_WRITE_STATE, + }; + + read_state_enum read_state; + write_state_enum write_state; + + // Parameters to track progress of the data loader. + int loader_issued, loader_completed; + + uint32_t *opaque; + int *issue_buf_size; + int *issue_buf_n; + unsigned char **issue_buf_pos; + unsigned char **issue_buf; + bool last_quiet1; + bool last_quiet2; + uint32_t total; + uint32_t cid; + uint32_t gets; + uint32_t gloc; + uint32_t ghits; + uint32_t sloc; + uint32_t esets; + uint32_t isets; + uint32_t iloc; + + + //std::vector> op_queue; + Operation ***op_queue; + uint32_t *op_queue_size; + + + Generator *valuesize; + Generator *keysize; + KeyGenerator *keygen; + Generator *iagen; + pthread_mutex_t* lock; + unordered_map> *g_wb_keys; + queue *trace_queue; + + // state machine functions / event processing + void pop_op(Operation *op); + void output_op(Operation *op, int type, bool was_found); + //void finish_op(Operation *op); + void finish_op(Operation *op,int was_hit); + int issue_getsetorset(double now = 0.0); + + // request functions + void issue_sasl(); + int issue_op(Operation* op); + void issue_noop(int level = 1); + size_t fill_read_buffer(int level, int *extra); + int issue_touch(const char* key, int valuelen, double now, int level); + int issue_delete(const char* key, double now, uint32_t flags); + int issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); + int issue_get_with_len(Operation *pop, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); + int issue_set(const char* key, const char* value, int length, double now, uint32_t flags); + int issue_set(Operation *pop, const char* value, double now, uint32_t flags); + + int read_response_l1(); + void read_response_l2(); + // protocol fucntions + int set_request_ascii(const char* key, const char* value, int length); + int set_request_binary(const char* key, const char* value, int length); + int set_request_resp(const char* key, const char* value, int length); + + int get_request_ascii(const char* key); + int get_request_binary(const char* key); + int get_request_resp(const char* key); + + bool consume_binary_response(evbuffer *input); + bool consume_ascii_line(evbuffer *input, bool &done); + bool consume_resp_line(evbuffer *input, bool &done); +}; + #endif diff --git a/ConnectionMultiApprox.cc b/ConnectionMultiApprox.cc index 40b071f..7ee052a 100644 --- a/ConnectionMultiApprox.cc +++ b/ConnectionMultiApprox.cc @@ -640,10 +640,9 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { // Operation *Op = trace_queue->front(); - trace_queue->pop(); //Operation *Op = g_trace_queue.dequeue(); - if (Op->type == Operation::SASL) { + if (Op == NULL || trace_queue->size() <= 0 || Op->type == Operation::SASL) { eof = 1; cid_rate.insert( {cid, 100 } ); fprintf(stderr,"cid %d done\n",cid); @@ -664,6 +663,7 @@ int ConnectionMultiApprox::issue_getsetorset(double now) { return 1; } + trace_queue->pop(); //trace_queue->pop(); @@ -906,11 +906,11 @@ int ConnectionMultiApprox::issue_touch(const char* key, int valuelen, double now pop->valuelen = valuelen; pop->type = Operation::TOUCH; pop->opaque = opaque[level]++; - pop->flags = flags; op_queue[level][pop->opaque] = pop; - //op_queue[level].push(op); op_queue_size[level]++; + pop->flags = flags; + if (opaque[level] > OPAQUE_MAX) { opaque[level] = 1; } diff --git a/ConnectionMultiApproxBatch.cc b/ConnectionMultiApproxBatch.cc index c26c406..16de236 100644 --- a/ConnectionMultiApproxBatch.cc +++ b/ConnectionMultiApproxBatch.cc @@ -240,7 +240,7 @@ ConnectionMultiApproxBatch::ConnectionMultiApproxBatch(struct event_base* _base, init_classes(); init_inclusives(options.inclusives); } - cid_rate.insert( { cid, 0 } ); + //cid_rate.insert( { cid, 0 } ); pthread_mutex_unlock(&cid_lock_m_approx_batch); @@ -294,12 +294,12 @@ ConnectionMultiApproxBatch::ConnectionMultiApproxBatch(struct event_base* _base, bev1 = bufferevent_socket_new(base, fd1, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev1, bev_read_cb1_approx_batch, bev_write_cb_m_approx_batch, bev_event_cb1_approx_batch, this); bufferevent_enable(bev1, EV_READ | EV_WRITE); - //bufferevent_setwatermark(bev1, EV_READ, 4*1024, 0); + //bufferevent_setwatermark(bev1, EV_READ, 512*1024, 0); bev2 = bufferevent_socket_new(base, fd2, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev2, bev_read_cb2_approx_batch, bev_write_cb_m_approx_batch, bev_event_cb2_approx_batch, this); bufferevent_enable(bev2, EV_READ | EV_WRITE); - //bufferevent_setwatermark(bev2, EV_READ, 4*1024, 0); + //bufferevent_setwatermark(bev2, EV_READ, 512*1024, 0); timer = evtimer_new(base, timer_cb_m_approx_batch, this); @@ -314,18 +314,22 @@ void ConnectionMultiApproxBatch::set_queue(queue* a_trace_queue) { incl_ = get_incl(Op->valuelen,strlen(Op->key)); clsid_ = get_class(Op->valuelen,strlen(Op->key)); - buffer_size_ = (1024*512)*options.depth; + buffer_size_ = 1024*1024*10; //setup the buffers //max is (valuelen + 256 + 24 + 4 + 4 ) * depth for (int i = 1; i <= LEVELS; i++) { - buffer_write[i] = (unsigned char*)malloc(buffer_size_); + buffer_write[i] = (unsigned char*)malloc(options.depth*512*1024); buffer_read[i] = (unsigned char*)malloc(buffer_size_); + buffer_leftover[i] = (unsigned char*)malloc(buffer_size_); + memset(buffer_read[i],0,buffer_size_); + memset(buffer_leftover[i],0,buffer_size_); buffer_write_n[i] = 0; buffer_read_n[i] = 0; buffer_write_nbytes[i] = 0; buffer_read_nbytes[i] = 0; buffer_write_pos[i] = buffer_write[i]; buffer_read_pos[i] = buffer_read[i]; + buffer_lasthdr[i] = 0; // buffer_read[i]; } } @@ -425,7 +429,7 @@ int ConnectionMultiApproxBatch::issue_op(Operation *Op) { issued = issue_get_with_len(Op, now, false, flags | LOG_OP | ITEM_L1); this->stats.gets++; gets++; - this->stats.gets_cid[cid]++; + //this->stats.gets_cid[cid]++; break; case Operation::SET: if (incl == 1) { @@ -447,7 +451,7 @@ int ConnectionMultiApproxBatch::issue_op(Operation *Op) { esets++; } this->stats.sets++; - this->stats.sets_cid[cid]++; + //this->stats.sets_cid[cid]++; break; case Operation::DELETE: case Operation::TOUCH: @@ -511,17 +515,20 @@ ConnectionMultiApproxBatch::~ConnectionMultiApproxBatch() { for (int i = 0; i <= LEVELS; i++) { free(op_queue[i]); - free(buffer_write[i]); - free(buffer_read[i]); + if (i > 0) { + free(buffer_write[i]); + free(buffer_read[i]); + } } free(op_queue_size); free(opaque); free(op_queue); - //event_free(timer); + event_free(timer); //timer = NULL; // FIXME: W("Drain op_q?"); + bufferevent_free(bev1); bufferevent_free(bev2); @@ -563,63 +570,61 @@ int ConnectionMultiApproxBatch::issue_getsetorset(double now) { Operation *Op = trace_queue->front(); if (Op->type == Operation::SASL) { - eof = 1; - cid_rate.insert( {cid, 100 } ); - fprintf(stderr,"cid %d done\n",cid); - string op_queue1; - string op_queue2; - for (int j = 0; j < 2; j++) { - for (int i = 0; i < OPAQUE_MAX; i++) { - if (op_queue[j+1][i] != NULL) { - if (j == 0) { - op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; - } else { - op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; - } - } + //cid_rate.insert( {cid, 100 } ); + //fprintf(stderr,"cid %d done before loop\n",cid); + //string op_queue1; + //string op_queue2; + //for (int j = 0; j < 2; j++) { + // for (int i = 0; i < OPAQUE_MAX; i++) { + // if (op_queue[j+1][i] != NULL) { + // if (j == 0) { + // op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + // } else { + // op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + // } + // } + // } + //} + for (int i = 1; i <= LEVELS; i++) { + if (buffer_write_n[i] > 0) { + send_write_buffer(i); } } - send_write_buffer(1); - send_write_buffer(2); - fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + eof = 1; + //fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); return 1; } - while (buffer_write_n[1] < (uint32_t)options.depth) { - int issued = issue_op(Op); - if (issued) { - trace_queue->pop(); - Op = trace_queue->front(); - - if (Op->type == Operation::SASL) { - eof = 1; - cid_rate.insert( {cid, 100 } ); - fprintf(stderr,"cid %d done\n",cid); - string op_queue1; - string op_queue2; - for (int j = 0; j < 2; j++) { - for (int i = 0; i < OPAQUE_MAX; i++) { - if (op_queue[j+1][i] != NULL) { - if (j == 0) { - op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; - } else { - op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; - } - } - } + int issued = issue_op(Op); + trace_queue->pop(); + while (issued != 2) { + Op = trace_queue->front(); + + if (Op->type == Operation::SASL) { + for (int i = 1; i <= LEVELS; i++) { + if (buffer_write_n[i] > 0) { + send_write_buffer(i); } - fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); - send_write_buffer(1); - send_write_buffer(2); - return 0; - } - total++; - if (issued == 2) { - return 0; } - } else { - fprintf(stderr,"failed to issue line: %s, vl: %d\n",Op->key,Op->valuelen); + //string op_queue1; + //string op_queue2; + //for (int j = 0; j < 2; j++) { + // for (int i = 0; i < OPAQUE_MAX; i++) { + // if (op_queue[j+1][i] != NULL) { + // if (j == 0) { + // op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + // } else { + // op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + // } + // } + // } + //} + //fprintf(stderr,"done in loop cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + eof = 1; + return 1; } + issued = issue_op(Op); + trace_queue->pop(); } return 0; @@ -1061,9 +1066,18 @@ void ConnectionMultiApproxBatch::finish_op(Operation *op, int was_hit) { uint8_t level = OP_level(op); if (op->l1 != NULL) { //delete op_queue[1][op->l1->opaque]; - op_queue[1][op->l1->opaque] = 0; - op_queue_size[1]--; - delete op->l1; + if (op->l1 == op_queue[1][op->l1->opaque]) { + op_queue[1][op->l1->opaque] = 0; + if (op_queue_size[1] > 0) { + op_queue_size[1]--; + } else { + fprintf(stderr,"chained op_Queue_size[%d] out of sync!!\n",1); + } + delete op->l1; + } else { + fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", + op,op_queue[1][op->opaque],op->opaque,op_queue[1][op->opaque]->opaque); + } } //op_queue[level].erase(op_queue[level].begin()+opopq); if (op == op_queue[level][op->opaque] && @@ -1071,11 +1085,15 @@ void ConnectionMultiApproxBatch::finish_op(Operation *op, int was_hit) { //delete op_queue[level][op->opaque]; op_queue[level][op->opaque] = 0; delete op; + if (op_queue_size[level] > 0) { + op_queue_size[level]--; + } else { + fprintf(stderr,"op_Queue_size[%d] out of sync!!\n",level); + } } else { fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", op,op_queue[level][op->opaque],op->opaque,op_queue[level][op->opaque]->opaque); } - op_queue_size[level]--; read_state = IDLE; } @@ -1086,11 +1104,20 @@ void ConnectionMultiApproxBatch::finish_op(Operation *op, int was_hit) { * Check if our testing is done and we should exit. */ bool ConnectionMultiApproxBatch::check_exit_condition(double now) { - if (eof && op_queue_size[1] == 0 && op_queue_size[2] == 0) { - return true; - } - if (read_state == INIT_READ) return false; - + if (eof == 1) { + int done = 1; + for (int i = 1; i <= LEVELS; i++) { + if (buffer_write_n[i] != 0) { + //fprintf(stderr,"%d sending %d\n",i,buffer_write_n[i]); + send_write_buffer(i); + done = 0; + } + } + if (done) { + //fprintf(stderr,"%d done - check exit\n",cid); + return true; + } + } return false; } @@ -1178,9 +1205,6 @@ void ConnectionMultiApproxBatch::drive_write_machine(double now) { struct timeval tv; int max_depth = (int)options.depth*2; - if (check_exit_condition(now)) { - return; - } while (1) { switch (write_state) { @@ -1201,7 +1225,8 @@ void ConnectionMultiApproxBatch::drive_write_machine(double now) { if (options.getsetorset) { int ret = issue_getsetorset(now); - if (ret == 1) return; //if at EOF + //if (ret) return; //if at EOF + return; } last_tx = now; @@ -1217,10 +1242,17 @@ void ConnectionMultiApproxBatch::drive_write_machine(double now) { case WAITING_FOR_OPQ: if ( (op_queue_size[1] >= (size_t) max_depth) || (op_queue_size[2] >= (size_t) max_depth) ) { - double delay = 0.01; - struct timeval tv; - double_to_tv(delay, &tv); - evtimer_add(timer, &tv); + for (int i = 1; i <= LEVELS; i++) { + if (max_depth > 16) { + if (buffer_write_n[i] > max_depth*0.8) { + send_write_buffer(i); + } + } + } + next_time = now + 0.01; + double_to_tv(delay, &tv); + evtimer_add(timer, &tv); + return; } else { write_state = ISSUING; @@ -1236,23 +1268,37 @@ size_t ConnectionMultiApproxBatch::handle_response_batch(unsigned char *rbuf_pos size_t read_bytes, size_t consumed_bytes, int level, int extra) { if (rbuf_pos[0] != 129) { - fprintf(stderr,"we don't have a valid header %u\n",rbuf_pos[0]); + //fprintf(stderr,"cid %d we don't have a valid header %u\n",cid,rbuf_pos[0]); //buffer_read_pos[level] = rbuf_pos; //buffer_read_n[level] = 1; return 0; } + if ((read_bytes+extra - consumed_bytes) < 24) { + size_t have = (read_bytes+extra) - (consumed_bytes); + size_t need = 24 - (have); + buffer_read_n[level] = need; + buffer_read_nbytes[level] = have; + memcpy(buffer_leftover[level],rbuf_pos,have); + //buffer_lasthdr[level] = rbuf_pos; + //buffer_read_n[level] = need; + //buffer_read_nbytes[level] = have; + //fprintf(stderr,"cid %d - we don't have enough header data, need %lu more bytes, have %lu (targetLen: %d) (read_bytes %ld) (extra %d) %d)\n",cid,need,have,24,read_bytes,extra,level); + return 0; + + } + binary_header_t* h = reinterpret_cast(rbuf_pos); uint32_t bl = ntohl(h->body_len); uint16_t kl = ntohs(h->key_len); uint8_t el = h->extra_len; int targetLen = 24 + bl; if (consumed_bytes + targetLen > (read_bytes+extra)) { - size_t need = (consumed_bytes+targetLen) - (read_bytes+extra); size_t have = (read_bytes+extra) - (consumed_bytes); - //fprintf(stderr,"we don't have enough data, need %lu more bytes (targetLen: %d)\n",need,targetLen); - buffer_read_pos[level] = buffer_read[level]; //reset to begining of buffer - memcpy(buffer_read_pos[level],rbuf_pos,have); - buffer_read_n[level] = have; + size_t need = targetLen - (have); + buffer_read_n[level] = need; + buffer_read_nbytes[level] = have; + memcpy(buffer_leftover[level],rbuf_pos,have); + //fprintf(stderr,"cid %d - we don't have enough data, need %lu more bytes, have %lu (targetLen: %d) (read_bytes %ld) (extra %d) %d)\n",cid,need,have,targetLen,read_bytes,extra,level); return 0; } @@ -1312,20 +1358,66 @@ size_t ConnectionMultiApproxBatch::handle_response_batch(unsigned char *rbuf_pos return targetLen; } + +size_t ConnectionMultiApproxBatch::fill_read_buffer(int level, int *extra) { + + size_t read_bytes = 0; + struct bufferevent *bev = NULL; + switch (level) { + case 1: + bev = bev1; + break; + case 2: + bev = bev2; + break; + default: + bev = bev1; + break; + } + if (buffer_read_n[level] != 0) { + uint32_t have = buffer_read_nbytes[level]; + struct evbuffer *input = bufferevent_get_input(bev); + size_t len = evbuffer_get_length(input); + if (len < buffer_read_n[level]) { + return 0; + } + memset(buffer_read[level],0,512*1024); + memcpy(buffer_read[level],buffer_leftover[level],have); + buffer_read_pos[level] = buffer_read[level]; + read_bytes = bufferevent_read(bev,buffer_read_pos[level]+have,len); + if (read_bytes != len) { + fprintf(stderr,"cid %d expected %lu got %lu\n",cid,len,read_bytes); + } + *extra = have; + buffer_read_n[level] = 0; + buffer_read_nbytes[level] = 0; + + } else { + memset(buffer_read[level],0,512*1024); + buffer_read_pos[level] = buffer_read[level]; + read_bytes = bufferevent_read(bev, buffer_read_pos[level], buffer_size_ / 4); + *extra = 0; + } + if (read_bytes == 0) { + fprintf(stderr,"cid %d read 0 bytes\n",cid); + } + return read_bytes; +} /** * Handle incoming data (responses). */ void ConnectionMultiApproxBatch::read_callback1() { int level = 1; - - int extra = buffer_read_n[level]; - buffer_read_n[level] = 0; - - unsigned char* rbuf_pos = buffer_read[level]; + int extra = 0; + size_t read_bytes = 0; + + read_bytes = fill_read_buffer(level,&extra); + if (read_bytes == 0) { + return; + } - size_t read_bytes = bufferevent_read(bev1, rbuf_pos+extra, buffer_size_); - //fprintf(stderr,"l1 read: %lu\n",read_bytes); + //fprintf(stderr,"cid %d l1 read: %lu\n",cid,read_bytes); size_t consumed_bytes = 0; size_t batch = options.depth; //we have at least some data to read @@ -1336,11 +1428,16 @@ void ConnectionMultiApproxBatch::read_callback1() { resp_t mc_resp; mc_resp.found = true; mc_resp.evict = evict; - size_t cbytes = handle_response_batch(rbuf_pos,&mc_resp,read_bytes,consumed_bytes,level,extra); + size_t cbytes = handle_response_batch(buffer_read_pos[level],&mc_resp,read_bytes,consumed_bytes,level,extra); if (cbytes == 0) { + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } break; } - rbuf_pos = rbuf_pos + cbytes; + buffer_read_pos[level] = buffer_read_pos[level] + cbytes; consumed_bytes += cbytes; uint32_t opaque = mc_resp.opaque; bool found = mc_resp.found; @@ -1348,7 +1445,7 @@ void ConnectionMultiApproxBatch::read_callback1() { Operation *op = op_queue[level][opaque]; #ifdef DEBUGMC char out[128]; - sprintf(out,"conn l1: %u, reading opaque: %u\n",cid,opaque); + sprintf(out,"l1 cid %u, reading opaque: %u\n",cid,opaque); write(2,out,strlen(out)); output_op(op,2,found); #endif @@ -1429,27 +1526,31 @@ void ConnectionMultiApproxBatch::read_callback1() { free(evict); } nread_ops++; - if (rbuf_pos[0] != 129) { + if (buffer_read_pos[level][0] == 0) { + break; + } + if (buffer_read_pos[level][0] != 129) { + fprintf(stderr,"cid %d we don't have a valid header post %u\n",cid,buffer_read_pos[level][0]); break; } } //if (buffer_read_n[level] == 0) { // memset(buffer_read[level],0,read_bytes); //} - if (nread_ops == 0) { - fprintf(stderr,"ugh only got: %lu ops expected %lu, read %lu, cid %u\n",nread_ops,batch,read_bytes,cid); - } + //if (nread_ops == 0) { + // fprintf(stderr,"ugh only got: %lu ops expected %lu, read %lu, cid %u\n",nread_ops,batch,read_bytes,cid); + // int *a = 0; + // *a = 0; + //} double now = get_time(); - if (check_exit_condition(now)) { - return; - } - last_tx = now; stats.log_op(op_queue_size[1]); stats.log_op(op_queue_size[2]); + drive_write_machine(); + } @@ -1457,14 +1558,17 @@ void ConnectionMultiApproxBatch::read_callback1() { * Handle incoming data (responses). */ void ConnectionMultiApproxBatch::read_callback2() { + int level = 2; + int extra = 0; - int extra = buffer_read_n[level]; - buffer_read_n[level] = 0; - unsigned char* rbuf_pos = buffer_read[level]; + size_t read_bytes = 0; + read_bytes = fill_read_buffer(level,&extra); + if (read_bytes == 0) { + return; + } - size_t read_bytes = bufferevent_read(bev2, rbuf_pos+extra,buffer_size_); //fprintf(stderr,"l2 read: %lu\n",read_bytes); size_t consumed_bytes = 0; size_t batch = options.depth; @@ -1474,11 +1578,11 @@ void ConnectionMultiApproxBatch::read_callback2() { resp_t mc_resp; mc_resp.found = true; mc_resp.evict = evict; - size_t cbytes = handle_response_batch(rbuf_pos,&mc_resp,read_bytes,consumed_bytes,level,extra); + size_t cbytes = handle_response_batch(buffer_read_pos[level],&mc_resp,read_bytes,consumed_bytes,level,extra); if (cbytes == 0) { break; } - rbuf_pos = rbuf_pos + cbytes; + buffer_read_pos[level] = buffer_read_pos[level] + cbytes; consumed_bytes += cbytes; uint32_t opaque = mc_resp.opaque; bool found = mc_resp.found; @@ -1486,7 +1590,7 @@ void ConnectionMultiApproxBatch::read_callback2() { Operation *op = op_queue[level][opaque]; #ifdef DEBUGMC char out[128]; - sprintf(out,"conn l2: %u, reading opaque: %u\n",cid,opaque); + sprintf(out,"l2 cid %u, reading opaque: %u\n",cid,opaque); write(2,out,strlen(out)); output_op(op,2,found); #endif @@ -1567,28 +1671,28 @@ void ConnectionMultiApproxBatch::read_callback2() { DIE("not implemented"); } nread_ops++; - if (rbuf_pos[0] != 129) { + if (buffer_read_pos[level][0] == 0) { + break; + } + if (buffer_read_pos[level][0] != 129) { + fprintf(stderr,"l2 cid %d we don't have a valid header post %u\n",cid,buffer_read_pos[level][0]); break; } } //if (buffer_read_n[level] == 0) { // memset(buffer_read[level],0,read_bytes); //} - if (nread_ops == 0) { - fprintf(stderr,"ugh l2 only got: %lu ops expected %lu\n",nread_ops,batch); - } + //if (nread_ops == 0) { + // fprintf(stderr,"ugh l2 only got: %lu ops expected %lu\n",nread_ops,batch); + //} double now = get_time(); - if (check_exit_condition(now)) { - return; - } - last_tx = now; stats.log_op(op_queue_size[2]); stats.log_op(op_queue_size[1]); - drive_write_machine(); + drive_write_machine(); } /** @@ -1624,13 +1728,21 @@ void bev_event_cb2_approx_batch(struct bufferevent *bev, short events, void *ptr void bev_read_cb1_approx_batch(struct bufferevent *bev, void *ptr) { ConnectionMultiApproxBatch* conn = (ConnectionMultiApproxBatch*) ptr; - conn->read_callback1(); + if (conn->options.v1callback) { + conn->read_callback1_v1(); + } else { + conn->read_callback1(); + } } void bev_read_cb2_approx_batch(struct bufferevent *bev, void *ptr) { ConnectionMultiApproxBatch* conn = (ConnectionMultiApproxBatch*) ptr; - conn->read_callback2(); + if (conn->options.v1callback) { + conn->read_callback2_v1(); + } else { + conn->read_callback2(); + } } void bev_write_cb_m_approx_batch(struct bufferevent *bev, void *ptr) { @@ -1640,4 +1752,436 @@ void timer_cb_m_approx_batch(evutil_socket_t fd, short what, void *ptr) { ConnectionMultiApproxBatch* conn = (ConnectionMultiApproxBatch*) ptr; conn->timer_callback(); } +//previous implmentation of read +// + +/** + * Tries to consume a binary response (in its entirety) from an evbuffer. + * + * @param input evBuffer to read response from + * @return true if consumed, false if not enough data in buffer. + */ +static bool handle_response(ConnectionMultiApproxBatch *conn, evbuffer *input, bool &done, bool &found, int &opcode, uint32_t &opaque, evicted_t *evict, int level) { + // Read the first 24 bytes as a header + int length = evbuffer_get_length(input); + if (length < 24) return false; + binary_header_t* h = + reinterpret_cast(evbuffer_pullup(input, 24)); + //assert(h); + + uint32_t bl = ntohl(h->body_len); + uint16_t kl = ntohs(h->key_len); + uint8_t el = h->extra_len; + // Not whole response + int targetLen = 24 + bl; + if (length < targetLen) { + return false; + } + + opcode = h->opcode; + opaque = ntohl(h->opaque); + uint16_t status = ntohs(h->status); +#ifdef DEBUGMC + fprintf(stderr,"cid: %d handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",conn->get_cid(),level, + h->opcode,ntohl(h->opaque),ntohs(h->key_len),h->extra_len, + ntohl(h->body_len),ntohs(h->status)); +#endif + + + // If something other than success, count it as a miss + if (opcode == CMD_GET && status == RESP_NOT_FOUND) { + switch(level) { + case 1: + conn->stats.get_misses_l1++; + break; + case 2: + conn->stats.get_misses_l2++; + conn->stats.get_misses++; + conn->stats.window_get_misses++; + break; + + } + found = false; + evbuffer_drain(input, targetLen); + + } else if (opcode == CMD_SET && kl > 0) { + //first data is extras: clsid, flags, eflags + if (evict) { + evbuffer_drain(input,24); + unsigned char *buf = evbuffer_pullup(input,bl); + + + evict->clsid = *((uint32_t*)buf); + evict->clsid = ntohl(evict->clsid); + buf += 4; + + evict->serverFlags = *((uint32_t*)buf); + evict->serverFlags = ntohl(evict->serverFlags); + buf += 4; + + evict->evictedFlags = *((uint32_t*)buf); + evict->evictedFlags = ntohl(evict->evictedFlags); + buf += 4; + + + evict->evictedKeyLen = kl; + evict->evictedKey = (char*)malloc(kl+1); + memset(evict->evictedKey,0,kl+1); + memcpy(evict->evictedKey,buf,kl); + buf += kl; + + + evict->evictedLen = bl - kl - el; + evict->evictedData = (char*)malloc(evict->evictedLen); + memcpy(evict->evictedData,buf,evict->evictedLen); + evict->evicted = true; + //fprintf(stderr,"class: %u, serverFlags: %u, evictedFlags: %u\n",evict->clsid,evict->serverFlags,evict->evictedFlags); + evbuffer_drain(input,bl); + } else { + evbuffer_drain(input, targetLen); + } + } else if (opcode == CMD_TOUCH && status == RESP_NOT_FOUND) { + found = false; + evbuffer_drain(input, targetLen); + } else if (opcode == CMD_DELETE && status == RESP_NOT_FOUND) { + found = false; + evbuffer_drain(input, targetLen); + } else { + evbuffer_drain(input, targetLen); + } + + conn->stats.rx_bytes += targetLen; + done = true; + return true; +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMultiApproxBatch::read_callback1_v1() { + struct evbuffer *input = bufferevent_get_input(bev1); + + Operation *op = NULL; + bool done, found; + + //initially assume found (for sets that may come through here) + //is this correct? do we want to assume true in case that + //GET was found, but wrong value size (i.e. update value) + found = true; + + //if (op_queue.size() == 0) V("Spurious read callback."); + bool full_read = true; + while (full_read) { + + + int opcode; + uint32_t opaque; + evicted_t *evict = (evicted_t*)malloc(sizeof(evicted_t)); + memset(evict,0,sizeof(evicted_t)); + + full_read = handle_response(this,input, done, found, opcode, opaque, evict,1); + if (full_read) { + if (opcode == CMD_NOOP) { +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + continue; + } + op = op_queue[1][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + if (strlen(op->key) < 1) { +#ifdef DEBUGMC + char out2[128]; + sprintf(out2,"conn l1: %u, bad op: %s\n",cid,op->key); + write(2,out2,strlen(out2)); +#endif + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + continue; + } + } else { + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + break; + } + + + double now = get_time(); + int wb = 0; + if (options.rand_admit) { + wb = (rand() % options.rand_admit); + } + switch (op->type) { + case Operation::GET: + if (done) { + + int vl = op->valuelen; + if ( !found && (options.getset || options.getsetorset) ) { + /* issue a get a l2 */ + int flags = OP_clu(op); + issue_get_with_len(op->key,vl,now,false, flags | SRC_L1_M | ITEM_L2 | LOG_OP, op); + op->end_time = now; + this->stats.log_get_l1(*op); + //finish_op(op,0); + + } else { + if (OP_incl(op) && ghits >= gloc) { + //int ret = add_to_touch_keys(string(op->key)); + //if (ret == 1) { + issue_touch(op->key,vl,now, ITEM_L2 | SRC_L1_H); + //} + gloc += rand()%(10*2-1)+1; + } + ghits++; + finish_op(op,1); + } + } else { + char out[128]; + sprintf(out,"conn l1: %u, not done reading, should do something",cid); + write(2,out,strlen(out)); + } + break; + case Operation::SET: + //if (OP_src(op) == SRC_L1_COPY || + // OP_src(op) == SRC_L2_M) { + // del_copy_keys(string(op->key)); + //} + if (evict->evicted) { + string wb_key(evict->evictedKey); + if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { + //int ret = add_to_wb_keys(wb_key); + //if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_WB | ITEM_DIRTY); + //} + this->stats.incl_wbs++; + } else if (evict->evictedFlags & ITEM_EXCL) { + //fprintf(stderr,"excl writeback %s\n",evict->evictedKey); + //strncpy(wb_key,evict->evictedKey,255); + if ( (options.rand_admit && wb == 0) || + (options.threshold && (g_key_hist[wb_key] == 1)) || + (options.wb_all) ) { + //int ret = add_to_wb_keys(wb_key); + //if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + //} + this->stats.excl_wbs++; + } + } + if (OP_src(op) == SRC_DIRECT_SET) { + if ( (evict->serverFlags & ITEM_SIZE_CHANGE) || ((evict->serverFlags & ITEM_WAS_HIT) == 0)) { + this->stats.set_misses_l1++; + } else if (OP_excl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_excl_hits_l1++; + } else if (OP_incl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_incl_hits_l1++; + } + } + } + finish_op(op,1); + break; + case Operation::TOUCH: + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); + DIE("not implemented"); + } + + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + + } + + + double now = get_time(); + + last_tx = now; + stats.log_op(op_queue_size[1]); + stats.log_op(op_queue_size[2]); + //for (int i = 1; i <= 2; i++) { + // fprintf(stderr,"max issue buf n[%d]: %u\n",i,max_n[i]); + //} + drive_write_machine(); + + // update events + //if (bev != NULL) { + // // no pending response (nothing to read) and output buffer empty (nothing to write) + // if ((op_queue.size() == 0) && (evbuffer_get_length(bufferevent_get_output(bev)) == 0)) { + // bufferevent_disable(bev, EV_WRITE|EV_READ); + // } + //} +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMultiApproxBatch::read_callback2_v1() { + struct evbuffer *input = bufferevent_get_input(bev2); + + Operation *op = NULL; + bool done, found; + //initially assume found (for sets that may come through here) + //is this correct? do we want to assume true in case that + //GET was found, but wrong value size (i.e. update value) + found = true; + + + //if (op_queue.size() == 0) V("Spurious read callback."); + bool full_read = true; + while (full_read) { + + + int opcode; + uint32_t opaque; + full_read = handle_response(this,input, done, found, opcode, opaque, NULL,2); + if (full_read) { + if (opcode == CMD_NOOP) { +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif + continue; + } + op = op_queue[2][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + if (strlen(op->key) < 1) { +#ifdef DEBUGMC + char out2[128]; + sprintf(out2,"conn l2: %u, bad op: %s\n",cid,op->key); + write(2,out2,strlen(out2)); +#endif + continue; + } + } else { + break; + } + + + double now = get_time(); + switch (op->type) { + case Operation::GET: + if (done) { + if ( !found && (options.getset || options.getsetorset) ) {// && + //(options.twitter_trace != 1)) { + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | SRC_L2_M | LOG_OP; + //int ret = add_to_copy_keys(string(op->key)); + //if (ret == 1) { + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L1); + if (OP_incl(op)) { + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L2); + last_quiet2 = false; + } + //} + last_quiet1 = false; + finish_op(op,0); // sets read_state = IDLE + + } else { + if (found) { + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; + string key = string(op->key); + const char *data = &random_char[index]; + //int ret = add_to_copy_keys(string(op->key)); + //if (ret == 1) { + issue_set(op->key,data,valuelen, now, flags); + //} + this->stats.copies_to_l1++; + //djb: this is automatically done in the L2 server + //if (OP_excl(op)) { //djb: todo should we delete here for approx or just let it die a slow death? + // issue_delete(op->key,now, ITEM_L2 | SRC_L1_COPY ); + //} + finish_op(op,1); + + } else { + finish_op(op,0); + } + } + } else { + char out[128]; + sprintf(out,"conn l2: %u, not done reading, should do something",cid); + write(2,out,strlen(out)); + } + break; + case Operation::SET: + //if (OP_src(op) == SRC_WB) { + // del_wb_keys(string(op->key)); + //} + finish_op(op,1); + break; + case Operation::TOUCH: + if (OP_src(op) == SRC_DIRECT_SET || SRC_L1_H) { + int valuelen = op->valuelen; + if (!found) { + int index = lrand48() % (1024 * 1024); + issue_set(op->key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP | SRC_L2_M); + this->stats.set_misses_l2++; + } else { + if (OP_src(op) == SRC_DIRECT_SET) { + issue_touch(op->key,valuelen,now, ITEM_L1 | SRC_L2_H | ITEM_DIRTY); + } + } + //del_touch_keys(string(op->key)); + } + finish_op(op,0); + break; + case Operation::DELETE: + //check to see if it was a hit + //fprintf(stderr," del %s -- %d from %d\n",op->key.c_str(),found,OP_src(op)); + if (OP_src(op) == SRC_DIRECT_SET) { + if (found) { + this->stats.delete_hits_l2++; + } else { + this->stats.delete_misses_l2++; + } + } + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); + DIE("not implemented"); + } + + } + + double now = get_time(); + + last_tx = now; + stats.log_op(op_queue_size[2]); + stats.log_op(op_queue_size[1]); + drive_write_machine(); + + // update events + //if (bev != NULL) { + // // no pending response (nothing to read) and output buffer empty (nothing to write) + // if ((op_queue.size() == 0) && (evbuffer_get_length(bufferevent_get_output(bev)) == 0)) { + // bufferevent_disable(bev, EV_WRITE|EV_READ); + // } + //} +} diff --git a/ConnectionMultiApproxShm.cc b/ConnectionMultiApproxShm.cc new file mode 100644 index 0000000..1db2e73 --- /dev/null +++ b/ConnectionMultiApproxShm.cc @@ -0,0 +1,1726 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "Connection.h" +#include "distributions.h" +#include "Generator.h" +#include "mutilate.h" +#include "binary_protocol.h" +#include "util.h" +#include +#include +#include +#include +#include +#include "blockingconcurrentqueue.h" +#include "bipbuffer.h" +//#include +//#include + + + +#define ITEM_L1 1 +#define ITEM_L2 2 +#define LOG_OP 4 +#define SRC_L1_M 8 +#define SRC_L1_H 16 +#define SRC_L2_M 32 +#define SRC_L2_H 64 +#define SRC_DIRECT_SET 128 +#define SRC_L1_COPY 256 +#define SRC_WB 512 + +#define ITEM_INCL 4096 +#define ITEM_EXCL 8192 +#define ITEM_DIRTY 16384 +#define ITEM_SIZE_CHANGE 131072 +#define ITEM_WAS_HIT 262144 + +#define LEVELS 2 +#define SET_INCL(incl,flags) \ + switch (incl) { \ + case 1: \ + flags |= ITEM_INCL; \ + break; \ + case 2: \ + flags |= ITEM_EXCL; \ + break; \ + \ + } \ + +#define GET_INCL(incl,flags) \ + if (flags & ITEM_INCL) incl = 1; \ + else if (flags & ITEM_EXCL) incl = 2; \ + +//#define OP_level(op) ( ((op)->flags & ITEM_L1) ? ITEM_L1 : ITEM_L2 ) +#define OP_level(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define FLAGS_level(flags) ( flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_clu(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_L1 | ITEM_L2 | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_src(op) ( (op)->flags & ~(ITEM_L1 | ITEM_L2 | LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY ) ) + +#define OP_log(op) ((op)->flags & LOG_OP) +#define OP_incl(op) ((op)->flags & ITEM_INCL) +#define OP_excl(op) ((op)->flags & ITEM_EXCL) +#define OP_set_flag(op,flag) ((op))->flags |= flag; + +//#define DEBUGMC +//#define DEBUGS +//using namespace folly; +using namespace moodycamel; +//using namespace fmt; + +//struct node { +// long long addr,label; +// node *nxt; +// node(long long _addr = 0, long long _label = 0, node *_nxt = NULL) +// : addr(_addr),label(_label),nxt(_nxt) {} +//}; +// +//struct tnode { +// long long tm,offset; int size; +//};//trace file data structure +// +//long long find(long long addr) { +// int t = addr%MAXH; +// node *tmp = hash[t],*pre = NULL; +// while (tmp) { +// if (tmp->addr == addr) { +// long long tlabel = tmp->label; +// if (pre == NULL) hash[t] = tmp->nxt; +// else pre->nxt = tmp->nxt; +// delete tmp; +// return tlabel; +// } +// pre = tmp; +// tmp = tmp->nxt; +// } +// return 0; +//} +// +//void insert(long long addr ) { +// int t = addr%MAXH; +// node *tmp = new node(addr,n,hash[t]); +// hash[t] = tmp; +//} + + + +pthread_mutex_t cid_lock_m_approx_shm = PTHREAD_MUTEX_INITIALIZER; +static uint32_t connids_m = 1; + +#define NCLASSES 40 +#define CHUNK_ALIGN_BYTES 8 +static int classes = 0; +static int sizes[NCLASSES+1]; +static int inclusives[NCLASSES+1]; + + + +static void init_inclusives(char *inclusive_str) { + int j = 1; + for (int i = 0; i < (int)strlen(inclusive_str); i++) { + if (inclusive_str[i] == '-') { + continue; + } else { + inclusives[j] = inclusive_str[i] - '0'; + j++; + } + } +} + +static void init_classes() { + + double factor = 1.25; + //unsigned int chunk_size = 48; + //unsigned int item_size = 24; + unsigned int size = 96; //warning if you change this you die + unsigned int i = 0; + unsigned int chunk_size_max = 1048576/2; + while (++i < NCLASSES-1) { + if (size >= chunk_size_max / factor) { + break; + } + if (size % CHUNK_ALIGN_BYTES) + size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); + sizes[i] = size; + size *= factor; + } + sizes[i] = chunk_size_max; + classes = i; + +} + +static int get_class(int vl, uint32_t kl) { + //warning if you change this you die + int vsize = vl+kl+48+1+2; + int res = 1; + while (vsize > sizes[res]) + if (res++ == classes) { + //fprintf(stderr,"item larger than max class size. vsize: %d, class size: %d\n",vsize,sizes[res]); + return -1; + } + return res; +} + +static int get_incl(int vl, int kl) { + int clsid = get_class(vl,kl); + if (clsid) { + return inclusives[clsid]; + } else { + return -1; + } +} + +void ConnectionMultiApproxShm::output_op(Operation *op, int type, bool found) { + char output[1024]; + char k[256]; + char a[256]; + char s[256]; + memset(k,0,256); + memset(a,0,256); + memset(s,0,256); + strncpy(k,op->key,255); + switch (type) { + case 0: //get + sprintf(a,"issue_get"); + break; + case 1: //set + sprintf(a,"issue_set"); + break; + case 2: //resp + sprintf(a,"resp"); + break; + } + switch(read_state) { + case INIT_READ: + sprintf(s,"init"); + break; + case CONN_SETUP: + sprintf(s,"setup"); + break; + case LOADING: + sprintf(s,"load"); + break; + case IDLE: + sprintf(s,"idle"); + break; + case WAITING_FOR_GET: + sprintf(s,"waiting for get"); + break; + case WAITING_FOR_SET: + sprintf(s,"waiting for set"); + break; + case WAITING_FOR_DELETE: + sprintf(s,"waiting for del"); + break; + case MAX_READ_STATE: + sprintf(s,"max"); + break; + } + if (type == 2) { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, found: %d, type: %d\n",cid,a,k,op->opaque,found,op->type); + } else { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, type: %d\n",cid,a,k,op->opaque,op->type); + } + write(2,output,strlen(output)); +} + +//extern USPMCQueue g_trace_queue; +//static vector cid_rate; +//extern ConcurrentHashMap cid_rate; +extern unordered_map cid_rate; +//extern ConcurrentHashMap> copy_keys; +extern unordered_map> copy_keys; +extern unordered_map touch_keys; +extern unordered_map> wb_keys; +//extern ConcurrentHashMap> wb_keys; + +extern map g_key_hist; +extern int max_n[3]; + +/** + * Create a new connection to a server endpoint. + */ +ConnectionMultiApproxShm::ConnectionMultiApproxShm(options_t _options, + bool sampling) : + start_time(0), stats(sampling), options(_options) +{ + pthread_mutex_lock(&cid_lock_m_approx_shm); + cid = connids_m++; + if (cid == 1) { + init_classes(); + init_inclusives(options.inclusives); + } + cid_rate.insert( { cid, 0 } ); + + pthread_mutex_unlock(&cid_lock_m_approx_shm); + + valuesize = createGenerator(options.valuesize); + keysize = createGenerator(options.keysize); + srand(time(NULL)); + keygen = new KeyGenerator(keysize, options.records); + + total = 0; + eof = 0; + o_percent = 0; + + if (options.lambda <= 0) { + iagen = createGenerator("0"); + } else { + D("iagen = createGenerator(%s)", options.ia); + iagen = createGenerator(options.ia); + iagen->set_lambda(options.lambda); + } + + read_state = IDLE; + write_state = INIT_WRITE; + last_quiet1 = false; + last_quiet2 = false; + + last_tx = last_rx = 0.0; + gets = 0; + ghits = 0; + esets = 0; + isets = 0; + gloc = rand() % (10*2-1)+1; + sloc = rand() % (10*2-1)+1; + iloc = rand() % (10*2-1)+1; + + op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + op_queue = (Operation***)malloc(sizeof(Operation**)*(LEVELS+1)); + + for (int i = 0; i <= LEVELS; i++) { + op_queue_size[i] = 0; + opaque[i] = 1; + //op_queue[i] = (Operation*)malloc(sizeof(int)*OPAQUE_MAX); + op_queue[i] = (Operation**)malloc(sizeof(Operation*)*(OPAQUE_MAX+1)); + for (int j = 0; j <= OPAQUE_MAX; j++) { + op_queue[i][j] = NULL; + } + + } + + read_state = IDLE; +} + + +void ConnectionMultiApproxShm::set_queue(queue* a_trace_queue) { + trace_queue = a_trace_queue; + trace_queue_n = a_trace_queue->size(); +} + +void ConnectionMultiApproxShm::set_lock(pthread_mutex_t* a_lock) { + lock = a_lock; +} + +void ConnectionMultiApproxShm::set_g_wbkeys(unordered_map> *a_wb_keys) { + g_wb_keys = a_wb_keys; +} + +uint32_t ConnectionMultiApproxShm::get_cid() { + return cid; +} + +int ConnectionMultiApproxShm::add_to_wb_keys(string key) { + auto pos = wb_keys.find(key); + if (pos == wb_keys.end()) { + wb_keys.insert( {key, vector() }); + return 1; + } + return 2; +} + +int ConnectionMultiApproxShm::add_to_copy_keys(string key) { + auto pos = copy_keys.find(key); + if (pos == copy_keys.end()) { + copy_keys.insert( {key, vector() }); + return 1; + } + return 2; +} + + +void ConnectionMultiApproxShm::del_copy_keys(string key) { + + auto position = copy_keys.find(key); + if (position != copy_keys.end()) { + vector op_list = vector(position->second); + copy_keys.erase(position); + for (auto it = op_list.begin(); it != op_list.end(); ++it) { + issue_op(*it); + } + } else { + fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); + } +} + +int ConnectionMultiApproxShm::add_to_touch_keys(string key) { + //return touch_keys.assign_if_equal( key, NULL, cid ) != NULL ? 1 : 2; + auto pos = touch_keys.find(key); + if (pos == touch_keys.end()) { + touch_keys.insert( {key, cid }); + return 1; + } + return 2; +} + + +void ConnectionMultiApproxShm::del_touch_keys(string key) { + //touch_keys.erase(key); + auto position = touch_keys.find(key); + if (position != touch_keys.end()) { + touch_keys.erase(position); + } else { + fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); + } +} + +int ConnectionMultiApproxShm::issue_op(Operation *Op) { + double now = get_time(); + int issued = 0; + int incl = get_incl(Op->valuelen,strlen(Op->key)); + int cid = get_class(Op->valuelen,strlen(Op->key)); + Op->clsid = cid; + int flags = 0; + int index = lrand48() % (1024 * 1024); + //int touch = 1; + SET_INCL(incl,flags); + + switch(Op->type) + { + case Operation::GET: + //if (nissued < options.depth-1) { + // issued = issue_get_with_len(key, vl, now, false, 1, flags, 0, 1); + // last_quiet1 = false; + //} else { + //} + issued = issue_get_with_len(Op, now, false, flags | LOG_OP | ITEM_L1); + last_quiet1 = false; + this->stats.gets++; + gets++; + this->stats.gets_cid[cid]++; + + break; + case Operation::SET: + if (last_quiet1) { + //issue_noop(1); + } + if (incl == 1) { + if (isets >= iloc) { + //if (1) { + const char *data = &random_char[index]; + issued = issue_set(Op, data, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + //int ret = add_to_touch_keys(string(Op->key)); + //if (ret == 1) { + issue_touch(Op->key,Op->valuelen,now, ITEM_L2 | SRC_DIRECT_SET); + //} + iloc += rand()%(10*2-1)+1; + } else { + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); + } + isets++; + } else if (incl == 2) { + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + if (esets >= sloc) { + issue_delete(Op->key,now,ITEM_L2 | SRC_DIRECT_SET); + sloc += rand()%(10*2-1)+1; + } + esets++; + } + last_quiet1 = false; + this->stats.sets++; + this->stats.sets_cid[cid]++; + break; + case Operation::DELETE: + case Operation::TOUCH: + case Operation::NOOP: + case Operation::SASL: + fprintf(stderr,"invalid line: %s, vl: %d\n",Op->key,Op->valuelen); + break; + + } + return issued; +} + +void ConnectionMultiApproxShm::del_wb_keys(string key) { + + auto position = wb_keys.find(key); + if (position != wb_keys.end()) { + vector op_list = vector(position->second); + wb_keys.erase(position); + for (auto it = op_list.begin(); it != op_list.end(); ++it) { + issue_op(*it); + } + } else { + fprintf(stderr,"expected %s, got nuthin\n",key.c_str()); + } +} + + +int ConnectionMultiApproxShm::do_connect() { + + + //the client should see for this cid, where the shared memory is + typedef struct shared_ { + bipbuf_t bipbuf_in; + bipbuf_t bipbuf_out; + pthread_mutex_t lock_in; + pthread_mutex_t lock_out; + int shared_id; + } shared_t; + + //this cid gets shared memory + // ftok to generate unique key + //char shmkey[64]; + //sprintf(shmkey,"shmfilel1%d",cid); + int id = cid+100; + //key_t key = ftok(shmkey,id); + + // shmget returns an identifier in shmid + int shmid = shmget(id,sizeof(shared_t),0666); + + // shmat to attach to shared memory + shared_t* share_l1 = (shared_t*) shmat(shmid,(void*)0,0); + + fprintf(stderr,"cid %d gets shared memory buf l1 %d\n",cid,share_l1->shared_id); + + // ftok to generate unique key + //char shmkey2[64]; + //sprintf(shmkey2,"shmfilel2%d",cid); + int id2 = cid+200; + //key_t key2 = ftok(shmkey2,id2); + + // shmget returns an identifier in shmid + int shmid2 = shmget(id2,sizeof(shared_t),0666); + + // shmat to attach to shared memory + shared_t* share_l2 = (shared_t*) shmat(shmid2,(void*)0,0); + + fprintf(stderr,"cid %d gets shared memory buf l2 %d\n",cid,share_l2->shared_id); + + //the leads are reveresed (from perspective of server) + bipbuf_in[1] = &share_l1->bipbuf_out; + bipbuf_in[2] = &share_l2->bipbuf_out; + bipbuf_out[1] = &share_l1->bipbuf_in; + bipbuf_out[2] = &share_l2->bipbuf_in; + + lock_in[1] = &share_l1->lock_out; + lock_in[2] = &share_l2->lock_out; + lock_out[1] = &share_l1->lock_in; + lock_out[2] = &share_l2->lock_in; + read_state = IDLE; + return 1; +} + +/** + * Destroy a connection, performing cleanup. + */ +ConnectionMultiApproxShm::~ConnectionMultiApproxShm() { + + + for (int i = 0; i <= LEVELS; i++) { + free(op_queue[i]); + + } + + free(op_queue_size); + free(opaque); + free(op_queue); + //event_free(timer); + //timer = NULL; + // FIXME: W("Drain op_q?"); + //bufferevent_free(bev1); + //bufferevent_free(bev2); + + delete iagen; + delete keygen; + delete keysize; + delete valuesize; +} + +/** + * Reset the connection back to an initial, fresh state. + */ +void ConnectionMultiApproxShm::reset() { + // FIXME: Actually check the connection, drain all bufferevents, drain op_q. + //assert(op_queue.size() == 0); + //evtimer_del(timer); + read_state = IDLE; + write_state = INIT_WRITE; + stats = ConnectionStats(stats.sampling); +} + + + + +/** + * Get/Set or Set Style + * If a GET command: Issue a get first, if not found then set + * If trace file (or prob. write) says to set, then set it + */ +int ConnectionMultiApproxShm::issue_getsetorset(double now) { + + + + int ret = 0; + int nissued = 0; + + //while (nissued < 1) { + + //pthread_mutex_lock(lock); + //if (!trace_queue->empty()) { + + /* check if in global wb queue */ + //double percent = (double)total/((double)trace_queue_n) * 100; + //if (percent > o_percent+2) { + // //update the percentage table and see if we should execute + // if (options.ratelimit) { + // double min_percent = 1000; + // auto it = cid_rate.begin(); + // while (it != cid_rate.end()) { + // if (it->second < min_percent) { + // min_percent = it->second; + // } + // ++it; + // } + + // if (percent > min_percent+2) { + // struct timeval tv; + // tv.tv_sec = 0; + // tv.tv_usec = 100; + // int good = 0; + // if (!event_pending(timer, EV_TIMEOUT, NULL)) { + // good = evtimer_add(timer, &tv); + // } + // if (good != 0) { + // fprintf(stderr,"eventimer is messed up!\n"); + // return 2; + // } + // return 1; + // } + // } + // cid_rate.insert( {cid, percent}); + // fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); + // o_percent = percent; + //} + // + + Operation *Op = trace_queue->front(); + //Operation *Op = g_trace_queue.dequeue(); + + if (Op == NULL || trace_queue->size() <= 0 || Op->type == Operation::SASL) { + eof = 1; + cid_rate.insert( {cid, 100 } ); + fprintf(stderr,"cid %d done\n",cid); + string op_queue1; + string op_queue2; + for (int j = 0; j < 2; j++) { + for (int i = 0; i < OPAQUE_MAX; i++) { + if (op_queue[j+1][i] != NULL) { + if (j == 0) { + op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + } else { + op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + } + } + } + } + fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + return 1; + } + + trace_queue->pop(); + + + //trace_queue->pop(); + + //pthread_mutex_lock(lock); + //auto check = wb_keys.find(string(Op->key)); + //if (check != wb_keys.end()) { + // check->second.push_back(Op); + // return 0; + //} + //pthread_mutex_unlock(lock); + //pthread_mutex_unlock(lock); + //struct timeval tv; + //double delay; + //delay = last_rx + 0.00025 - now; + //double_to_tv(delay,&tv); + //int good = 0; + ////if (!event_pending(timer, EV_TIMEOUT, NULL)) { + //good = evtimer_add(timer, &tv); + ////} + //if (good != 0) { + // fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op->key); + // return 2; + //} + //return 1; + //} else { + //pthread_mutex_unlock(lock); + int issued = issue_op(Op); + if (issued) { + nissued++; + total++; + } else { + fprintf(stderr,"failed to issue line: %s, vl: %d\n",Op->key,Op->valuelen); + } + //} + + //} else { + // return 1; + //} + //} + //if (last_quiet1) { + // issue_noop(now,1); + // last_quiet1 = false; + //} + + return ret; + +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApproxShm::issue_get_with_len(Operation *pop, double now, bool quiet, uint32_t flags, Operation *l1) { + + int level = FLAGS_level(flags); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + pop->opaque = opaque[level]++; + pop->flags = flags; + if (l1 != NULL) { + pop->l1 = l1; + } + + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,pop->key,pop->valuelen,level,flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(pop->key); + + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_GET, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + if (quiet) { + h.opcode = CMD_GETQ; + } + h.opaque = htonl(pop->opaque); + + pthread_mutex_lock(lock_out[level]); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); + pthread_mutex_unlock(lock_out[level]); + + stats.tx_bytes += 24 + keylen; + return 1; +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApproxShm::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1) { + + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + strncpy(pop->key,key,255); + pop->valuelen = valuelen; + pop->type = Operation::GET; + pop->opaque = opaque[level]++; + pop->flags = flags; + pop->clsid = get_class(valuelen,strlen(key)); + if (l1 != NULL) { + pop->l1 = l1; + } + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,valuelen,level,flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_GET, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + if (quiet) { + h.opcode = CMD_GETQ; + } + h.opaque = htonl(pop->opaque); + + + pthread_mutex_lock(lock_out[level]); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)key,keylen); + pthread_mutex_unlock(lock_out[level]); + + stats.tx_bytes += 24 + keylen; + return 1; +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApproxShm::issue_touch(const char* key, int valuelen, double now, int flags) { + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + strncpy(pop->key,key,255); + pop->valuelen = valuelen; + pop->type = Operation::TOUCH; + pop->opaque = opaque[level]++; + op_queue[level][pop->opaque] = pop; + op_queue_size[level]++; + + pop->flags = flags; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + +#ifdef DEBUGS + fprintf(stderr,"issing touch: %s, size: %u, level %d, flags: %d, opaque: %d\n",key,valuelen,level,flags,pop->opaque); +#endif + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_TOUCH, htons(keylen), + 0x04, 0x00, htons(0), + htonl(keylen + 4) }; + h.opaque = htonl(pop->opaque); + + uint32_t exp = 0; + if (flags & ITEM_DIRTY) { + exp = htonl(flags); + } + + pthread_mutex_lock(lock_out[level]); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)key,keylen); + pthread_mutex_unlock(lock_out[level]); + + stats.tx_bytes += 24 + keylen; + + //stats.log_access(op); + return 1; +} + +/** + * Issue a delete request to the server. + */ +int ConnectionMultiApproxShm::issue_delete(const char* key, double now, uint32_t flags) { + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + strncpy(pop->key,key,255); + pop->type = Operation::DELETE; + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing delete: %s, level %d, flags: %d, opaque: %d\n",cid,key,level,flags,pop->opaque); +#endif + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_DELETE, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + h.opaque = htonl(pop->opaque); + + pthread_mutex_lock(lock_out[level]); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)key,keylen); + pthread_mutex_unlock(lock_out[level]); + + + stats.tx_bytes += 24 + keylen; + + //stats.log_access(op); + return 1; +} + +void ConnectionMultiApproxShm::issue_noop(int level) { + Operation op; + + + binary_header_t h = { 0x80, CMD_NOOP, 0x0000, + 0x00, 0x00, htons(0), + 0x00 }; + + + //bipbuf_offer(bipbuf[level],&h,24); +} + +/** + * Issue a set request to the server. + */ +int ConnectionMultiApproxShm::issue_set(Operation *pop, const char* value, double now, uint32_t flags) { + + int level = FLAGS_level(flags); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) pop->start_time = get_time(); + else pop->start_time = now; +#endif + + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,pop->key,pop->valuelen,level,flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + uint16_t keylen = strlen(pop->key); + + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_SET, htons(keylen), + 0x08, 0x00, htons(0), + htonl(keylen + 8 + pop->valuelen) }; + h.opaque = htonl(pop->opaque); + + uint32_t f = htonl(flags); + uint32_t exp = 0; + + + pthread_mutex_lock(lock_out[level]); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&f,4); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)value,pop->valuelen); + pthread_mutex_unlock(lock_out[level]); + + stats.tx_bytes += pop->valuelen + 32 + keylen; + return 1; +} + +/** + * Issue a set request to the server. + */ +int ConnectionMultiApproxShm::issue_set(const char* key, const char* value, int length, double now, uint32_t flags) { + + int level = FLAGS_level(flags); + //Operation op; + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) pop->start_time = get_time(); + else pop->start_time = now; +#endif + + strncpy(pop->key,key,255); + pop->valuelen = length; + pop->type = Operation::SET; + pop->opaque = opaque[level]++; + pop->flags = flags; + pop->clsid = get_class(length,strlen(key)); + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,length,level,flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_SET, htons(keylen), + 0x08, 0x00, htons(0), + htonl(keylen + 8 + length) }; + h.opaque = htonl(pop->opaque); + + uint32_t f = htonl(flags); + uint32_t exp = 0; + + + pthread_mutex_lock(lock_out[level]); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&f,4); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)key,keylen); + bipbuf_offer(bipbuf_out[level],(const unsigned char*)value,length); + pthread_mutex_unlock(lock_out[level]); + + stats.tx_bytes += length + 32 + keylen; + return 1; +} + +/** + * Return the oldest live operation in progress. + */ +void ConnectionMultiApproxShm::pop_op(Operation *op) { + + uint8_t level = OP_level(op); + //op_queue[level].erase(op); + op_queue_size[level]--; + + + if (read_state == LOADING) return; + read_state = IDLE; + + // Advance the read state machine. + //if (op_queue.size() > 0) { + // Operation& op = op_queue.front(); + // switch (op.type) { + // case Operation::GET: read_state = WAITING_FOR_GET; break; + // case Operation::SET: read_state = WAITING_FOR_SET; break; + // case Operation::DELETE: read_state = WAITING_FOR_DELETE; break; + // default: DIE("Not implemented."); + // } + //} +} + +/** + * Finish up (record stats) an operation that just returned from the + * server. + */ +void ConnectionMultiApproxShm::finish_op(Operation *op, int was_hit) { + double now; +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); +#else + now = get_time(); +#endif +#if HAVE_CLOCK_GETTIME + op->end_time = get_time_accurate(); +#else + op->end_time = now; +#endif + + if (was_hit) { + switch (op->type) { + case Operation::GET: + switch (OP_level(op)) { + case 1: + stats.log_get_l1(*op); + break; + case 2: + stats.log_get_l2(*op); + if (op->l1 != NULL) { + op->l1->end_time = now; + stats.log_get(*(op->l1)); + } + break; + } + break; + case Operation::SET: + switch (OP_level(op)) { + case 1: + stats.log_set_l1(*op); + break; + case 2: + stats.log_set_l2(*op); + break; + } + break; + case Operation::DELETE: break; + case Operation::TOUCH: break; + default: DIE("Not implemented."); + } + } + //} else { + // switch (op->type) { + // case Operation::GET: + // if (OP_log(op)) { + // switch (OP_level(op)) { + // case 1: + // stats.log_get_l1(*op); + // break; + // case 2: + // stats.log_get_l2(*op); + // if (op->l1 != NULL) { + // op->l1->end_time = now; + // stats.log_get(*(op->l1)); + // } + // break; + // } + // } + // break; + // case Operation::SET: + // if (OP_log(op)) { + // switch (OP_level(op)) { + // case 1: + // stats.log_set_l1(*op); + // break; + // case 2: + // stats.log_set_l2(*op); + // break; + // } + // } + // break; + // case Operation::DELETE: break; + // case Operation::TOUCH: break; + // default: DIE("Not implemented."); + // } + //} + + last_rx = now; + uint8_t level = OP_level(op); + if (op->l1 != NULL) { + //delete op_queue[1][op->l1->opaque]; + op_queue[1][op->l1->opaque] = 0; + op_queue_size[1]--; + delete op->l1; + } + //op_queue[level].erase(op_queue[level].begin()+opopq); + if (op == op_queue[level][op->opaque] && + op->opaque == op_queue[level][op->opaque]->opaque) { + //delete op_queue[level][op->opaque]; + op_queue[level][op->opaque] = 0; + delete op; + } else { + fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", + op,op_queue[level][op->opaque],op->opaque,op_queue[level][op->opaque]->opaque); + } + op_queue_size[level]--; + read_state = IDLE; + + +} + + + +/** + * Check if our testing is done and we should exit. + */ +bool ConnectionMultiApproxShm::check_exit_condition(double now) { + if (eof && op_queue_size[1] == 0 && op_queue_size[2] == 0) { + return true; + } + if (read_state == INIT_READ) return false; + + return false; +} + + + +/** + * Request generation loop + */ +void ConnectionMultiApproxShm::drive_write_machine_shm(double now) { + + while (trace_queue->size() > 0) { + int nissued = 0; + int nissuedl2 = 0; + while (nissued < options.depth) { + Operation *Op = trace_queue->front(); + + if (Op == NULL || trace_queue->size() <= 0 || Op->type == Operation::SASL) { + eof = 1; + cid_rate.insert( {cid, 100 } ); + fprintf(stderr,"cid %d done\n",cid); + string op_queue1; + string op_queue2; + for (int j = 0; j < 2; j++) { + for (int i = 0; i < OPAQUE_MAX; i++) { + if (op_queue[j+1][i] != NULL) { + if (j == 0) { + op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + } else { + op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + } + } + } + } + fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + break; + } + + trace_queue->pop(); + int l2issued = issue_op(Op); + nissuedl2 += l2issued; + } + + //wait for response (at least nissued) + int l2issued = read_response_l1(); + nissuedl2 += l2issued; + if (nissuedl2 > 0) { + read_response_l2(); + } + + } + +} + +/** + * Tries to consume a binary response (in its entirety) from shared memory. + * + * @param input evBuffer to read response from + * @return true if consumed, false if not enough data in buffer. + */ +static int handle_response(ConnectionMultiApproxShm *conn, unsigned char *input, bool &done, bool &found, int &opcode, uint32_t &opaque, evicted_t *evict, int level) { + // Read the first 24 bytes as a header + //int length = evbuffer_get_length(input); + //if (length < 24) return false; + //binary_header_t* h = + // reinterpret_cast(evbuffer_pullup(input, 24)); + //assert(h); + binary_header_t* h = + reinterpret_cast(input); + + uint32_t bl = ntohl(h->body_len); + uint16_t kl = ntohs(h->key_len); + uint8_t el = h->extra_len; + // Not whole response + int targetLen = 24 + bl; + + opcode = h->opcode; + opaque = ntohl(h->opaque); + uint16_t status = ntohs(h->status); +#ifdef DEBUGMC + fprintf(stderr,"cid: %d handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",conn->get_cid(),level, + h->opcode,ntohl(h->opaque),ntohs(h->key_len),h->extra_len, + ntohl(h->body_len),ntohs(h->status)); +#endif + + pthread_mutex_lock(conn->lock_in[level]); + unsigned char *abuf = bipbuf_poll(conn->bipbuf_in[level],bl); + int tries = 0; + while (abuf == NULL) { + tries++; + if (tries > 1000) { + fprintf(stderr,"more than 1000 tries for cid: %d\n",conn->get_cid()); + break; + } + abuf = bipbuf_poll(conn->bipbuf_in[level],bl); + + } + unsigned char bbuf[1024*1024]; + unsigned char *buf = (unsigned char*) &bbuf; + if (abuf != NULL) { + memcpy(bbuf,abuf,bl); + } + pthread_mutex_unlock(conn->lock_in[level]); + + + // If something other than success, count it as a miss + if (opcode == CMD_GET && status == RESP_NOT_FOUND) { + switch(level) { + case 1: + conn->stats.get_misses_l1++; + break; + case 2: + conn->stats.get_misses_l2++; + conn->stats.get_misses++; + conn->stats.window_get_misses++; + break; + + } + found = false; + //evbuffer_drain(input, targetLen); + + } else if (opcode == CMD_SET && kl > 0 && evict != NULL) { + //evbuffer_drain(input,24); + //unsigned char *buf = evbuffer_pullup(input,bl); + + + evict->clsid = *((uint32_t*)buf); + evict->clsid = ntohl(evict->clsid); + buf += 4; + + evict->serverFlags = *((uint32_t*)buf); + evict->serverFlags = ntohl(evict->serverFlags); + buf += 4; + + evict->evictedFlags = *((uint32_t*)buf); + evict->evictedFlags = ntohl(evict->evictedFlags); + buf += 4; + + + evict->evictedKeyLen = kl; + evict->evictedKey = (char*)malloc(kl+1); + memset(evict->evictedKey,0,kl+1); + memcpy(evict->evictedKey,buf,kl); + buf += kl; + + + evict->evictedLen = bl - kl - el; + evict->evictedData = (char*)malloc(evict->evictedLen); + memcpy(evict->evictedData,buf,evict->evictedLen); + evict->evicted = true; + fprintf(stderr,"class: %u, serverFlags: %u, evictedFlags: %u\n",evict->clsid,evict->serverFlags,evict->evictedFlags); + } else if ( (opcode == CMD_TOUCH && status == RESP_NOT_FOUND) || + (opcode == CMD_DELETE && status == RESP_NOT_FOUND) ) { + found = false; + } + + conn->stats.rx_bytes += targetLen; + done = true; + return targetLen; +} + +int ConnectionMultiApproxShm::read_response_l1() { + + //maybe need mutex etc. + unsigned char input[64]; + pthread_mutex_lock(lock_in[1]); + unsigned char *in = bipbuf_poll(bipbuf_in[1],24); + if (in) { + memcpy(input,in,24); + } + pthread_mutex_unlock(lock_in[1]); + + uint32_t responses_expected = op_queue_size[1]; + Operation *op = NULL; + bool done, found; + found = true; + int bytes_read = 1; + int l2reqs = 0; + uint32_t responses = 0; + while (bytes_read > 0 && responses < responses_expected && input) { + + + int opcode; + uint32_t opaque; + evicted_t *evict = (evicted_t*)malloc(sizeof(evicted_t)); + memset(evict,0,sizeof(evicted_t)); + bytes_read = handle_response(this,input, done, found, opcode, opaque, evict,1); + + if (bytes_read > 0) { + if (opcode == CMD_NOOP) { +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + continue; + } + op = op_queue[1][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l1: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + if (strlen(op->key) < 1) { +#ifdef DEBUGMC + char out2[128]; + sprintf(out2,"conn l1: %u, bad op: %s\n",cid,op->key); + write(2,out2,strlen(out2)); +#endif + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + continue; + } + responses++; + } else { + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + } + + + double now = get_time(); + int wb = 0; + if (options.rand_admit) { + wb = (rand() % options.rand_admit); + } + switch (op->type) { + case Operation::GET: + if (done) { + + int vl = op->valuelen; + if ( !found && (options.getset || options.getsetorset) ) { + /* issue a get a l2 */ + int flags = OP_clu(op); + issue_get_with_len(op->key,vl,now,false, flags | SRC_L1_M | ITEM_L2 | LOG_OP, op); + op->end_time = now; + this->stats.log_get_l1(*op); + //finish_op(op,0); + + } else { + if (OP_incl(op) && ghits >= gloc) { + //int ret = add_to_touch_keys(string(op->key)); + //if (ret == 1) { + issue_touch(op->key,vl,now, ITEM_L2 | SRC_L1_H); + //} + gloc += rand()%(10*2-1)+1; + } + ghits++; + finish_op(op,1); + } + l2reqs++; + } else { + char out[128]; + sprintf(out,"conn l1: %u, not done reading, should do something",cid); + write(2,out,strlen(out)); + } + break; + case Operation::SET: + //if (OP_src(op) == SRC_L1_COPY || + // OP_src(op) == SRC_L2_M) { + // del_copy_keys(string(op->key)); + //} + if (evict->evicted) { + string wb_key(evict->evictedKey); + if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { + //int ret = add_to_wb_keys(wb_key); + //if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_WB | ITEM_DIRTY); + //} + this->stats.incl_wbs++; + l2reqs++; + } else if (evict->evictedFlags & ITEM_EXCL) { + //fprintf(stderr,"excl writeback %s\n",evict->evictedKey); + //strncpy(wb_key,evict->evictedKey,255); + if ( (options.rand_admit && wb == 0) || + (options.threshold && (g_key_hist[wb_key] == 1)) || + (options.wb_all) ) { + //int ret = add_to_wb_keys(wb_key); + //if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + //} + this->stats.excl_wbs++; + l2reqs++; + } + } + if (OP_src(op) == SRC_DIRECT_SET) { + if ( (evict->serverFlags & ITEM_SIZE_CHANGE) || ((evict->serverFlags & ITEM_WAS_HIT) == 0)) { + this->stats.set_misses_l1++; + } else if (OP_excl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_excl_hits_l1++; + } else if (OP_incl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_incl_hits_l1++; + } + } + } + finish_op(op,1); + break; + case Operation::TOUCH: + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); + DIE("not implemented"); + } + + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + pthread_mutex_lock(lock_in[1]); + unsigned char *in = bipbuf_poll(bipbuf_in[1],24); + int tries = 0; + while (input == NULL) { + tries++; + if (tries > 1000) { + fprintf(stderr,"more than 1000 tries for header cid: %d\n",cid); + break; + } + in = bipbuf_poll(bipbuf_in[1],24); + + } + if (in) { + memcpy(input,in,24); + } + pthread_mutex_unlock(lock_in[1]); + + } + return l2reqs; +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMultiApproxShm::read_response_l2() { + + //maybe need mutex etc. + unsigned char input[64]; + pthread_mutex_lock(lock_in[2]); + unsigned char *in = bipbuf_poll(bipbuf_in[2],24); + if (in) { + memcpy(input,in,24); + } + pthread_mutex_unlock(lock_in[2]); + + uint32_t responses_expected = op_queue_size[2]; + Operation *op = NULL; + bool done, found; + found = true; + int bytes_read = 1; + int l2reqs = 0; + uint32_t responses = 0; + + while (bytes_read > 0 && responses < responses_expected && input) { + + int opcode; + uint32_t opaque; + evicted_t *evict = (evicted_t*)malloc(sizeof(evicted_t)); + memset(evict,0,sizeof(evicted_t)); + bytes_read = handle_response(this,input, done, found, opcode, opaque, evict,2); + + if (bytes_read > 0) { + if (opcode == CMD_NOOP) { +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading noop\n",cid); + write(2,out,strlen(out)); +#endif + continue; + } + op = op_queue[2][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"conn l2: %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + if (strlen(op->key) < 1) { +#ifdef DEBUGMC + char out2[128]; + sprintf(out2,"conn l2: %u, bad op: %s\n",cid,op->key); + write(2,out2,strlen(out2)); +#endif + continue; + } + responses++; + } + + + double now = get_time(); + switch (op->type) { + case Operation::GET: + if ( !found && (options.getset || options.getsetorset) ) { // && + //(options.twitter_trace != 1)) { + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | SRC_L2_M | LOG_OP; + //int ret = add_to_copy_keys(string(op->key)); + //if (ret == 1) { + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L1); + if (OP_incl(op)) { + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L2); + last_quiet2 = false; + } + //} + last_quiet1 = false; + finish_op(op,0); // sets read_state = IDLE + + } else { + if (found) { + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; + string key = string(op->key); + const char *data = &random_char[index]; + //int ret = add_to_copy_keys(string(op->key)); + //if (ret == 1) { + issue_set(op->key,data,valuelen, now, flags); + //} + this->stats.copies_to_l1++; + //djb: this is automatically done in the L2 server + //if (OP_excl(op)) { //djb: todo should we delete here for approx or just let it die a slow death? + // issue_delete(op->key,now, ITEM_L2 | SRC_L1_COPY ); + //} + finish_op(op,1); + + } else { + finish_op(op,0); + } + } + break; + case Operation::SET: + //if (OP_src(op) == SRC_WB) { + // del_wb_keys(string(op->key)); + //} + finish_op(op,1); + break; + case Operation::TOUCH: + if (OP_src(op) == SRC_DIRECT_SET || SRC_L1_H) { + int valuelen = op->valuelen; + if (!found) { + int index = lrand48() % (1024 * 1024); + issue_set(op->key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP | SRC_L2_M); + this->stats.set_misses_l2++; + } else { + if (OP_src(op) == SRC_DIRECT_SET) { + issue_touch(op->key,valuelen,now, ITEM_L1 | SRC_L2_H | ITEM_DIRTY); + } + } + //del_touch_keys(string(op->key)); + } + finish_op(op,0); + break; + case Operation::DELETE: + //check to see if it was a hit + //fprintf(stderr," del %s -- %d from %d\n",op->key.c_str(),found,OP_src(op)); + if (OP_src(op) == SRC_DIRECT_SET) { + if (found) { + this->stats.delete_hits_l2++; + } else { + this->stats.delete_misses_l2++; + } + } + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); + DIE("not implemented"); + } + + pthread_mutex_lock(lock_in[2]); + unsigned char *in = bipbuf_poll(bipbuf_in[2],24); + int tries = 0; + while (in == NULL) { + tries++; + if (tries > 2000) { + fprintf(stderr,"more than 2000 tries for header cid: %d\n",cid); + break; + } + in = bipbuf_poll(bipbuf_in[2],24); + + } + if (in) { + memcpy(input,in,24); + } + pthread_mutex_unlock(lock_in[2]); + + } +} + diff --git a/ConnectionOptions.h b/ConnectionOptions.h index 8f33388..96d70fc 100644 --- a/ConnectionOptions.h +++ b/ConnectionOptions.h @@ -7,6 +7,7 @@ typedef struct { int apps; int rand_admit; bool ratelimit; + bool v1callback; int threshold; int wb_all; bool miss_through; diff --git a/SConstruct b/SConstruct index 3ba7049..b39af7c 100644 --- a/SConstruct +++ b/SConstruct @@ -44,8 +44,8 @@ if not conf.CheckFunc('pthread_barrier_init'): env = conf.Finish() -env.Append(CFLAGS = ' -O0 -Wall -g --std=c++17 -lstdc++fs') -env.Append(CPPFLAGS = ' -O0 -Wall -g --std=c++17 -lstdc++fs') +env.Append(CFLAGS = '-O3 -Wall -g --std=c++17 -lstdc++fs') +env.Append(CPPFLAGS = '-O3 -Wall -g --std=c++17 -lstdc++fs') #env.Append(CFLAGS = ' -O3 -Wall -g --std=c++17 -lstdc++fs') #env.Append(CPPFLAGS = ' -O3 -Wall -g --std=c++17 -lstdc++fs') #env.Append(CFLAGS = ' -O3 -Wall -g') @@ -62,7 +62,7 @@ env.Append(CPPFLAGS = ' -O0 -Wall -g --std=c++17 -lstdc++fs') env.Command(['cmdline.cc', 'cmdline.h'], 'cmdline.ggo', 'gengetopt < $SOURCE') src = Split("""mutilate.cc cmdline.cc log.cc distributions.cc util.cc - Connection.cc ConnectionMulti.cc ConnectionMultiApprox.cc Protocol.cc Generator.cc""") + Connection.cc ConnectionMulti.cc ConnectionMultiApprox.cc ConnectionMultiApproxBatch.cc ConnectionMultiApproxShm.cc Protocol.cc Generator.cc bipbuffer.cc""") if not env['HAVE_POSIX_BARRIER']: # USE_POSIX_BARRIER: src += ['barrier.cc'] diff --git a/binary_protocol.h b/binary_protocol.h index 1698149..7c59ddf 100644 --- a/binary_protocol.h +++ b/binary_protocol.h @@ -1,9 +1,12 @@ #ifndef BINARY_PROTOCOL_H #define BINARY_PROTOCOL_H +#include + #define CMD_GET 0x00 #define CMD_GETQ 0x09 #define CMD_TOUCH 0x1c +#define CMD_TOUCH 0x1c #define CMD_DELETE 0x04 #define CMD_SET 0x01 #define CMD_NOOP 0x0a diff --git a/bipbuffer.cc b/bipbuffer.cc new file mode 100644 index 0000000..03e306f --- /dev/null +++ b/bipbuffer.cc @@ -0,0 +1,180 @@ +/** + * Copyright (c) 2011, Willem-Hendrik Thiart + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE.bipbuffer file. + * + * @file + * @author Willem Thiart himself@willemthiart.com + */ + +//#include "stdio.h" +#include + +/* for memcpy */ +#include + +#include "bipbuffer.h" + +static size_t bipbuf_sizeof(const unsigned int size) +{ + return sizeof(bipbuf_t) + size; +} + +int bipbuf_unused(const bipbuf_t* me) +{ + if (1 == me->b_inuse) + /* distance between region B and region A */ + return me->a_start - me->b_end; + else + return me->size - me->a_end; +} + +int bipbuf_size(const bipbuf_t* me) +{ + return me->size; +} + +int bipbuf_used(const bipbuf_t* me) +{ + return (me->a_end - me->a_start) + me->b_end; +} + +void bipbuf_init(bipbuf_t* me, const unsigned int size) +{ + me->a_start = me->a_end = me->b_end = 0; + me->size = size; + me->b_inuse = 0; +} + +bipbuf_t *bipbuf_new(const unsigned int size) +{ + bipbuf_t *me = (bipbuf_t*)malloc(bipbuf_sizeof(size)); + if (!me) + return NULL; + bipbuf_init(me, size); + return me; +} + +void bipbuf_free(bipbuf_t* me) +{ + free(me); +} + +int bipbuf_is_empty(const bipbuf_t* me) +{ + return me->a_start == me->a_end; +} + +/* find out if we should turn on region B + * ie. is the distance from A to buffer's end less than B to A? */ +static void __check_for_switch_to_b(bipbuf_t* me) +{ + if (me->size - me->a_end < me->a_start - me->b_end) + me->b_inuse = 1; +} + +/* TODO: DOCUMENT THESE TWO FUNCTIONS */ +unsigned char *bipbuf_request(bipbuf_t* me, const int size) +{ + if (bipbuf_unused(me) < size) + return 0; + if (1 == me->b_inuse) + { + return (unsigned char *)me->data + me->b_end; + } + else + { + return (unsigned char *)me->data + me->a_end; + } +} + +int bipbuf_push(bipbuf_t* me, const int size) +{ + if (bipbuf_unused(me) < size) + return 0; + + if (1 == me->b_inuse) + { + me->b_end += size; + } + else + { + me->a_end += size; + } + + __check_for_switch_to_b(me); + return size; +} + +int bipbuf_offer(bipbuf_t* me, const unsigned char *data, const int size) +{ + /* not enough space */ + if (bipbuf_unused(me) < size) + return 0; + + if (1 == me->b_inuse) + { + memcpy(me->data + me->b_end, data, size); + me->b_end += size; + } + else + { + memcpy(me->data + me->a_end, data, size); + me->a_end += size; + } + + __check_for_switch_to_b(me); + return size; +} + +unsigned char *bipbuf_peek(const bipbuf_t* me, const unsigned int size) +{ + /* make sure we can actually peek at this data */ + if (me->size < me->a_start + size) + return NULL; + + if (bipbuf_is_empty(me)) + return NULL; + + return (unsigned char *)me->data + me->a_start; +} + +unsigned char *bipbuf_peek_all(const bipbuf_t* me, unsigned int *size) +{ + if (bipbuf_is_empty(me)) + return NULL; + + *size = me->a_end - me->a_start; + return (unsigned char*)me->data + me->a_start; +} + +unsigned char *bipbuf_poll(bipbuf_t* me, const unsigned int size) +{ + if (bipbuf_is_empty(me)) + return NULL; + + /* make sure we can actually poll this data */ + if (me->size < me->a_start + size) + return NULL; + + void *end = me->data + me->a_start; + me->a_start += size; + + /* we seem to be empty.. */ + if (me->a_start == me->a_end) + { + /* replace a with region b */ + if (1 == me->b_inuse) + { + me->a_start = 0; + me->a_end = me->b_end; + me->b_end = me->b_inuse = 0; + } + else + /* safely move cursor back to the start because we are empty */ + me->a_start = me->a_end = 0; + } + + __check_for_switch_to_b(me); + return (unsigned char*) end; +} diff --git a/bipbuffer.h b/bipbuffer.h new file mode 100644 index 0000000..7ccf1f7 --- /dev/null +++ b/bipbuffer.h @@ -0,0 +1,92 @@ +#ifndef BIPBUFFER_H +#define BIPBUFFER_H + +#define BIPBUFSIZE 4*1024*1024 +#include "binary_protocol.h" + +extern "C" { + typedef struct + { + unsigned long int size; + + /* region A */ + unsigned int a_start, a_end; + + /* region B */ + unsigned int b_end; + + /* is B inuse? */ + int b_inuse; + + unsigned char data[BIPBUFSIZE]; + } bipbuf_t; + +/** + * Create a new bip buffer. + * + * malloc()s space + * + * @param[in] size The size of the buffer */ +bipbuf_t *bipbuf_new(const unsigned int size); + +/** + * Initialise a bip buffer. Use memory provided by user. + * + * No malloc()s are performed. + * + * @param[in] size The size of the array */ +void bipbuf_init(bipbuf_t* me, const unsigned int size); + +/** + * Free the bip buffer */ +void bipbuf_free(bipbuf_t *me); + +/* TODO: DOCUMENTATION */ +unsigned char *bipbuf_request(bipbuf_t* me, const int size); +int bipbuf_push(bipbuf_t* me, const int size); + +/** + * @param[in] data The data to be offered to the buffer + * @param[in] size The size of the data to be offered + * @return number of bytes offered */ +int bipbuf_offer(bipbuf_t *me, const unsigned char *data, const int size); + +/** + * Look at data. Don't move cursor + * + * @param[in] len The length of the data to be peeked + * @return data on success, NULL if we can't peek at this much data */ +unsigned char *bipbuf_peek(const bipbuf_t* me, const unsigned int len); + +/** + * Look at data. Don't move cursor + * + * @param[in] len The length of the data returned + * @return data on success, NULL if nothing available */ +unsigned char *bipbuf_peek_all(const bipbuf_t* me, unsigned int *len); + +/** + * Get pointer to data to read. Move the cursor on. + * + * @param[in] len The length of the data to be polled + * @return pointer to data, NULL if we can't poll this much data */ +unsigned char *bipbuf_poll(bipbuf_t* me, const unsigned int size); + +/** + * @return the size of the bipbuffer */ +int bipbuf_size(const bipbuf_t* me); + +/** + * @return 1 if buffer is empty; 0 otherwise */ +int bipbuf_is_empty(const bipbuf_t* me); + +/** + * @return how much space we have assigned */ +int bipbuf_used(const bipbuf_t* cb); + +/** + * @return bytes of unused space */ +int bipbuf_unused(const bipbuf_t* me); + +} +#endif /* BIPBUFFER_H */ diff --git a/cmdline.ggo b/cmdline.ggo index 9580e00..f72097d 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -10,7 +10,9 @@ option "quiet" - "Disable log messages." text "\nBasic options:" +option "use_shm" - "use shared memory" option "ratelimit" - "limit conns from exceeding each other in requests" +option "v1callback" - "use v1 readcallbacks" option "server" s "Memcached server hostname[:port]. \ Repeat to specify multiple servers." string multiple option "unix_socket" - "Use UNIX socket instead of TCP." diff --git a/mutilate.cc b/mutilate.cc index 053eafd..772cf13 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -1629,7 +1629,7 @@ void do_mutilate(const vector& servers, options_t& options, event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); - } else if (servers.size() == 2 && ! ( args.approx_given || args.approx_batch_given)) { + } else if (servers.size() == 2 && ! ( args.approx_given || args.approx_batch_given || args.use_shm_given)) { vector connections; vector server_lead; @@ -2079,7 +2079,12 @@ void do_mutilate(const vector& servers, options_t& options, } } if (restart) continue; - else break; + else { + for (ConnectionMultiApproxBatch *conn: connections) { + fprintf(stderr,"tid %ld, cid: %d\n",pthread_self(),conn->get_cid()); + } + break; + } } @@ -2101,6 +2106,61 @@ void do_mutilate(const vector& servers, options_t& options, event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); + } else if (servers.size() == 2 && args.use_shm_given) { + vector connections; + + int conns = args.measure_connections_given ? args.measure_connections_arg : + options.connections; + + srand(time(NULL)); + for (int c = 0; c < conns; c++) { + + + ConnectionMultiApproxShm* conn = new ConnectionMultiApproxShm(options,args.agentmode_given ? false : true); + int connected = 0; + if (conn && conn->do_connect()) { + connected = 1; + } + int cid = conn->get_cid(); + + if (connected) { + fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front()->key); + if (g_lock != NULL) { + conn->set_g_wbkeys(g_wb_keys); + conn->set_lock(g_lock); + } + conn->set_queue(trace_queue->at(cid)); + connections.push_back(conn); + } else { + fprintf(stderr,"conn multi: %d, not connected!!\n",c); + + } + } + + // wait for all threads to reach here + pthread_barrier_wait(&barrier); + //fprintf(stderr,"Start = %f\n", start); + double start = get_time(); + double now = start; + for (ConnectionMultiApproxShm *conn: connections) { + conn->start_time = now; + conn->drive_write_machine_shm(now); + } + + + + if (master && !args.scan_given && !args.search_given) + V("stopped at %f options.time = %d", get_time(), options.time); + + // Tear-down and accumulate stats. + for (ConnectionMultiApproxShm *conn: connections) { + stats.accumulate(conn->stats); + delete conn; + } + + stats.start = start; + stats.stop = now; + } } @@ -2117,6 +2177,7 @@ void args_to_options(options_t* options) { options->threshold = args.threshold_arg; options->wb_all = args.wb_all_arg; options->ratelimit = args.ratelimit_given; + options->v1callback = args.v1callback_given; if (args.inclusives_given) { memset(options->inclusives,0,256); strncpy(options->inclusives,args.inclusives_arg,256); From ca7fc37ec59016e706ae08099a69f547bf3857aa Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Sun, 29 May 2022 17:32:07 -0400 Subject: [PATCH 54/57] updated shm --- ConnectionMultiApproxShm.cc | 187 +++++++++++++++++++++++++++--------- SConstruct | 4 +- 2 files changed, 146 insertions(+), 45 deletions(-) diff --git a/ConnectionMultiApproxShm.cc b/ConnectionMultiApproxShm.cc index 1db2e73..868a854 100644 --- a/ConnectionMultiApproxShm.cc +++ b/ConnectionMultiApproxShm.cc @@ -91,7 +91,7 @@ #define OP_excl(op) ((op)->flags & ITEM_EXCL) #define OP_set_flag(op,flag) ((op))->flags |= flag; -//#define DEBUGMC +#define DEBUGMC //#define DEBUGS //using namespace folly; using namespace moodycamel; @@ -761,9 +761,20 @@ int ConnectionMultiApproxShm::issue_get_with_len(Operation *pop, double now, boo } h.opaque = htonl(pop->opaque); + int res = 0; pthread_mutex_lock(lock_out[level]); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + if (res != 24) { + fprintf(stderr,"failed offer 24 get level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); + if (res != keylen) { + fprintf(stderr,"failed offer %d get level %d\n",keylen,level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } pthread_mutex_unlock(lock_out[level]); stats.tx_bytes += 24 + keylen; @@ -828,9 +839,20 @@ int ConnectionMultiApproxShm::issue_get_with_len(const char* key, int valuelen, h.opaque = htonl(pop->opaque); + int res = 0; pthread_mutex_lock(lock_out[level]); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)key,keylen); + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + if (res != 24) { + fprintf(stderr,"failed offer 24 get level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); + if (res != keylen) { + fprintf(stderr,"failed offer %d get level %d\n",keylen,level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } pthread_mutex_unlock(lock_out[level]); stats.tx_bytes += 24 + keylen; @@ -890,10 +912,26 @@ int ConnectionMultiApproxShm::issue_touch(const char* key, int valuelen, double exp = htonl(flags); } + int res = 0; pthread_mutex_lock(lock_out[level]); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)key,keylen); + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + if (res != 24) { + fprintf(stderr,"failed offer 24 touch level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + if (res != keylen) { + bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); + fprintf(stderr,"failed offer 4 touch level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); + if (res != keylen) { + fprintf(stderr,"failed offer %d touch level %d\n",keylen,level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } pthread_mutex_unlock(lock_out[level]); stats.tx_bytes += 24 + keylen; @@ -1012,13 +1050,38 @@ int ConnectionMultiApproxShm::issue_set(Operation *pop, const char* value, doubl uint32_t f = htonl(flags); uint32_t exp = 0; - + int res = 0; pthread_mutex_lock(lock_out[level]); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)&f,4); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)value,pop->valuelen); + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + if (res != 24) { + fprintf(stderr,"failed offer 24 set level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&f,4); + if (res != 4) { + fprintf(stderr,"failed offer 4 set level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); + if (res != 4) { + fprintf(stderr,"failed offer 4 set level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); + if (res != keylen) { + fprintf(stderr,"failed offer %d set level %d\n",keylen,level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)value,pop->valuelen); + if (res != pop->valuelen) { + fprintf(stderr,"failed offer %d set level %d\n",pop->valuelen,level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } pthread_mutex_unlock(lock_out[level]); stats.tx_bytes += pop->valuelen + 32 + keylen; @@ -1069,13 +1132,38 @@ int ConnectionMultiApproxShm::issue_set(const char* key, const char* value, int uint32_t f = htonl(flags); uint32_t exp = 0; - + int res = 0; pthread_mutex_lock(lock_out[level]); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)&f,4); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)key,keylen); - bipbuf_offer(bipbuf_out[level],(const unsigned char*)value,length); + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + if (res != 24) { + fprintf(stderr,"failed offer 24 set level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&f,4); + if (res != 4) { + fprintf(stderr,"failed offer 4 set level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); + if (res != 4) { + fprintf(stderr,"failed offer 4 set level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)key,keylen); + if (res != keylen) { + fprintf(stderr,"failed offer %d set level %d\n",keylen,level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)value,length); + if (res != length) { + fprintf(stderr,"failed offer %d set level %d\n",length,level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } pthread_mutex_unlock(lock_out[level]); stats.tx_bytes += length + 32 + keylen; @@ -1268,6 +1356,7 @@ void ConnectionMultiApproxShm::drive_write_machine_shm(double now) { trace_queue->pop(); int l2issued = issue_op(Op); nissuedl2 += l2issued; + nissued++; } //wait for response (at least nissued) @@ -1398,7 +1487,10 @@ int ConnectionMultiApproxShm::read_response_l1() { memcpy(input,in,24); } pthread_mutex_unlock(lock_in[1]); - + if (in == NULL) { + return 0; + } + uint32_t responses_expected = op_queue_size[1]; Operation *op = NULL; bool done, found; @@ -1546,20 +1638,23 @@ int ConnectionMultiApproxShm::read_response_l1() { } pthread_mutex_lock(lock_in[1]); unsigned char *in = bipbuf_poll(bipbuf_in[1],24); - int tries = 0; - while (input == NULL) { - tries++; - if (tries > 1000) { - fprintf(stderr,"more than 1000 tries for header cid: %d\n",cid); - break; - } - in = bipbuf_poll(bipbuf_in[1],24); - - } + //int tries = 0; + //while (input == NULL) { + // tries++; + // if (tries > 1000) { + // fprintf(stderr,"more than 1000 tries for header cid: %d\n",cid); + // break; + // } + // in = bipbuf_poll(bipbuf_in[1],24); + // + //} if (in) { memcpy(input,in,24); + pthread_mutex_unlock(lock_in[1]); + } else { + pthread_mutex_unlock(lock_in[1]); + break; } - pthread_mutex_unlock(lock_in[1]); } return l2reqs; @@ -1578,6 +1673,9 @@ void ConnectionMultiApproxShm::read_response_l2() { memcpy(input,in,24); } pthread_mutex_unlock(lock_in[2]); + if (in == NULL) { + return; + } uint32_t responses_expected = op_queue_size[2]; Operation *op = NULL; @@ -1706,20 +1804,23 @@ void ConnectionMultiApproxShm::read_response_l2() { pthread_mutex_lock(lock_in[2]); unsigned char *in = bipbuf_poll(bipbuf_in[2],24); - int tries = 0; - while (in == NULL) { - tries++; - if (tries > 2000) { - fprintf(stderr,"more than 2000 tries for header cid: %d\n",cid); - break; - } - in = bipbuf_poll(bipbuf_in[2],24); - - } + //int tries = 0; + //while (in == NULL) { + // tries++; + // if (tries > 2000) { + // fprintf(stderr,"more than 2000 tries for header cid: %d\n",cid); + // break; + // } + // in = bipbuf_poll(bipbuf_in[2],24); + // + //} if (in) { memcpy(input,in,24); + pthread_mutex_unlock(lock_in[2]); + } else { + pthread_mutex_unlock(lock_in[2]); + break; } - pthread_mutex_unlock(lock_in[2]); } } diff --git a/SConstruct b/SConstruct index b39af7c..212fb8f 100644 --- a/SConstruct +++ b/SConstruct @@ -44,8 +44,8 @@ if not conf.CheckFunc('pthread_barrier_init'): env = conf.Finish() -env.Append(CFLAGS = '-O3 -Wall -g --std=c++17 -lstdc++fs') -env.Append(CPPFLAGS = '-O3 -Wall -g --std=c++17 -lstdc++fs') +env.Append(CFLAGS = '-O0 -Wall -g --std=c++17 -lstdc++fs') +env.Append(CPPFLAGS = '-O0 -Wall -g --std=c++17 -lstdc++fs') #env.Append(CFLAGS = ' -O3 -Wall -g --std=c++17 -lstdc++fs') #env.Append(CPPFLAGS = ' -O3 -Wall -g --std=c++17 -lstdc++fs') #env.Append(CFLAGS = ' -O3 -Wall -g') From 50cdc24f044e8cba360f9ae34adfd8b364113460 Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Wed, 1 Jun 2022 22:38:34 -0400 Subject: [PATCH 55/57] updates --- Connection.h | 3 + ConnectionMultiApproxShm.cc | 451 ++++++++++++++---------------------- mutilate.cc | 6 +- 3 files changed, 174 insertions(+), 286 deletions(-) diff --git a/Connection.h b/Connection.h index f06fd13..8df47c3 100644 --- a/Connection.h +++ b/Connection.h @@ -802,6 +802,7 @@ class ConnectionMultiApproxShm { pthread_mutex_t* lock; unordered_map> *g_wb_keys; queue *trace_queue; + queue extra_queue; // state machine functions / event processing void pop_op(Operation *op); @@ -821,6 +822,8 @@ class ConnectionMultiApproxShm { int issue_get_with_len(Operation *pop, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); int issue_set(const char* key, const char* value, int length, double now, uint32_t flags); int issue_set(Operation *pop, const char* value, double now, uint32_t flags); + int offer_set(Operation *pop, int extra = 0); + int offer_get(Operation *pop, int extra = 0); int read_response_l1(); void read_response_l2(); diff --git a/ConnectionMultiApproxShm.cc b/ConnectionMultiApproxShm.cc index 868a854..a7b6951 100644 --- a/ConnectionMultiApproxShm.cc +++ b/ConnectionMultiApproxShm.cc @@ -91,7 +91,7 @@ #define OP_excl(op) ((op)->flags & ITEM_EXCL) #define OP_set_flag(op,flag) ((op))->flags |= flag; -#define DEBUGMC +//#define DEBUGMC //#define DEBUGS //using namespace folly; using namespace moodycamel; @@ -583,128 +583,49 @@ void ConnectionMultiApproxShm::reset() { /** - * Get/Set or Set Style - * If a GET command: Issue a get first, if not found then set - * If trace file (or prob. write) says to set, then set it + * Issue a get request to the server. */ -int ConnectionMultiApproxShm::issue_getsetorset(double now) { - - - - int ret = 0; - int nissued = 0; - - //while (nissued < 1) { - - //pthread_mutex_lock(lock); - //if (!trace_queue->empty()) { - - /* check if in global wb queue */ - //double percent = (double)total/((double)trace_queue_n) * 100; - //if (percent > o_percent+2) { - // //update the percentage table and see if we should execute - // if (options.ratelimit) { - // double min_percent = 1000; - // auto it = cid_rate.begin(); - // while (it != cid_rate.end()) { - // if (it->second < min_percent) { - // min_percent = it->second; - // } - // ++it; - // } - - // if (percent > min_percent+2) { - // struct timeval tv; - // tv.tv_sec = 0; - // tv.tv_usec = 100; - // int good = 0; - // if (!event_pending(timer, EV_TIMEOUT, NULL)) { - // good = evtimer_add(timer, &tv); - // } - // if (good != 0) { - // fprintf(stderr,"eventimer is messed up!\n"); - // return 2; - // } - // return 1; - // } - // } - // cid_rate.insert( {cid, percent}); - // fprintf(stderr,"%f,%d,%.4f\n",now,cid,percent); - // o_percent = percent; - //} - // - - Operation *Op = trace_queue->front(); - //Operation *Op = g_trace_queue.dequeue(); - - if (Op == NULL || trace_queue->size() <= 0 || Op->type == Operation::SASL) { - eof = 1; - cid_rate.insert( {cid, 100 } ); - fprintf(stderr,"cid %d done\n",cid); - string op_queue1; - string op_queue2; - for (int j = 0; j < 2; j++) { - for (int i = 0; i < OPAQUE_MAX; i++) { - if (op_queue[j+1][i] != NULL) { - if (j == 0) { - op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; - } else { - op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; - } - } - } - } - fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); - return 1; - } - - trace_queue->pop(); - - - //trace_queue->pop(); - - //pthread_mutex_lock(lock); - //auto check = wb_keys.find(string(Op->key)); - //if (check != wb_keys.end()) { - // check->second.push_back(Op); - // return 0; - //} - //pthread_mutex_unlock(lock); - //pthread_mutex_unlock(lock); - //struct timeval tv; - //double delay; - //delay = last_rx + 0.00025 - now; - //double_to_tv(delay,&tv); - //int good = 0; - ////if (!event_pending(timer, EV_TIMEOUT, NULL)) { - //good = evtimer_add(timer, &tv); - ////} - //if (good != 0) { - // fprintf(stderr,"eventimer is messed up in checking for key: %s\n",Op->key); - // return 2; - //} - //return 1; - //} else { - //pthread_mutex_unlock(lock); - int issued = issue_op(Op); - if (issued) { - nissued++; - total++; - } else { - fprintf(stderr,"failed to issue line: %s, vl: %d\n",Op->key,Op->valuelen); - } - //} +int ConnectionMultiApproxShm::offer_get(Operation *pop, int extra) { + + uint16_t keylen = strlen(pop->key); + int level = FLAGS_level(pop->flags); - //} else { - // return 1; - //} - //} - //if (last_quiet1) { - // issue_noop(now,1); - // last_quiet1 = false; - //} - return ret; + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_GET, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + //if (quiet) { + // h.opcode = CMD_GETQ; + //} + h.opaque = htonl(pop->opaque); + + int res = 0; + pthread_mutex_lock(lock_out[level]); + int gtg = bipbuf_unused(bipbuf_out[level]) > (int)(24+keylen) ? 1 : 0; + if (gtg) { + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + if (res != 24) { + fprintf(stderr,"failed offer 24 get level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); + if (res != keylen) { + fprintf(stderr,"failed offer %d get level %d\n",keylen,level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + if (extra == 1) { + extra_queue.pop(); + } + } else { + if (extra == 0) { + extra_queue.push(pop); + } + } + pthread_mutex_unlock(lock_out[level]); + return 1; } @@ -738,7 +659,6 @@ int ConnectionMultiApproxShm::issue_get_with_len(Operation *pop, double now, boo } op_queue[level][pop->opaque] = pop; - //op_queue[level].push(op); op_queue_size[level]++; #ifdef DEBUGS fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,pop->key,pop->valuelen,level,flags,pop->opaque); @@ -748,36 +668,9 @@ int ConnectionMultiApproxShm::issue_get_with_len(Operation *pop, double now, boo opaque[level] = 1; } - //if (read_state == IDLE) read_state = WAITING_FOR_GET; - uint16_t keylen = strlen(pop->key); - - - // each line is 4-bytes - binary_header_t h = { 0x80, CMD_GET, htons(keylen), - 0x00, 0x00, htons(0), - htonl(keylen) }; - if (quiet) { - h.opcode = CMD_GETQ; - } - h.opaque = htonl(pop->opaque); - - int res = 0; - pthread_mutex_lock(lock_out[level]); - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); - if (res != 24) { - fprintf(stderr,"failed offer 24 get level %d\n",level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); - if (res != keylen) { - fprintf(stderr,"failed offer %d get level %d\n",keylen,level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - pthread_mutex_unlock(lock_out[level]); - stats.tx_bytes += 24 + keylen; + offer_get(pop,0); + stats.tx_bytes += 24 + strlen(pop->key); return 1; } @@ -815,7 +708,6 @@ int ConnectionMultiApproxShm::issue_get_with_len(const char* key, int valuelen, pop->l1 = l1; } op_queue[level][pop->opaque] = pop; - //op_queue[level].push(op); op_queue_size[level]++; #ifdef DEBUGS @@ -826,36 +718,9 @@ int ConnectionMultiApproxShm::issue_get_with_len(const char* key, int valuelen, opaque[level] = 1; } - //if (read_state == IDLE) read_state = WAITING_FOR_GET; - uint16_t keylen = strlen(key); - - // each line is 4-bytes - binary_header_t h = { 0x80, CMD_GET, htons(keylen), - 0x00, 0x00, htons(0), - htonl(keylen) }; - if (quiet) { - h.opcode = CMD_GETQ; - } - h.opaque = htonl(pop->opaque); - - - int res = 0; - pthread_mutex_lock(lock_out[level]); - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); - if (res != 24) { - fprintf(stderr,"failed offer 24 get level %d\n",level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); - if (res != keylen) { - fprintf(stderr,"failed offer %d get level %d\n",keylen,level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - pthread_mutex_unlock(lock_out[level]); - stats.tx_bytes += 24 + keylen; + offer_get(pop,0); + stats.tx_bytes += 24 + strlen(pop->key);; return 1; } @@ -1037,55 +902,84 @@ int ConnectionMultiApproxShm::issue_set(Operation *pop, const char* value, doubl if (opaque[level] > OPAQUE_MAX) { opaque[level] = 1; } + + offer_set(pop); + + + stats.tx_bytes += pop->valuelen + 32 + strlen(pop->key); + return 1; +} + +/** + * Issue a set request to the server. + */ +int ConnectionMultiApproxShm::offer_set(Operation *pop, int extra) { uint16_t keylen = strlen(pop->key); - + uint32_t length = pop->valuelen; + int level = FLAGS_level(pop->flags); // each line is 4-bytes binary_header_t h = { 0x80, CMD_SET, htons(keylen), 0x08, 0x00, htons(0), - htonl(keylen + 8 + pop->valuelen) }; + htonl(keylen + 8 + length) }; h.opaque = htonl(pop->opaque); - uint32_t f = htonl(flags); + uint32_t f = htonl(pop->flags); uint32_t exp = 0; - + int ret = 0; int res = 0; pthread_mutex_lock(lock_out[level]); - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); - if (res != 24) { - fprintf(stderr,"failed offer 24 set level %d\n",level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&f,4); - if (res != 4) { - fprintf(stderr,"failed offer 4 set level %d\n",level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); - if (res != 4) { - fprintf(stderr,"failed offer 4 set level %d\n",level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); - if (res != keylen) { - fprintf(stderr,"failed offer %d set level %d\n",keylen,level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)value,pop->valuelen); - if (res != pop->valuelen) { - fprintf(stderr,"failed offer %d set level %d\n",pop->valuelen,level); - pthread_mutex_unlock(lock_out[level]); - return 0; + int gtg = bipbuf_unused(bipbuf_out[level]) > (int)(32+pop->valuelen) ? 1 : 0; + if (gtg) { + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); + if (res != 24) { + fprintf(stderr,"failed offer 24 set level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&f,4); + if (res != 4) { + fprintf(stderr,"failed offer 4 set level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); + if (res != 4) { + fprintf(stderr,"failed offer 4 set level %d\n",level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)pop->key,keylen); + if (res != keylen) { + fprintf(stderr,"failed offer %d set level %d\n",keylen,level); + pthread_mutex_unlock(lock_out[level]); + return 0; + } + int i = 0; + int index = lrand48() % (1024 * 1024); + const char *value = &random_char[index]; + while ((res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)value,length)) != (int)length) { + pthread_mutex_unlock(lock_out[level]); + i++; + if (i > 1000) { + fprintf(stderr,"failed offer %d set level %d\n",length,level); + break; + } + pthread_mutex_lock(lock_out[level]); + } + if (extra == 1) { + extra_queue.pop(); + } + ret = 1; + } else { + if (extra == 0) { + extra_queue.push(pop); + } + ret = 0; } pthread_mutex_unlock(lock_out[level]); - - stats.tx_bytes += pop->valuelen + 32 + keylen; - return 1; + return ret; } /** @@ -1111,7 +1005,6 @@ int ConnectionMultiApproxShm::issue_set(const char* key, const char* value, int pop->flags = flags; pop->clsid = get_class(length,strlen(key)); op_queue[level][pop->opaque] = pop; - //op_queue[level].push(op); op_queue_size[level]++; #ifdef DEBUGS fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,key,length,level,flags,pop->opaque); @@ -1121,52 +1014,8 @@ int ConnectionMultiApproxShm::issue_set(const char* key, const char* value, int opaque[level] = 1; } - uint16_t keylen = strlen(key); - - // each line is 4-bytes - binary_header_t h = { 0x80, CMD_SET, htons(keylen), - 0x08, 0x00, htons(0), - htonl(keylen + 8 + length) }; - h.opaque = htonl(pop->opaque); - - uint32_t f = htonl(flags); - uint32_t exp = 0; - - int res = 0; - pthread_mutex_lock(lock_out[level]); - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&h,24); - if (res != 24) { - fprintf(stderr,"failed offer 24 set level %d\n",level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&f,4); - if (res != 4) { - fprintf(stderr,"failed offer 4 set level %d\n",level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)&exp,4); - if (res != 4) { - fprintf(stderr,"failed offer 4 set level %d\n",level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)key,keylen); - if (res != keylen) { - fprintf(stderr,"failed offer %d set level %d\n",keylen,level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - res = bipbuf_offer(bipbuf_out[level],(const unsigned char*)value,length); - if (res != length) { - fprintf(stderr,"failed offer %d set level %d\n",length,level); - pthread_mutex_unlock(lock_out[level]); - return 0; - } - pthread_mutex_unlock(lock_out[level]); - - stats.tx_bytes += length + 32 + keylen; + offer_set(pop); + stats.tx_bytes += length + 32 + strlen(key); return 1; } @@ -1327,9 +1176,23 @@ bool ConnectionMultiApproxShm::check_exit_condition(double now) { void ConnectionMultiApproxShm::drive_write_machine_shm(double now) { while (trace_queue->size() > 0) { + int extra_tries = extra_queue.size(); + for (int i = 0; i < extra_tries; i++) { + Operation *Op = extra_queue.front(); + switch(Op->type) + { + case Operation::GET: + offer_get(Op,1); + break; + case Operation::SET: + offer_set(Op,1); + break; + } + } + int nissued = 0; int nissuedl2 = 0; - while (nissued < options.depth) { + while (nissued < options.depth && extra_queue.size() == 0) { Operation *Op = trace_queue->front(); if (Op == NULL || trace_queue->size() <= 0 || Op->type == Operation::SASL) { @@ -1350,13 +1213,30 @@ void ConnectionMultiApproxShm::drive_write_machine_shm(double now) { } } fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); - break; + return; } - - trace_queue->pop(); - int l2issued = issue_op(Op); - nissuedl2 += l2issued; - nissued++; + int gtg = 0; + pthread_mutex_lock(lock_out[1]); + switch(Op->type) + { + case Operation::GET: + gtg = bipbuf_unused(bipbuf_out[1]) > (int)(24+strlen(Op->key)) ? 1 : 0; + break; + case Operation::SET: + gtg = bipbuf_unused(bipbuf_out[1]) > (int)(32+Op->valuelen) ? 1 : 0; + break; + } + pthread_mutex_unlock(lock_out[1]); + + + if (gtg) { + trace_queue->pop(); + int l2issued = issue_op(Op); + nissuedl2 += l2issued; + nissued++; + } else { + break; + } } //wait for response (at least nissued) @@ -1402,22 +1282,24 @@ static int handle_response(ConnectionMultiApproxShm *conn, unsigned char *input, #endif pthread_mutex_lock(conn->lock_in[level]); - unsigned char *abuf = bipbuf_poll(conn->bipbuf_in[level],bl); + unsigned char *abuf; int tries = 0; - while (abuf == NULL) { + while ((abuf = bipbuf_poll(conn->bipbuf_in[level],targetLen)) == NULL) { + pthread_mutex_unlock(conn->lock_in[level]); tries++; - if (tries > 1000) { - fprintf(stderr,"more than 1000 tries for cid: %d\n",conn->get_cid()); - break; + if (tries > 100) { + //fprintf(stderr,"more than 10000 tries for cid: %d for length %d\n",conn->get_cid(),targetLen); + return 0; + } - abuf = bipbuf_poll(conn->bipbuf_in[level],bl); - + pthread_mutex_lock(conn->lock_in[level]); } unsigned char bbuf[1024*1024]; unsigned char *buf = (unsigned char*) &bbuf; if (abuf != NULL) { - memcpy(bbuf,abuf,bl); + memcpy(bbuf,abuf,targetLen); } + buf += 24; pthread_mutex_unlock(conn->lock_in[level]); @@ -1482,7 +1364,7 @@ int ConnectionMultiApproxShm::read_response_l1() { //maybe need mutex etc. unsigned char input[64]; pthread_mutex_lock(lock_in[1]); - unsigned char *in = bipbuf_poll(bipbuf_in[1],24); + unsigned char *in = bipbuf_peek(bipbuf_in[1],24); if (in) { memcpy(input,in,24); } @@ -1544,6 +1426,7 @@ int ConnectionMultiApproxShm::read_response_l1() { if (evict->evictedData) free(evict->evictedData); free(evict); } + return 0; } @@ -1637,7 +1520,7 @@ int ConnectionMultiApproxShm::read_response_l1() { free(evict); } pthread_mutex_lock(lock_in[1]); - unsigned char *in = bipbuf_poll(bipbuf_in[1],24); + unsigned char *in = bipbuf_peek(bipbuf_in[1],24); //int tries = 0; //while (input == NULL) { // tries++; @@ -1668,7 +1551,7 @@ void ConnectionMultiApproxShm::read_response_l2() { //maybe need mutex etc. unsigned char input[64]; pthread_mutex_lock(lock_in[2]); - unsigned char *in = bipbuf_poll(bipbuf_in[2],24); + unsigned char *in = bipbuf_peek(bipbuf_in[2],24); if (in) { memcpy(input,in,24); } @@ -1718,7 +1601,9 @@ void ConnectionMultiApproxShm::read_response_l2() { continue; } responses++; - } + } else { + return; + } double now = get_time(); @@ -1803,7 +1688,7 @@ void ConnectionMultiApproxShm::read_response_l2() { } pthread_mutex_lock(lock_in[2]); - unsigned char *in = bipbuf_poll(bipbuf_in[2],24); + unsigned char *in = bipbuf_peek(bipbuf_in[2],24); //int tries = 0; //while (in == NULL) { // tries++; diff --git a/mutilate.cc b/mutilate.cc index 772cf13..a9b415b 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -536,7 +536,7 @@ int main(int argc, char **argv) { vector servers; for (unsigned int s = 0; s < args.server_given; s++) { - if (options.unix_socket) { + if (options.unix_socket || args.use_shm_given) { servers.push_back(string(args.server_arg[s])); } else { servers.push_back(name_to_ipaddr(string(args.server_arg[s]))); @@ -1786,7 +1786,7 @@ void do_mutilate(const vector& servers, options_t& options, event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); - } else if (servers.size() == 2 && args.approx_given && !args.approx_batch_given) { + } else if (servers.size() == 2 && args.approx_given && !args.approx_batch_given && !args.use_shm_given) { vector connections; vector server_lead; @@ -1944,7 +1944,7 @@ void do_mutilate(const vector& servers, options_t& options, evdns_base_free(evdns, 0); event_base_free(base); - } else if (servers.size() == 2 && args.approx_batch_given) { + } else if (servers.size() == 2 && args.approx_batch_given && !args.use_shm_given) { vector connections; vector server_lead; From e878bbfd7b0d86fefeeb9ff73c07f2790f31b5ca Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Sat, 11 Jun 2022 06:52:42 -0400 Subject: [PATCH 56/57] updates --- Connection.h | 170 ++++ ConnectionMultiApproxBatchShm.cc | 1624 ++++++++++++++++++++++++++++++ ConnectionMultiApproxShm.cc | 104 +- cmdline.ggo | 1 + mutilate.cc | 71 +- 5 files changed, 1942 insertions(+), 28 deletions(-) create mode 100644 ConnectionMultiApproxBatchShm.cc diff --git a/Connection.h b/Connection.h index 8df47c3..7125eaa 100644 --- a/Connection.h +++ b/Connection.h @@ -733,6 +733,8 @@ class ConnectionMultiApproxShm { bipbuf_t* bipbuf_out[3]; pthread_mutex_t* lock_in[3]; pthread_mutex_t* lock_out[3]; + pthread_cond_t* cond_in[3]; + pthread_cond_t* cond_out[3]; private: string hostname1; @@ -841,4 +843,172 @@ class ConnectionMultiApproxShm { bool consume_resp_line(evbuffer *input, bool &done); }; +class ConnectionMultiApproxBatchShm { +public: + ConnectionMultiApproxBatchShm(options_t options, bool sampling = true); + + ~ConnectionMultiApproxBatchShm(); + + int do_connect(); + + double start_time; // Time when this connection began operations. + ConnectionStats stats; + options_t options; + + bool is_ready() { return read_state == IDLE; } + void set_priority(int pri); + + void start_loading(); + void reset(); + bool check_exit_condition(double now = 0.0); + + void read_callback1(); + void read_callback2(); + + int eof; + uint32_t get_cid(); + //void set_queue(ConcurrentQueue *a_trace_queue); + int add_to_wb_keys(string wb_key); + int add_to_copy_keys(string key); + int add_to_touch_keys(string key); + void del_wb_keys(string wb_key); + void del_copy_keys(string key); + void del_touch_keys(string key); + void set_g_wbkeys(unordered_map> *a_wb_keys); + void set_queue(queue *a_trace_queue); + void set_lock(pthread_mutex_t* a_lock); + size_t handle_response_batch(unsigned char *rbuf_pos, resp_t *resp, + size_t read_bytes, size_t consumed_bytes, + int level, int extra); + void drive_write_machine_shm(double now = 0.0); + bipbuf_t* bipbuf_in[3]; + bipbuf_t* bipbuf_out[3]; + pthread_mutex_t* lock_in[3]; + pthread_mutex_t* lock_out[3]; + pthread_cond_t* cond_in[3]; + pthread_cond_t* cond_out[3]; + +private: + string hostname1; + string hostname2; + string port; + + double o_percent; + int trace_queue_n; + + struct event *timer; // Used to control inter-transmission time. + double next_time; // Inter-transmission time parameters. + double last_rx; // Used to moderate transmission rate. + double last_tx; + + enum read_state_enum { + INIT_READ, + CONN_SETUP, + LOADING, + IDLE, + WAITING_FOR_GET, + WAITING_FOR_SET, + WAITING_FOR_DELETE, + MAX_READ_STATE, + }; + + enum write_state_enum { + INIT_WRITE, + ISSUING, + WAITING_FOR_TIME, + WAITING_FOR_OPQ, + MAX_WRITE_STATE, + }; + + read_state_enum read_state; + write_state_enum write_state; + + // Parameters to track progress of the data loader. + int loader_issued, loader_completed; + + uint32_t *opaque; + int *issue_buf_size; + int *issue_buf_n; + unsigned char **issue_buf_pos; + unsigned char **issue_buf; + bool last_quiet1; + bool last_quiet2; + uint32_t total; + uint32_t cid; + uint32_t gets; + uint32_t gloc; + uint32_t ghits; + uint32_t sloc; + uint32_t esets; + uint32_t isets; + uint32_t iloc; + + uint32_t buffer_size_; + unsigned char* buffer_write[MAX_LEVELS]; + unsigned char* buffer_read[MAX_LEVELS]; + unsigned char* buffer_write_pos[MAX_LEVELS]; + unsigned char* buffer_read_pos[MAX_LEVELS]; + unsigned char* buffer_lasthdr[MAX_LEVELS]; + unsigned char* buffer_leftover[MAX_LEVELS]; + uint32_t buffer_read_n[MAX_LEVELS]; + uint32_t buffer_write_n[MAX_LEVELS]; + uint32_t buffer_read_nbytes[MAX_LEVELS]; + uint32_t buffer_write_nbytes[MAX_LEVELS]; + + + //std::vector> op_queue; + Operation ***op_queue; + uint32_t *op_queue_size; + uint32_t *issued_queue; + + + Generator *valuesize; + Generator *keysize; + KeyGenerator *keygen; + Generator *iagen; + pthread_mutex_t* lock; + unordered_map> *g_wb_keys; + queue *trace_queue; + queue extra_queue; + + // state machine functions / event processing + void pop_op(Operation *op); + void output_op(Operation *op, int type, bool was_found); + //void finish_op(Operation *op); + void finish_op(Operation *op,int was_hit); + int issue_getsetorset(double now = 0.0); + + // request functions + void issue_sasl(); + int issue_op(Operation* op); + int issue_noop(int level = 1); + int issue_touch(const char* key, int valuelen, double now, int level); + int issue_delete(const char* key, double now, uint32_t flags); + int issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); + int issue_get_with_len(Operation *pop, double now, bool quiet, uint32_t flags, Operation *l1 = NULL); + int issue_set(const char* key, const char* value, int length, double now, uint32_t flags); + int issue_set(Operation *pop, const char* value, double now, uint32_t flags); + int offer_set(Operation *pop, int extra = 0); + int offer_get(Operation *pop, int extra = 0); + int send_write_buffer(int level); + size_t fill_read_buffer(int level, int *extra); + int add_get_op_to_queue(Operation *pop, int level, int cb = 0); + int add_set_to_queue(Operation *pop, int level, const char *value, int cb = 0); + + int read_response_l1(); + void read_response_l2(); + // protocol fucntions + int set_request_ascii(const char* key, const char* value, int length); + int set_request_binary(const char* key, const char* value, int length); + int set_request_resp(const char* key, const char* value, int length); + + int get_request_ascii(const char* key); + int get_request_binary(const char* key); + int get_request_resp(const char* key); + + bool consume_binary_response(evbuffer *input); + bool consume_ascii_line(evbuffer *input, bool &done); + bool consume_resp_line(evbuffer *input, bool &done); +}; + #endif diff --git a/ConnectionMultiApproxBatchShm.cc b/ConnectionMultiApproxBatchShm.cc new file mode 100644 index 0000000..212f488 --- /dev/null +++ b/ConnectionMultiApproxBatchShm.cc @@ -0,0 +1,1624 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "config.h" + +#include "Connection.h" +#include "distributions.h" +#include "Generator.h" +#include "mutilate.h" +#include "binary_protocol.h" +#include "util.h" +#include +#include +#include +#include +#include +#include "blockingconcurrentqueue.h" + +//#include +//#include + +#define ITEM_L1 1 +#define ITEM_L2 2 +#define LOG_OP 4 +#define SRC_L1_M 8 +#define SRC_L1_H 16 +#define SRC_L2_M 32 +#define SRC_L2_H 64 +#define SRC_DIRECT_SET 128 +#define SRC_L1_COPY 256 +#define SRC_WB 512 + +#define ITEM_INCL 4096 +#define ITEM_EXCL 8192 +#define ITEM_DIRTY 16384 +#define ITEM_SIZE_CHANGE 131072 +#define ITEM_WAS_HIT 262144 + +#define LEVELS 2 +#define SET_INCL(incl,flags) \ + switch (incl) { \ + case 1: \ + flags |= ITEM_INCL; \ + break; \ + case 2: \ + flags |= ITEM_EXCL; \ + break; \ + \ + } \ + +#define GET_INCL(incl,flags) \ + if (flags & ITEM_INCL) incl = 1; \ + else if (flags & ITEM_EXCL) incl = 2; \ + +//#define OP_level(op) ( ((op)->flags & ITEM_L1) ? ITEM_L1 : ITEM_L2 ) +#define OP_level(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define FLAGS_level(flags) ( flags & ~(LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_clu(op) ( (op)->flags & ~(LOG_OP | \ + ITEM_L1 | ITEM_L2 | ITEM_DIRTY | \ + SRC_L1_M | SRC_L1_H | SRC_L2_M | SRC_L2_H | \ + SRC_DIRECT_SET | SRC_L1_COPY | SRC_WB ) ) + +#define OP_src(op) ( (op)->flags & ~(ITEM_L1 | ITEM_L2 | LOG_OP | \ + ITEM_INCL | ITEM_EXCL | ITEM_DIRTY ) ) + +#define OP_log(op) ((op)->flags & LOG_OP) +#define OP_incl(op) ((op)->flags & ITEM_INCL) +#define OP_excl(op) ((op)->flags & ITEM_EXCL) +#define OP_set_flag(op,flag) ((op))->flags |= flag; + +//#define DEBUGMC +//#define DEBUGS + + + +pthread_mutex_t cid_lock_m_approx_batch_shm = PTHREAD_MUTEX_INITIALIZER; +static uint32_t connids_m = 1; + +#define NCLASSES 40 +#define CHUNK_ALIGN_BYTES 8 +static int classes = 0; +static int sizes[NCLASSES+1]; +static int inclusives[NCLASSES+1]; + + + +static void init_inclusives(char *inclusive_str) { + int j = 1; + for (int i = 0; i < (int)strlen(inclusive_str); i++) { + if (inclusive_str[i] == '-') { + continue; + } else { + inclusives[j] = inclusive_str[i] - '0'; + j++; + } + } +} + +static void init_classes() { + + double factor = 1.25; + //unsigned int chunk_size = 48; + //unsigned int item_size = 24; + unsigned int size = 96; //warning if you change this you die + unsigned int i = 0; + unsigned int chunk_size_max = 1048576/2; + while (++i < NCLASSES-1) { + if (size >= chunk_size_max / factor) { + break; + } + if (size % CHUNK_ALIGN_BYTES) + size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); + sizes[i] = size; + size *= factor; + } + sizes[i] = chunk_size_max; + classes = i; + +} + +static int get_class(int vl, uint32_t kl) { + //warning if you change this you die + int vsize = vl+kl+48+1+2; + int res = 1; + while (vsize > sizes[res]) + if (res++ == classes) { + //fprintf(stderr,"item larger than max class size. vsize: %d, class size: %d\n",vsize,sizes[res]); + return -1; + } + return res; +} + +//static int get_incl(int vl, int kl) { +// int clsid = get_class(vl,kl); +// if (clsid) { +// return inclusives[clsid]; +// } else { +// return -1; +// } +//} + + +void ConnectionMultiApproxBatchShm::output_op(Operation *op, int type, bool found) { + char output[1024]; + char k[256]; + char a[256]; + char s[256]; + memset(k,0,256); + memset(a,0,256); + memset(s,0,256); + strncpy(k,op->key,255); + switch (type) { + case 0: //get + sprintf(a,"issue_get"); + break; + case 1: //set + sprintf(a,"issue_set"); + break; + case 2: //resp + sprintf(a,"resp"); + break; + } + switch(read_state) { + case INIT_READ: + sprintf(s,"init"); + break; + case CONN_SETUP: + sprintf(s,"setup"); + break; + case LOADING: + sprintf(s,"load"); + break; + case IDLE: + sprintf(s,"idle"); + break; + case WAITING_FOR_GET: + sprintf(s,"waiting for get"); + break; + case WAITING_FOR_SET: + sprintf(s,"waiting for set"); + break; + case WAITING_FOR_DELETE: + sprintf(s,"waiting for del"); + break; + case MAX_READ_STATE: + sprintf(s,"max"); + break; + } + if (type == 2) { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, found: %d, type: %d\n",cid,a,k,op->opaque,found,op->type); + } else { + sprintf(output,"conn: %u, action: %s op: %s, opaque: %u, type: %d\n",cid,a,k,op->opaque,op->type); + } + size_t res = write(2,output,strlen(output)); + if (res != strlen(output)) { + fprintf(stderr,"error outputingiii\n"); + } +} + +extern unordered_map cid_rate; +extern unordered_map> copy_keys; +extern unordered_map touch_keys; +extern unordered_map> wb_keys; + +extern map g_key_hist; +extern int max_n[3]; + +/** + * Create a new connection to a server endpoint. + */ +ConnectionMultiApproxBatchShm::ConnectionMultiApproxBatchShm(options_t _options, bool sampling) : + start_time(0), stats(sampling), options(_options) +{ + pthread_mutex_lock(&cid_lock_m_approx_batch_shm); + cid = connids_m++; + if (cid == 1) { + init_classes(); + init_inclusives(options.inclusives); + } + //cid_rate.insert( { cid, 0 } ); + + pthread_mutex_unlock(&cid_lock_m_approx_batch_shm); + + valuesize = createGenerator(options.valuesize); + keysize = createGenerator(options.keysize); + srand(time(NULL)); + keygen = new KeyGenerator(keysize, options.records); + + total = 0; + eof = 0; + o_percent = 0; + + if (options.lambda <= 0) { + iagen = createGenerator("0"); + } else { + D("iagen = createGenerator(%s)", options.ia); + iagen = createGenerator(options.ia); + iagen->set_lambda(options.lambda); + } + + read_state = IDLE; + write_state = INIT_WRITE; + last_quiet1 = false; + last_quiet2 = false; + + last_tx = last_rx = 0.0; + gets = 0; + ghits = 0; + esets = 0; + isets = 0; + gloc = rand() % (10*2-1)+1; + sloc = rand() % (10*2-1)+1; + iloc = rand() % (10*2-1)+1; + + op_queue_size = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + issued_queue = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + opaque = (uint32_t*)malloc(sizeof(uint32_t)*(LEVELS+1)); + op_queue = (Operation***)malloc(sizeof(Operation**)*(LEVELS+1)); + + for (int i = 0; i <= LEVELS; i++) { + op_queue_size[i] = 0; + issued_queue[i] = 0; + opaque[i] = 1; + //op_queue[i] = (Operation*)malloc(sizeof(int)*OPAQUE_MAX); + op_queue[i] = (Operation**)malloc(sizeof(Operation*)*(OPAQUE_MAX+1)); + for (int j = 0; j <= OPAQUE_MAX; j++) { + op_queue[i][j] = NULL; + } + + } + + + read_state = IDLE; +} + + +void ConnectionMultiApproxBatchShm::set_queue(queue* a_trace_queue) { + trace_queue = a_trace_queue; + trace_queue_n = a_trace_queue->size(); + //Operation *Op = trace_queue->front(); + //incl_ = get_incl(Op->valuelen,strlen(Op->key)); + //clsid_ = get_class(Op->valuelen,strlen(Op->key)); + + buffer_size_ = 1024*1024*4; + //setup the buffers + //max is (valuelen + 256 + 24 + 4 + 4 ) * depth + for (int i = 1; i <= LEVELS; i++) { + buffer_write[i] = (unsigned char*)malloc(buffer_size_); + buffer_read[i] = (unsigned char*)malloc(buffer_size_); + buffer_leftover[i] = (unsigned char*)malloc(buffer_size_); + memset(buffer_read[i],0,buffer_size_); + memset(buffer_leftover[i],0,buffer_size_); + buffer_write_n[i] = 0; + buffer_read_n[i] = 0; + buffer_write_nbytes[i] = 0; + buffer_read_nbytes[i] = 0; + buffer_write_pos[i] = buffer_write[i]; + buffer_read_pos[i] = buffer_read[i]; + buffer_lasthdr[i] = 0; // buffer_read[i]; + } + +} + +void ConnectionMultiApproxBatchShm::set_lock(pthread_mutex_t* a_lock) { + lock = a_lock; +} + +void ConnectionMultiApproxBatchShm::set_g_wbkeys(unordered_map> *a_wb_keys) { + g_wb_keys = a_wb_keys; +} + +uint32_t ConnectionMultiApproxBatchShm::get_cid() { + return cid; +} + +int ConnectionMultiApproxBatchShm::add_to_wb_keys(string key) { + auto pos = wb_keys.find(key); + if (pos == wb_keys.end()) { + wb_keys.insert( {key, vector() }); + return 1; + } + return 2; +} + +void ConnectionMultiApproxBatchShm::del_wb_keys(string key) { + + auto position = wb_keys.find(key); + if (position != wb_keys.end()) { + vector op_list = vector(position->second); + wb_keys.erase(position); + for (auto it = op_list.begin(); it != op_list.end(); ++it) { + issue_op(*it); + } + } else { + fprintf(stderr,"expected wb %s, got nuthin\n",key.c_str()); + } +} + +int ConnectionMultiApproxBatchShm::add_to_copy_keys(string key) { + auto pos = copy_keys.find(key); + if (pos == copy_keys.end()) { + copy_keys.insert( {key, vector() }); + return 1; + } + return 2; +} + + +void ConnectionMultiApproxBatchShm::del_copy_keys(string key) { + + auto position = copy_keys.find(key); + if (position != copy_keys.end()) { + vector op_list = vector(position->second); + copy_keys.erase(position); + for (auto it = op_list.begin(); it != op_list.end(); ++it) { + issue_op(*it); + } + } else { + fprintf(stderr,"expected copy %s, got nuthin\n",key.c_str()); + } +} + +int ConnectionMultiApproxBatchShm::add_to_touch_keys(string key) { + //return touch_keys.assign_if_equal( key, NULL, cid ) != NULL ? 1 : 2; + auto pos = touch_keys.find(key); + if (pos == touch_keys.end()) { + touch_keys.insert( {key, cid }); + return 1; + } + return 2; +} + + +void ConnectionMultiApproxBatchShm::del_touch_keys(string key) { + //touch_keys.erase(key); + auto position = touch_keys.find(key); + if (position != touch_keys.end()) { + touch_keys.erase(position); + } else { + fprintf(stderr,"expected touch %s, got nuthin\n",key.c_str()); + } +} + +int ConnectionMultiApproxBatchShm::issue_op(Operation *Op) { + double now = get_time(); + int issued = 0; + Op->clsid = get_class(Op->valuelen,strlen(Op->key)); + int flags = 0; + int index = lrand48() % (1024 * 1024); + int incl = inclusives[Op->clsid]; + SET_INCL(incl,flags); + + switch(Op->type) { + + case Operation::GET: + issued = issue_get_with_len(Op, now, false, flags | LOG_OP | ITEM_L1); + this->stats.gets++; + gets++; + //this->stats.gets_cid[cid]++; + break; + case Operation::SET: + if (incl == 1) { + if (isets >= iloc) { + const char *data = &random_char[index]; + issued = issue_set(Op, data, now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + issued = issue_touch(Op->key,Op->valuelen,now, ITEM_L2 | SRC_DIRECT_SET); + iloc += rand()%(10*2-1)+1; + } else { + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET | ITEM_DIRTY); + } + isets++; + } else if (incl == 2) { + issued = issue_set(Op, &random_char[index], now, flags | LOG_OP | ITEM_L1 | SRC_DIRECT_SET); + if (esets >= sloc) { + issued = issue_delete(Op->key,now,ITEM_L2 | SRC_DIRECT_SET); + sloc += rand()%(10*2-1)+1; + } + esets++; + } + this->stats.sets++; + //this->stats.sets_cid[cid]++; + break; + case Operation::DELETE: + case Operation::TOUCH: + case Operation::NOOP: + case Operation::SASL: + fprintf(stderr,"invalid line: %s, vl: %d\n",Op->key,Op->valuelen); + break; + + } + return issued; +} + + +int ConnectionMultiApproxBatchShm::do_connect() { + + int connected = 0; + //the client should see for this cid, where the shared memory is + typedef struct shared_ { + bipbuf_t bipbuf_in; + bipbuf_t bipbuf_out; + pthread_mutex_t lock_in; + pthread_mutex_t lock_out; + pthread_cond_t cond_in; + pthread_cond_t cond_out; + int shared_id; + } shared_t; + + //this cid gets shared memory + // ftok to generate unique key + //char shmkey[64]; + //sprintf(shmkey,"shmfilel1%d",cid); + int id = cid+100; + //key_t key = ftok(shmkey,id); + + // shmget returns an identifier in shmid + int shmid = shmget(id,sizeof(shared_t),0666); + + // shmat to attach to shared memory + shared_t* share_l1 = (shared_t*) shmat(shmid,(void*)0,0); + + fprintf(stderr,"cid %d gets shared memory buf l1 %d\n",cid,share_l1->shared_id); + + // ftok to generate unique key + //char shmkey2[64]; + //sprintf(shmkey2,"shmfilel2%d",cid); + int id2 = cid+200; + //key_t key2 = ftok(shmkey2,id2); + + // shmget returns an identifier in shmid + int shmid2 = shmget(id2,sizeof(shared_t),0666); + + // shmat to attach to shared memory + shared_t* share_l2 = (shared_t*) shmat(shmid2,(void*)0,0); + + fprintf(stderr,"cid %d gets shared memory buf l2 %d\n",cid,share_l2->shared_id); + connected = 1; + + //the leads are reveresed (from perspective of server) + bipbuf_in[1] = &share_l1->bipbuf_out; + bipbuf_in[2] = &share_l2->bipbuf_out; + bipbuf_out[1] = &share_l1->bipbuf_in; + bipbuf_out[2] = &share_l2->bipbuf_in; + + lock_in[1] = &share_l1->lock_out; + lock_in[2] = &share_l2->lock_out; + lock_out[1] = &share_l1->lock_in; + lock_out[2] = &share_l2->lock_in; + + cond_in[1] = &share_l1->cond_out; + cond_in[2] = &share_l2->cond_out; + cond_out[1] = &share_l1->cond_in; + cond_out[2] = &share_l2->cond_in; + read_state = IDLE; + return connected; +} + +/** + * Destroy a connection, performing cleanup. + */ +ConnectionMultiApproxBatchShm::~ConnectionMultiApproxBatchShm() { + + + for (int i = 0; i <= LEVELS; i++) { + free(op_queue[i]); + if (i > 0) { + free(buffer_write[i]); + free(buffer_read[i]); + } + + } + + free(op_queue_size); + free(opaque); + free(op_queue); + + delete iagen; + delete keygen; + delete keysize; + delete valuesize; +} + +/** + * Reset the connection back to an initial, fresh state. + */ +void ConnectionMultiApproxBatchShm::reset() { + // FIXME: Actually check the connection, drain all bufferevents, drain op_q. + //assert(op_queue.size() == 0); + //evtimer_del(timer); + read_state = IDLE; + write_state = INIT_WRITE; + stats = ConnectionStats(stats.sampling); +} + + + + +/** + * Get/Set or Set Style + * If a GET command: Issue a get first, if not found then set + * If trace file (or prob. write) says to set, then set it + */ +int ConnectionMultiApproxBatchShm::issue_getsetorset(double now) { + + Operation *Op = trace_queue->front(); + if (Op->type == Operation::SASL) { + //cid_rate.insert( {cid, 100 } ); + //fprintf(stderr,"cid %d done before loop\n",cid); + //string op_queue1; + //string op_queue2; + //for (int j = 0; j < 2; j++) { + // for (int i = 0; i < OPAQUE_MAX; i++) { + // if (op_queue[j+1][i] != NULL) { + // if (j == 0) { + // op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + // } else { + // op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + // } + // } + // } + //} + for (int i = 1; i <= LEVELS; i++) { + if (buffer_write_n[i] > 0) { + send_write_buffer(i); + } + } + eof = 1; + //fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + return 1; + } + + int issued = issue_op(Op); + trace_queue->pop(); + while (issued != 2) { + Op = trace_queue->front(); + + if (Op->type == Operation::SASL) { + for (int i = 1; i <= LEVELS; i++) { + if (buffer_write_n[i] > 0) { + send_write_buffer(i); + } + } + //string op_queue1; + //string op_queue2; + //for (int j = 0; j < 2; j++) { + // for (int i = 0; i < OPAQUE_MAX; i++) { + // if (op_queue[j+1][i] != NULL) { + // if (j == 0) { + // op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + // } else { + // op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + // } + // } + // } + //} + //fprintf(stderr,"done in loop cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + eof = 1; + return 1; + } + issued = issue_op(Op); + trace_queue->pop(); + } + + return 0; +} + +int ConnectionMultiApproxBatchShm::send_write_buffer(int level) { + int rc = 1; + pthread_mutex_lock(lock_out[level]); + int to_write = buffer_write_nbytes[level]; + int gtg = bipbuf_unused(bipbuf_out[level]) >= to_write ? 1 : 0; + if (gtg) { + int ret = bipbuf_offer(bipbuf_out[level],buffer_write[level],to_write); + if (ret != to_write) { + fprintf(stderr,"error writing buffer! level %d, size %d\n",level,to_write); + } + issued_queue[level] = buffer_write_n[level]; + buffer_write_n[level] = 0; + buffer_write_pos[level] = buffer_write[level]; + memset(buffer_write_pos[level],0,buffer_write_nbytes[level]); + stats.tx_bytes += buffer_write_nbytes[level]; + buffer_write_nbytes[level] = 0; + rc = 2; + pthread_cond_signal(cond_out[level]); + pthread_mutex_unlock(lock_out[level]); + } else { + pthread_mutex_unlock(lock_out[level]); + rc = 1; + } + return rc; +} + +int ConnectionMultiApproxBatchShm::add_get_op_to_queue(Operation *pop, int level, int cb) { + + op_queue[level][pop->opaque] = pop; + op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing get: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,pop->key,pop->valuelen,level,pop->flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + uint16_t keylen = strlen(pop->key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_GET, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + //if (quiet) { + // h.opcode = CMD_GETQ; + //} + h.opaque = htonl(pop->opaque); + + //int to_write = buffer_write_nbytes[level] + 24 + keylen; + //int gtg = bipbuf_unused(bipbuf_out[level]) >= to_write ? 1 : 0; + //if (gtg == 0) { + // switch (level) { + // case 1: + // read_callback1(); + // break; + // case 2: + // read_callback2(); + // break; + // } + //} + + memcpy(buffer_write_pos[level], &h, 24); + buffer_write_pos[level] += 24; + memcpy(buffer_write_pos[level], pop->key, keylen); + buffer_write_pos[level] += keylen; + buffer_write_n[level]++; + buffer_write_nbytes[level] += 24 + keylen; + + int res = 1; + if (buffer_write_n[level] >= (uint32_t)options.depth && cb == 0) { + res = send_write_buffer(level); + } + return res; +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApproxBatchShm::issue_get_with_len(Operation *pop, double now, bool quiet, uint32_t flags, Operation *l1) { + + int level = FLAGS_level(flags); + + //initialize op for sending +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + pop->opaque = opaque[level]++; + pop->flags = flags; + if (l1 != NULL) { + pop->l1 = l1; + } + + //put op into queue + return add_get_op_to_queue(pop,level); +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApproxBatchShm::issue_get_with_len(const char* key, int valuelen, double now, bool quiet, uint32_t flags, Operation *l1) { + + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + strncpy(pop->key,key,255); + pop->valuelen = valuelen; + pop->type = Operation::GET; + pop->opaque = opaque[level]++; + pop->flags = flags; + pop->clsid = get_class(valuelen,strlen(key)); + + if (l1 != NULL) { + pop->l1 = l1; + } + + return add_get_op_to_queue(pop,level,0); + +} + +/** + * Issue a get request to the server. + */ +int ConnectionMultiApproxBatchShm::issue_touch(const char* key, int valuelen, double now, int flags) { + + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + strncpy(pop->key,key,255); + pop->valuelen = valuelen; + pop->type = Operation::TOUCH; + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + +#ifdef DEBUGS + fprintf(stderr,"issing touch: %s, size: %u, level %d, flags: %d, opaque: %d\n",key,valuelen,level,flags,pop->opaque); +#endif + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_TOUCH, htons(keylen), + 0x04, 0x00, htons(0), + htonl(keylen + 4) }; + h.opaque = htonl(pop->opaque); + + uint32_t exp = 0; + if (flags & ITEM_DIRTY) { + exp = htonl(flags); + } + + memcpy(buffer_write_pos[level], &h, 24); + buffer_write_pos[level] += 24; + memcpy(buffer_write_pos[level], &exp, 4); + buffer_write_pos[level] += 4; + memcpy(buffer_write_pos[level], pop->key, keylen); + buffer_write_pos[level] += keylen; + buffer_write_nbytes[level] += 24 + keylen + 4; + buffer_write_n[level]++; + + int ret = 1; + //if (buffer_write_n[level] == (uint32_t)options.depth) { + // ret = send_write_buffer(level); + //} + + return ret; +} + +/** + * Issue a delete request to the server. + */ +int ConnectionMultiApproxBatchShm::issue_delete(const char* key, double now, uint32_t flags) { + + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) { +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + pop->start_time = tv_to_double(&now_tv); +#else + pop->start_time = get_time(); +#endif + } else { + pop->start_time = now; + } +#endif + + strncpy(pop->key,key,255); + pop->type = Operation::DELETE; + pop->opaque = opaque[level]++; + pop->flags = flags; + op_queue[level][pop->opaque] = pop; + op_queue_size[level]++; + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing delete: %s, level %d, flags: %d, opaque: %d\n",cid,key,level,flags,pop->opaque); +#endif + + //if (read_state == IDLE) read_state = WAITING_FOR_GET; + uint16_t keylen = strlen(key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_DELETE, htons(keylen), + 0x00, 0x00, htons(0), + htonl(keylen) }; + h.opaque = htonl(pop->opaque); + + memcpy(buffer_write_pos[level], &h, 24); + buffer_write_pos[level] += 24; + memcpy(buffer_write_pos[level], pop->key, keylen); + buffer_write_pos[level] += keylen; + buffer_write_n[level]++; + buffer_write_nbytes[level] += 24 + keylen; + + int ret = 1; + //if (buffer_write_n[level] >= (uint32_t)options.depth) { + // ret = send_write_buffer(level); + //} + + return ret; +} + +int ConnectionMultiApproxBatchShm::issue_noop(int level) { + + binary_header_t h = { 0x80, CMD_NOOP, 0x0000, + 0x00, 0x00, htons(0), + 0x00 }; + memcpy(buffer_write_pos[level], &h, 24); + buffer_write_pos[level] += 24; + + buffer_write_n[level]++; + buffer_write_nbytes[level] += 24; + + int ret = 1; + //if (buffer_write_n[level] >= (uint32_t)options.depth) { + // ret = send_write_buffer(level); + //} + + return ret; +} + +int ConnectionMultiApproxBatchShm::add_set_to_queue(Operation *pop, int level, const char* value, int cb) { + int length = pop->valuelen; + + op_queue[level][pop->opaque] = pop; + //op_queue[level].push(op); + op_queue_size[level]++; +#ifdef DEBUGS + fprintf(stderr,"cid: %d issing set: %s, size: %u, level %d, flags: %d, opaque: %d\n",cid,pop->key,length,level,pop->flags,pop->opaque); +#endif + + if (opaque[level] > OPAQUE_MAX) { + opaque[level] = 1; + } + + uint16_t keylen = strlen(pop->key); + + // each line is 4-bytes + binary_header_t h = { 0x80, CMD_SET, htons(keylen), + 0x08, 0x00, htons(0), + htonl(keylen + 8 + length) }; + h.opaque = htonl(pop->opaque); + + uint32_t f = htonl(pop->flags); + uint32_t exp = 0; + //int to_write = buffer_write_nbytes[level] + 32 + keylen + length; + //int gtg = bipbuf_unused(bipbuf_out[level]) >= to_write ? 1 : 0; + //if (gtg == 0) { + // switch (level) { + // case 1: + // read_callback1(); + // break; + // case 2: + // read_callback2(); + // break; + // } + //} + //fprintf(stderr,"write_n[%d] %d bytes: %d\n",level,buffer_write_n[level],buffer_write_nbytes[level]); + memcpy(buffer_write_pos[level], &h, 24); + buffer_write_pos[level] += 24; + memcpy(buffer_write_pos[level], &f, 4); + buffer_write_pos[level] += 4; + memcpy(buffer_write_pos[level], &exp, 4); + buffer_write_pos[level] += 4; + memcpy(buffer_write_pos[level], pop->key, keylen); + buffer_write_pos[level] += keylen; + memcpy(buffer_write_pos[level], value, length); + buffer_write_pos[level] += length; + buffer_write_n[level]++; + buffer_write_nbytes[level] += length + 32 + keylen; + + int ret = 1; + if (buffer_write_n[level] >= (uint32_t)options.depth && cb == 0) { + ret = send_write_buffer(level); + } + return ret; + +} + +/** + * Issue a set request to the server. + */ +int ConnectionMultiApproxBatchShm::issue_set(Operation *pop, const char* value, double now, uint32_t flags) { + + int level = FLAGS_level(flags); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) pop->start_time = get_time(); + else pop->start_time = now; +#endif + + pop->opaque = opaque[level]++; + pop->flags = flags; + return add_set_to_queue(pop,level,value); + +} + +/** + * Issue a set request to the server. + */ +int ConnectionMultiApproxBatchShm::issue_set(const char* key, const char* value, int length, double now, uint32_t flags) { + + int level = FLAGS_level(flags); + Operation *pop = new Operation(); + +#if HAVE_CLOCK_GETTIME + pop->start_time = get_time_accurate(); +#else + if (now == 0.0) pop->start_time = get_time(); + else pop->start_time = now; +#endif + + strncpy(pop->key,key,255); + pop->valuelen = length; + pop->type = Operation::SET; + pop->opaque = opaque[level]++; + pop->flags = flags; + pop->clsid = get_class(length,strlen(key)); + + return add_set_to_queue(pop,level,value,0); + +} + + +/** + * Finish up (record stats) an operation that just returned from the + * server. + */ +void ConnectionMultiApproxBatchShm::finish_op(Operation *op, int was_hit) { + double now; +#if USE_CACHED_TIME + struct timeval now_tv; + event_base_gettimeofday_cached(base, &now_tv); + now = tv_to_double(&now_tv); +#else + now = get_time(); +#endif +#if HAVE_CLOCK_GETTIME + op->end_time = get_time_accurate(); +#else + op->end_time = now; +#endif + + if (was_hit) { + switch (op->type) { + case Operation::GET: + switch (OP_level(op)) { + case 1: + stats.log_get_l1(*op); + break; + case 2: + stats.log_get_l2(*op); + if (op->l1 != NULL) { + op->l1->end_time = now; + stats.log_get(*(op->l1)); + } + break; + } + break; + case Operation::SET: + switch (OP_level(op)) { + case 1: + stats.log_set_l1(*op); + break; + case 2: + stats.log_set_l2(*op); + break; + } + break; + case Operation::DELETE: break; + case Operation::TOUCH: break; + default: DIE("Not implemented."); + } + } + + last_rx = now; + uint8_t level = OP_level(op); + if (op->l1 != NULL) { + //delete op_queue[1][op->l1->opaque]; + if (op->l1 == op_queue[1][op->l1->opaque]) { + op_queue[1][op->l1->opaque] = 0; + if (op_queue_size[1] > 0) { + op_queue_size[1]--; + } else { + fprintf(stderr,"chained op_Queue_size[%d] out of sync!!\n",1); + } + delete op->l1; + } else { + fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", + op,op_queue[1][op->opaque],op->opaque,op_queue[1][op->opaque]->opaque); + } + } + //op_queue[level].erase(op_queue[level].begin()+opopq); + if (op == op_queue[level][op->opaque] && + op->opaque == op_queue[level][op->opaque]->opaque) { + //delete op_queue[level][op->opaque]; + op_queue[level][op->opaque] = 0; + delete op; + if (op_queue_size[level] > 0) { + op_queue_size[level]--; + } else { + fprintf(stderr,"op_Queue_size[%d] out of sync!!\n",level); + } + } else { + fprintf(stderr,"op_queue out of sync! Expected %p, got %p, opa1: %d opaq2: %d\n", + op,op_queue[level][op->opaque],op->opaque,op_queue[level][op->opaque]->opaque); + } + read_state = IDLE; + +} + + + +/** + * Request generation loop. Determines whether or not to issue a new command, + * based on timer events. + * + * Note that this function loops. Be wary of break vs. return. + */ +void ConnectionMultiApproxBatchShm::drive_write_machine_shm(double now) { + + while (trace_queue->size() > 0) { + Operation *Op = trace_queue->front(); + if (Op == NULL || trace_queue->size() <= 0 || Op->type == Operation::SASL) { + eof = 1; + for (int i = 1; i <= LEVELS; i++) { + if (buffer_write_n[i] > 0) { + send_write_buffer(i); + } + } + + cid_rate.insert( {cid, 100 } ); + fprintf(stderr,"cid %d done\n",cid); + string op_queue1; + string op_queue2; + for (int j = 0; j < 2; j++) { + for (int i = 0; i < OPAQUE_MAX; i++) { + if (op_queue[j+1][i] != NULL) { + if (j == 0) { + op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + } else { + op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + } + } + } + } + fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + return; + } + int issued = 0; + while (issued != 2) { + Op = trace_queue->front(); + + if (Op->type == Operation::SASL) { + for (int i = 1; i <= LEVELS; i++) { + if (buffer_write_n[i] > 0) { + send_write_buffer(i); + } + } + //string op_queue1; + //string op_queue2; + //for (int j = 0; j < 2; j++) { + // for (int i = 0; i < OPAQUE_MAX; i++) { + // if (op_queue[j+1][i] != NULL) { + // if (j == 0) { + // op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; + // } else { + // op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; + // } + // } + // } + //} + //fprintf(stderr,"done in loop cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); + eof = 1; + return; + } + issued = issue_op(Op); //this will return 2 if the write buffer was sent (i.e. buffer has depth commands) + trace_queue->pop(); + } + if ( (int)(issued_queue[1]) > 0) { + read_callback1(); + issued_queue[1] = 0; + } + if ( (int)(issued_queue[2]) > 0) { + read_callback2(); + issued_queue[2] = 0; + } + } +} + +size_t ConnectionMultiApproxBatchShm::handle_response_batch(unsigned char *rbuf_pos, resp_t *resp, + size_t read_bytes, size_t consumed_bytes, + int level, int extra) { + if (rbuf_pos[0] != 129) { + //fprintf(stderr,"cid %d we don't have a valid header %u\n",cid,rbuf_pos[0]); + //buffer_read_pos[level] = rbuf_pos; + //buffer_read_n[level] = 1; + return 0; + } + if ((read_bytes+extra - consumed_bytes) < 24) { + size_t have = (read_bytes+extra) - (consumed_bytes); + size_t need = 24 - (have); + buffer_read_n[level] = need; + buffer_read_nbytes[level] = have; + memcpy(buffer_leftover[level],rbuf_pos,have); + //buffer_lasthdr[level] = rbuf_pos; + //buffer_read_n[level] = need; + //buffer_read_nbytes[level] = have; + //fprintf(stderr,"cid %d - we don't have enough header data, need %lu more bytes, have %lu (targetLen: %d) (read_bytes %ld) (extra %d) %d)\n",cid,need,have,24,read_bytes,extra,level); + return 0; + + } + + binary_header_t* h = reinterpret_cast(rbuf_pos); + uint32_t bl = ntohl(h->body_len); + uint16_t kl = ntohs(h->key_len); + uint8_t el = h->extra_len; + int targetLen = 24 + bl; + if (consumed_bytes + targetLen > (read_bytes+extra)) { + size_t have = (read_bytes+extra) - (consumed_bytes); + size_t need = targetLen - (have); + buffer_read_n[level] = need; + buffer_read_nbytes[level] = have; + memcpy(buffer_leftover[level],rbuf_pos,have); + //fprintf(stderr,"cid %d - we don't have enough data, need %lu more bytes, have %lu (targetLen: %d) (read_bytes %ld) (extra %d) %d)\n",cid,need,have,targetLen,read_bytes,extra,level); + return 0; + } + + resp->opcode = h->opcode; + resp->opaque = ntohl(h->opaque); + uint16_t status = ntohs(h->status); +#ifdef DEBUGMC + fprintf(stderr,"cid: %d handle resp from l%d - opcode: %u opaque: %u keylen: %u extralen: %u datalen: %u status: %u\n",cid,level, + h->opcode,ntohl(h->opaque),ntohs(h->key_len),h->extra_len, + ntohl(h->body_len),ntohs(h->status)); +#endif + // If something other than success, count it as a miss + if (resp->opcode == CMD_GET && status == RESP_NOT_FOUND) { + switch(level) { + case 1: + stats.get_misses_l1++; + break; + case 2: + stats.get_misses_l2++; + stats.get_misses++; + stats.window_get_misses++; + break; + } + resp->found = false; + } else if (resp->opcode == CMD_SET && kl > 0) { + //first data is extras: clsid, flags, eflags + if (resp->evict) { + unsigned char *buf = rbuf_pos + 24; + resp->evict->clsid = *((uint32_t*)buf); + resp->evict->clsid = ntohl(resp->evict->clsid); + buf += 4; + + resp->evict->serverFlags = *((uint32_t*)buf); + resp->evict->serverFlags = ntohl(resp->evict->serverFlags); + buf += 4; + + resp->evict->evictedFlags = *((uint32_t*)buf); + resp->evict->evictedFlags = ntohl(resp->evict->evictedFlags); + buf += 4; + + resp->evict->evictedKeyLen = kl; + resp->evict->evictedKey = (char*)malloc(kl+1); + memset(resp->evict->evictedKey,0,kl+1); + memcpy(resp->evict->evictedKey,buf,kl); + buf += kl; + + resp->evict->evictedLen = bl - kl - el; + resp->evict->evictedData = (char*)malloc(resp->evict->evictedLen); + memcpy(resp->evict->evictedData,buf,resp->evict->evictedLen); + resp->evict->evicted = true; + } + } else if ( (resp->opcode == CMD_DELETE || resp->opcode == CMD_TOUCH) && + status == RESP_NOT_FOUND) { + resp->found = false; + } + this->stats.rx_bytes += targetLen; + return targetLen; +} + + +size_t ConnectionMultiApproxBatchShm::fill_read_buffer(int level, int *extra) { + + size_t read_bytes = 0; + + pthread_mutex_lock(lock_in[level]); + int len = bipbuf_used(bipbuf_in[level]); + while (len == 0) { + pthread_cond_wait(cond_in[level],lock_in[level]); + len = bipbuf_used(bipbuf_in[level]); + } + + if (buffer_read_n[level] != 0) { + uint32_t have = buffer_read_nbytes[level]; + //if ((size_t)len < buffer_read_n[level]) { + // pthread_mutex_unlock(lock_in[level]); + // return 0; + //} + unsigned char* input = bipbuf_poll(bipbuf_in[level],len); + if (!input || len == 0) { + if (!input && len > 0) + fprintf(stderr,"cid %d expected %d on level %d (already have %u)\n",cid,len,level,have); + pthread_mutex_unlock(lock_in[level]); + return 0; + } + memcpy(buffer_read[level],buffer_leftover[level],have); + buffer_read_pos[level] = buffer_read[level]; + memcpy(buffer_read_pos[level]+have,input,len); + read_bytes = len; + *extra = have; + buffer_read_n[level] = 0; + buffer_read_nbytes[level] = 0; + + } else { + unsigned char *input = bipbuf_poll(bipbuf_in[level],len); + if (!input || len == 0) { + if (!input && len > 0) + fprintf(stderr,"cid %d expected %d on level %d\n",cid,len,level); + pthread_mutex_unlock(lock_in[level]); + return 0; + } + read_bytes = len; + buffer_read_pos[level] = input; + //memcpy(buffer_read_pos[level],input,len); + + *extra = 0; + } + if (read_bytes == 0) { + fprintf(stderr,"cid %d read 0 bytes\n",cid); + } + pthread_mutex_unlock(lock_in[level]); + return read_bytes; +} +/** + * Handle incoming data (responses). + */ +void ConnectionMultiApproxBatchShm::read_callback1() { + + int level = 1; + int extra = 0; + size_t read_bytes = 0; + + read_bytes = fill_read_buffer(level,&extra); + if (read_bytes == 0) { + return; + } + + //fprintf(stderr,"cid %d l1 read: %lu\n",cid,read_bytes); + size_t consumed_bytes = 0; + size_t batch = options.depth; + //we have at least some data to read + size_t nread_ops = 0; + while (1) { + evicted_t *evict = (evicted_t*)malloc(sizeof(evicted_t)); + memset(evict,0,sizeof(evicted_t)); + resp_t mc_resp; + mc_resp.found = true; + mc_resp.evict = evict; + size_t cbytes = handle_response_batch(buffer_read_pos[level],&mc_resp,read_bytes,consumed_bytes,level,extra); + if (cbytes == 0) { + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + break; + } + buffer_read_pos[level] = buffer_read_pos[level] + cbytes; + consumed_bytes += cbytes; + uint32_t opaque = mc_resp.opaque; + bool found = mc_resp.found; + + Operation *op = op_queue[level][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"l1 cid %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + + double now = get_time(); + int wb = 0; + if (options.rand_admit) { + wb = (rand() % options.rand_admit); + } + int vl = op->valuelen; + int flags = OP_clu(op); + switch (op->type) { + case Operation::GET: + if ( !found && (options.getset || options.getsetorset) ) { + /* issue a get a l2 */ + issue_get_with_len(op->key,vl,now,false, flags | SRC_L1_M | ITEM_L2 | LOG_OP, op); + op->end_time = now; + this->stats.log_get_l1(*op); + + } else { + if (OP_incl(op) && ghits >= gloc) { + issue_touch(op->key,vl,now, ITEM_L2 | SRC_L1_H); + gloc += rand()%(10*2-1)+1; + } + ghits++; + finish_op(op,1); + } + break; + case Operation::SET: + //if (OP_src(op) == SRC_L1_COPY || + // OP_src(op) == SRC_L2_M) { + // del_copy_keys(string(op->key)); + //} + if (evict->evicted) { + string wb_key(evict->evictedKey); + if ((evict->evictedFlags & ITEM_INCL) && (evict->evictedFlags & ITEM_DIRTY)) { + //int ret = add_to_wb_keys(wb_key); + //if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_INCL | LOG_OP | SRC_WB | ITEM_DIRTY); + //} + this->stats.incl_wbs++; + } else if (evict->evictedFlags & ITEM_EXCL) { + //fprintf(stderr,"excl writeback %s\n",evict->evictedKey); + //strncpy(wb_key,evict->evictedKey,255); + if ( (options.rand_admit && wb == 0) || + (options.threshold && (g_key_hist[wb_key] == 1)) || + (options.wb_all) ) { + //int ret = add_to_wb_keys(wb_key); + //if (ret == 1) { + issue_set(evict->evictedKey, evict->evictedData, evict->evictedLen, now, ITEM_L2 | ITEM_EXCL | LOG_OP | SRC_WB); + //} + this->stats.excl_wbs++; + } + } + if (OP_src(op) == SRC_DIRECT_SET) { + if ( (evict->serverFlags & ITEM_SIZE_CHANGE) || ((evict->serverFlags & ITEM_WAS_HIT) == 0)) { + this->stats.set_misses_l1++; + } else if (OP_excl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_excl_hits_l1++; + } else if (OP_incl(op) && evict->serverFlags & ITEM_WAS_HIT) { + this->stats.set_incl_hits_l1++; + } + } + } + finish_op(op,1); + break; + case Operation::TOUCH: + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); + DIE("not implemented"); + } + + if (evict) { + if (evict->evictedKey) free(evict->evictedKey); + if (evict->evictedData) free(evict->evictedData); + free(evict); + } + nread_ops++; + if (buffer_read_pos[level][0] == 0) { + break; + } + if (buffer_read_pos[level][0] != 129) { + //fprintf(stderr,"cid %d we don't have a valid header post %u\n",cid,buffer_read_pos[level][0]); + break; + } + } + + + double now = get_time(); + last_tx = now; + stats.log_op(op_queue_size[1]); + stats.log_op(op_queue_size[2]); + + +} + +/** + * Handle incoming data (responses). + */ +void ConnectionMultiApproxBatchShm::read_callback2() { + + int level = 2; + int extra = 0; + + size_t read_bytes = 0; + + read_bytes = fill_read_buffer(level,&extra); + if (read_bytes == 0) { + return; + } + + //fprintf(stderr,"l2 read: %lu\n",read_bytes); + size_t consumed_bytes = 0; + size_t batch = options.depth; + size_t nread_ops = 0; + while (1) { + evicted_t *evict = NULL; + resp_t mc_resp; + mc_resp.found = true; + mc_resp.evict = evict; + size_t cbytes = handle_response_batch(buffer_read_pos[level],&mc_resp,read_bytes,consumed_bytes,level,extra); + if (cbytes == 0) { + break; + } + buffer_read_pos[level] = buffer_read_pos[level] + cbytes; + consumed_bytes += cbytes; + uint32_t opaque = mc_resp.opaque; + bool found = mc_resp.found; + + Operation *op = op_queue[level][opaque]; +#ifdef DEBUGMC + char out[128]; + sprintf(out,"l2 cid %u, reading opaque: %u\n",cid,opaque); + write(2,out,strlen(out)); + output_op(op,2,found); +#endif + double now = get_time(); + switch (op->type) { + case Operation::GET: + if ( !found && (options.getset || options.getsetorset) ) { + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | SRC_L2_M | LOG_OP; + //int ret = add_to_copy_keys(string(op->key)); + //if (ret == 1) { + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L1); + if (OP_incl(op)) { + issue_set(op->key, &random_char[index], valuelen, now, flags | ITEM_L2); + } + //} + finish_op(op,0); // sets read_state = IDLE + } else { + if (found) { + int valuelen = op->valuelen; + int index = lrand48() % (1024 * 1024); + int flags = OP_clu(op) | ITEM_L1 | SRC_L1_COPY; + string key = string(op->key); + const char *data = &random_char[index]; + //int ret = add_to_copy_keys(string(op->key)); + //if (ret == 1) { + issue_set(op->key,data,valuelen, now, flags); + //} + this->stats.copies_to_l1++; + finish_op(op,1); + + } else { + finish_op(op,0); + } + } + break; + case Operation::SET: + //if (OP_src(op) == SRC_WB) { + // del_wb_keys(string(op->key)); + //} + finish_op(op,1); + break; + case Operation::TOUCH: + if (OP_src(op) == SRC_DIRECT_SET || SRC_L1_H) { + int valuelen = op->valuelen; + if (!found) { + int index = lrand48() % (1024 * 1024); + issue_set(op->key, &random_char[index],valuelen,now, ITEM_INCL | ITEM_L2 | LOG_OP | SRC_L2_M); + this->stats.set_misses_l2++; + } else { + if (OP_src(op) == SRC_DIRECT_SET) { + issue_touch(op->key,valuelen,now, ITEM_L1 | SRC_L2_H | ITEM_DIRTY); + } + } + //del_touch_keys(string(op->key)); + } + finish_op(op,0); + break; + case Operation::DELETE: + //check to see if it was a hit + //fprintf(stderr," del %s -- %d from %d\n",op->key.c_str(),found,OP_src(op)); + if (OP_src(op) == SRC_DIRECT_SET) { + if (found) { + this->stats.delete_hits_l2++; + } else { + this->stats.delete_misses_l2++; + } + } + finish_op(op,1); + break; + default: + fprintf(stderr,"op: %p, key: %s opaque: %u\n",(void*)op,op->key,op->opaque); + DIE("not implemented"); + } + nread_ops++; + if (buffer_read_pos[level][0] == 0) { + break; + } + if (buffer_read_pos[level][0] != 129) { + //fprintf(stderr,"l2 cid %d we don't have a valid header post %u\n",cid,buffer_read_pos[level][0]); + break; + } + } + //if (buffer_read_n[level] == 0) { + // memset(buffer_read[level],0,read_bytes); + //} + //if (nread_ops == 0) { + // fprintf(stderr,"ugh l2 only got: %lu ops expected %lu\n",nread_ops,batch); + //} + + + double now = get_time(); + last_tx = now; + stats.log_op(op_queue_size[2]); + stats.log_op(op_queue_size[1]); + +} + diff --git a/ConnectionMultiApproxShm.cc b/ConnectionMultiApproxShm.cc index a7b6951..e3c006d 100644 --- a/ConnectionMultiApproxShm.cc +++ b/ConnectionMultiApproxShm.cc @@ -1250,6 +1250,86 @@ void ConnectionMultiApproxShm::drive_write_machine_shm(double now) { } +/** + * Request generation loop + */ +//void ConnectionMultiApproxShm::drive_write_machine_shm_2(double now) { +// +// while (trace_queue->size() > 0) { +// int extra_tries = extra_queue.size(); +// for (int i = 0; i < extra_tries; i++) { +// Operation *Op = extra_queue.front(); +// switch(Op->type) +// { +// case Operation::GET: +// offer_get(Op,1); +// break; +// case Operation::SET: +// offer_set(Op,1); +// break; +// } +// } +// +// int nissued = 0; +// int nissuedl2 = 0; +// while (nissued < options.depth && extra_queue.size() == 0) { +// Operation *Op = trace_queue->front(); +// +// if (Op == NULL || trace_queue->size() <= 0 || Op->type == Operation::SASL) { +// eof = 1; +// cid_rate.insert( {cid, 100 } ); +// fprintf(stderr,"cid %d done\n",cid); +// string op_queue1; +// string op_queue2; +// for (int j = 0; j < 2; j++) { +// for (int i = 0; i < OPAQUE_MAX; i++) { +// if (op_queue[j+1][i] != NULL) { +// if (j == 0) { +// op_queue1 = op_queue1 + "," + op_queue[j+1][i]->key; +// } else { +// op_queue2 = op_queue2 + "," + op_queue[j+1][i]->key; +// } +// } +// } +// } +// fprintf(stderr,"cid %d op_queue1: %s op_queue2: %s, op_queue_size1: %d, op_queue_size2: %d\n",cid,op_queue1.c_str(),op_queue2.c_str(),op_queue_size[1],op_queue_size[2]); +// return; +// } +// int gtg = 0; +// pthread_mutex_lock(lock_out[1]); +// switch(Op->type) +// { +// case Operation::GET: +// gtg = bipbuf_unused(bipbuf_out[1]) > (int)(24+strlen(Op->key)) ? 1 : 0; +// break; +// case Operation::SET: +// gtg = bipbuf_unused(bipbuf_out[1]) > (int)(32+Op->valuelen) ? 1 : 0; +// break; +// } +// pthread_mutex_unlock(lock_out[1]); +// +// +// if (gtg) { +// trace_queue->pop(); +// int l2issued = issue_op(Op); +// nissuedl2 += l2issued; +// nissued++; +// } else { +// break; +// } +// } +// +// //wait for response (at least nissued) +// int l2issued = read_response_l1(); +// nissuedl2 += l2issued; +// if (nissuedl2 > 0) { +// read_response_l2(); +// } +// +// } +// +//} + /** * Tries to consume a binary response (in its entirety) from shared memory. * @@ -1287,7 +1367,7 @@ static int handle_response(ConnectionMultiApproxShm *conn, unsigned char *input, while ((abuf = bipbuf_poll(conn->bipbuf_in[level],targetLen)) == NULL) { pthread_mutex_unlock(conn->lock_in[level]); tries++; - if (tries > 100) { + if (tries > 10) { //fprintf(stderr,"more than 10000 tries for cid: %d for length %d\n",conn->get_cid(),targetLen); return 0; @@ -1348,7 +1428,7 @@ static int handle_response(ConnectionMultiApproxShm *conn, unsigned char *input, evict->evictedData = (char*)malloc(evict->evictedLen); memcpy(evict->evictedData,buf,evict->evictedLen); evict->evicted = true; - fprintf(stderr,"class: %u, serverFlags: %u, evictedFlags: %u\n",evict->clsid,evict->serverFlags,evict->evictedFlags); + //fprintf(stderr,"class: %u, serverFlags: %u, evictedFlags: %u\n",evict->clsid,evict->serverFlags,evict->evictedFlags); } else if ( (opcode == CMD_TOUCH && status == RESP_NOT_FOUND) || (opcode == CMD_DELETE && status == RESP_NOT_FOUND) ) { found = false; @@ -1521,16 +1601,6 @@ int ConnectionMultiApproxShm::read_response_l1() { } pthread_mutex_lock(lock_in[1]); unsigned char *in = bipbuf_peek(bipbuf_in[1],24); - //int tries = 0; - //while (input == NULL) { - // tries++; - // if (tries > 1000) { - // fprintf(stderr,"more than 1000 tries for header cid: %d\n",cid); - // break; - // } - // in = bipbuf_poll(bipbuf_in[1],24); - // - //} if (in) { memcpy(input,in,24); pthread_mutex_unlock(lock_in[1]); @@ -1689,16 +1759,6 @@ void ConnectionMultiApproxShm::read_response_l2() { pthread_mutex_lock(lock_in[2]); unsigned char *in = bipbuf_peek(bipbuf_in[2],24); - //int tries = 0; - //while (in == NULL) { - // tries++; - // if (tries > 2000) { - // fprintf(stderr,"more than 2000 tries for header cid: %d\n",cid); - // break; - // } - // in = bipbuf_poll(bipbuf_in[2],24); - // - //} if (in) { memcpy(input,in,24); pthread_mutex_unlock(lock_in[2]); diff --git a/cmdline.ggo b/cmdline.ggo index f72097d..73e4b34 100644 --- a/cmdline.ggo +++ b/cmdline.ggo @@ -11,6 +11,7 @@ option "quiet" - "Disable log messages." text "\nBasic options:" option "use_shm" - "use shared memory" +option "use_shm_batch" - "use shared memory BATCHED" option "ratelimit" - "limit conns from exceeding each other in requests" option "v1callback" - "use v1 readcallbacks" option "server" s "Memcached server hostname[:port]. \ diff --git a/mutilate.cc b/mutilate.cc index a9b415b..a1f298a 100644 --- a/mutilate.cc +++ b/mutilate.cc @@ -1377,7 +1377,7 @@ void do_mutilate(const vector& servers, options_t& options, if (servers.size() == 1) { vector connections; vector server_lead; - for (auto s: servers) { + for (auto s: servers) { // Split args.server_arg[s] into host:port using strtok(). char *s_copy = new char[s.length() + 1]; strcpy(s_copy, s.c_str()); @@ -1629,7 +1629,7 @@ void do_mutilate(const vector& servers, options_t& options, event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); - } else if (servers.size() == 2 && ! ( args.approx_given || args.approx_batch_given || args.use_shm_given)) { + } else if (servers.size() == 2 && !(args.approx_given || args.approx_batch_given || args.use_shm_given || args.use_shm_batch_given)) { vector connections; vector server_lead; @@ -1786,7 +1786,7 @@ void do_mutilate(const vector& servers, options_t& options, event_config_free(config); evdns_base_free(evdns, 0); event_base_free(base); - } else if (servers.size() == 2 && args.approx_given && !args.approx_batch_given && !args.use_shm_given) { + } else if (servers.size() == 2 && args.approx_given) { vector connections; vector server_lead; @@ -1944,7 +1944,7 @@ void do_mutilate(const vector& servers, options_t& options, evdns_base_free(evdns, 0); event_base_free(base); - } else if (servers.size() == 2 && args.approx_batch_given && !args.use_shm_given) { + } else if (servers.size() == 2 && args.approx_batch_given) { vector connections; vector server_lead; @@ -2139,8 +2139,8 @@ void do_mutilate(const vector& servers, options_t& options, // wait for all threads to reach here pthread_barrier_wait(&barrier); - //fprintf(stderr,"Start = %f\n", start); double start = get_time(); + fprintf(stderr,"Start = %f\n", start); double now = start; for (ConnectionMultiApproxShm *conn: connections) { conn->start_time = now; @@ -2157,9 +2157,68 @@ void do_mutilate(const vector& servers, options_t& options, stats.accumulate(conn->stats); delete conn; } + double stop = get_time(); + fprintf(stderr,"Stop = %f\n", stop); + stats.start = start; + stats.stop = stop; + + + } else if (servers.size() == 2 && args.use_shm_batch_given) { + vector connections; + + int conns = args.measure_connections_given ? args.measure_connections_arg : + options.connections; + + srand(time(NULL)); + for (int c = 0; c < conns; c++) { + + + ConnectionMultiApproxBatchShm* conn = new ConnectionMultiApproxBatchShm(options,args.agentmode_given ? false : true); + int connected = 0; + if (conn && conn->do_connect()) { + connected = 1; + } + int cid = conn->get_cid(); + + if (connected) { + fprintf(stderr,"cid %d gets trace_queue\nfirst: %s\n",cid,trace_queue->at(cid)->front()->key); + if (g_lock != NULL) { + conn->set_g_wbkeys(g_wb_keys); + conn->set_lock(g_lock); + } + conn->set_queue(trace_queue->at(cid)); + connections.push_back(conn); + } else { + fprintf(stderr,"conn multi: %d, not connected!!\n",c); + + } + } + + // wait for all threads to reach here + pthread_barrier_wait(&barrier); + double start = get_time(); + fprintf(stderr,"Start = %f\n", start); + double now = start; + for (ConnectionMultiApproxBatchShm *conn: connections) { + conn->start_time = now; + conn->drive_write_machine_shm(now); + } + + + + if (master && !args.scan_given && !args.search_given) + V("stopped at %f options.time = %d", get_time(), options.time); + // Tear-down and accumulate stats. + for (ConnectionMultiApproxBatchShm *conn: connections) { + stats.accumulate(conn->stats); + delete conn; + } + double stop = get_time(); + fprintf(stderr,"Stop = %f\n", stop); stats.start = start; - stats.stop = now; + stats.stop = stop; + } } From 457d183bf2b9cbf23df533558833796edb1532da Mon Sep 17 00:00:00 2001 From: Daniel Byrne Date: Tue, 14 Jun 2022 20:34:03 -0400 Subject: [PATCH 57/57] works --- Connection.h | 15 ++- ConnectionMultiApproxBatchShm.cc | 157 ++++++++++++++++++------------- SConstruct | 13 ++- bipbuffer.cc | 4 +- bipbuffer.h | 1 + 5 files changed, 112 insertions(+), 78 deletions(-) diff --git a/Connection.h b/Connection.h index 7125eaa..b617b9e 100644 --- a/Connection.h +++ b/Connection.h @@ -733,8 +733,10 @@ class ConnectionMultiApproxShm { bipbuf_t* bipbuf_out[3]; pthread_mutex_t* lock_in[3]; pthread_mutex_t* lock_out[3]; - pthread_cond_t* cond_in[3]; - pthread_cond_t* cond_out[3]; + pthread_cond_t* cond_in_not_empty[3]; + pthread_cond_t* cond_in_not_full[3]; + pthread_cond_t* cond_out_not_empty[3]; + pthread_cond_t* cond_out_not_full[3]; private: string hostname1; @@ -885,8 +887,13 @@ class ConnectionMultiApproxBatchShm { bipbuf_t* bipbuf_out[3]; pthread_mutex_t* lock_in[3]; pthread_mutex_t* lock_out[3]; - pthread_cond_t* cond_in[3]; - pthread_cond_t* cond_out[3]; + + int *bipbuf_out_bytes[3]; + int *bipbuf_in_bytes[3]; + pthread_cond_t* cond_in_not_empty[3]; + pthread_cond_t* cond_in_not_full[3]; + pthread_cond_t* cond_out_not_empty[3]; + pthread_cond_t* cond_out_not_full[3]; private: string hostname1; diff --git a/ConnectionMultiApproxBatchShm.cc b/ConnectionMultiApproxBatchShm.cc index 212f488..21e7593 100644 --- a/ConnectionMultiApproxBatchShm.cc +++ b/ConnectionMultiApproxBatchShm.cc @@ -463,8 +463,12 @@ int ConnectionMultiApproxBatchShm::do_connect() { bipbuf_t bipbuf_out; pthread_mutex_t lock_in; pthread_mutex_t lock_out; - pthread_cond_t cond_in; - pthread_cond_t cond_out; + pthread_cond_t cond_in_not_empty; + pthread_cond_t cond_in_not_full; + pthread_cond_t cond_out_not_empty; + pthread_cond_t cond_out_not_full; + int bipbuf_in_bytes; + int bipbuf_out_bytes; int shared_id; } shared_t; @@ -504,15 +508,24 @@ int ConnectionMultiApproxBatchShm::do_connect() { bipbuf_out[1] = &share_l1->bipbuf_in; bipbuf_out[2] = &share_l2->bipbuf_in; + bipbuf_in_bytes[1] = &share_l1->bipbuf_out_bytes; + bipbuf_in_bytes[2] = &share_l2->bipbuf_out_bytes; + bipbuf_out_bytes[1] = &share_l1->bipbuf_in_bytes; + bipbuf_out_bytes[2] = &share_l2->bipbuf_in_bytes; + lock_in[1] = &share_l1->lock_out; lock_in[2] = &share_l2->lock_out; lock_out[1] = &share_l1->lock_in; lock_out[2] = &share_l2->lock_in; - cond_in[1] = &share_l1->cond_out; - cond_in[2] = &share_l2->cond_out; - cond_out[1] = &share_l1->cond_in; - cond_out[2] = &share_l2->cond_in; + cond_in_not_empty[1] = &share_l1->cond_out_not_empty; + cond_in_not_empty[2] = &share_l2->cond_out_not_empty; + cond_in_not_full[1] = &share_l1->cond_out_not_full; + cond_in_not_full[2] = &share_l2->cond_out_not_full; + cond_out_not_empty[1] = &share_l1->cond_in_not_empty; + cond_out_not_empty[2] = &share_l2->cond_in_not_empty; + cond_out_not_full[1] = &share_l1->cond_in_not_full; + cond_out_not_full[2] = &share_l2->cond_in_not_full; read_state = IDLE; return connected; } @@ -631,24 +644,25 @@ int ConnectionMultiApproxBatchShm::send_write_buffer(int level) { pthread_mutex_lock(lock_out[level]); int to_write = buffer_write_nbytes[level]; int gtg = bipbuf_unused(bipbuf_out[level]) >= to_write ? 1 : 0; - if (gtg) { - int ret = bipbuf_offer(bipbuf_out[level],buffer_write[level],to_write); - if (ret != to_write) { - fprintf(stderr,"error writing buffer! level %d, size %d\n",level,to_write); - } - issued_queue[level] = buffer_write_n[level]; - buffer_write_n[level] = 0; - buffer_write_pos[level] = buffer_write[level]; - memset(buffer_write_pos[level],0,buffer_write_nbytes[level]); - stats.tx_bytes += buffer_write_nbytes[level]; - buffer_write_nbytes[level] = 0; - rc = 2; - pthread_cond_signal(cond_out[level]); - pthread_mutex_unlock(lock_out[level]); - } else { - pthread_mutex_unlock(lock_out[level]); - rc = 1; + while (gtg == 0) { + pthread_cond_wait(cond_out_not_full[level],lock_out[level]); + gtg = bipbuf_unused(bipbuf_out[level]) >= to_write ? 1 : 0; } + int ret = bipbuf_offer(bipbuf_out[level],buffer_write[level],to_write); + if (ret != to_write) { + fprintf(stderr,"error writing buffer! level %d, size %d\n",level,to_write); + } + *bipbuf_out_bytes[level] += to_write; + //fprintf(stderr,"writing %d to %d, total %d\n",to_write,level,*bipbuf_out_bytes[level]); + issued_queue[level] = buffer_write_n[level]; + buffer_write_n[level] = 0; + buffer_write_pos[level] = buffer_write[level]; + memset(buffer_write_pos[level],0,buffer_write_nbytes[level]); + stats.tx_bytes += buffer_write_nbytes[level]; + buffer_write_nbytes[level] = 0; + rc = 2; + pthread_cond_signal(cond_out_not_empty[level]); + pthread_mutex_unlock(lock_out[level]); return rc; } @@ -675,18 +689,6 @@ int ConnectionMultiApproxBatchShm::add_get_op_to_queue(Operation *pop, int level //} h.opaque = htonl(pop->opaque); - //int to_write = buffer_write_nbytes[level] + 24 + keylen; - //int gtg = bipbuf_unused(bipbuf_out[level]) >= to_write ? 1 : 0; - //if (gtg == 0) { - // switch (level) { - // case 1: - // read_callback1(); - // break; - // case 2: - // read_callback2(); - // break; - // } - //} memcpy(buffer_write_pos[level], &h, 24); buffer_write_pos[level] += 24; @@ -696,7 +698,7 @@ int ConnectionMultiApproxBatchShm::add_get_op_to_queue(Operation *pop, int level buffer_write_nbytes[level] += 24 + keylen; int res = 1; - if (buffer_write_n[level] >= (uint32_t)options.depth && cb == 0) { + if (buffer_write_n[level] >= (uint32_t)options.depth) { // && cb == 0) { res = send_write_buffer(level); } return res; @@ -733,7 +735,7 @@ int ConnectionMultiApproxBatchShm::issue_get_with_len(Operation *pop, double now } //put op into queue - return add_get_op_to_queue(pop,level); + return add_get_op_to_queue(pop,level,0); } /** @@ -771,7 +773,7 @@ int ConnectionMultiApproxBatchShm::issue_get_with_len(const char* key, int value pop->l1 = l1; } - return add_get_op_to_queue(pop,level,0); + return add_get_op_to_queue(pop,level,1); } @@ -978,7 +980,7 @@ int ConnectionMultiApproxBatchShm::add_set_to_queue(Operation *pop, int level, c buffer_write_nbytes[level] += length + 32 + keylen; int ret = 1; - if (buffer_write_n[level] >= (uint32_t)options.depth && cb == 0) { + if (buffer_write_n[level] >= (uint32_t)options.depth) { // && cb == 0) { ret = send_write_buffer(level); } return ret; @@ -1001,7 +1003,7 @@ int ConnectionMultiApproxBatchShm::issue_set(Operation *pop, const char* value, pop->opaque = opaque[level]++; pop->flags = flags; - return add_set_to_queue(pop,level,value); + return add_set_to_queue(pop,level,value,0); } @@ -1027,7 +1029,7 @@ int ConnectionMultiApproxBatchShm::issue_set(const char* key, const char* value, pop->flags = flags; pop->clsid = get_class(length,strlen(key)); - return add_set_to_queue(pop,level,value,0); + return add_set_to_queue(pop,level,value,1); } @@ -1202,7 +1204,7 @@ size_t ConnectionMultiApproxBatchShm::handle_response_batch(unsigned char *rbuf_ size_t read_bytes, size_t consumed_bytes, int level, int extra) { if (rbuf_pos[0] != 129) { - //fprintf(stderr,"cid %d we don't have a valid header %u\n",cid,rbuf_pos[0]); + fprintf(stderr,"cid %d we don't have a valid header %u\n",cid,rbuf_pos[0]); //buffer_read_pos[level] = rbuf_pos; //buffer_read_n[level] = 1; return 0; @@ -1216,7 +1218,7 @@ size_t ConnectionMultiApproxBatchShm::handle_response_batch(unsigned char *rbuf_ //buffer_lasthdr[level] = rbuf_pos; //buffer_read_n[level] = need; //buffer_read_nbytes[level] = have; - //fprintf(stderr,"cid %d - we don't have enough header data, need %lu more bytes, have %lu (targetLen: %d) (read_bytes %ld) (extra %d) %d)\n",cid,need,have,24,read_bytes,extra,level); + fprintf(stderr,"cid %d - we don't have enough header data, need %lu more bytes, have %lu (targetLen: %d) (read_bytes %ld) (extra %d) %d)\n",cid,need,have,24,read_bytes,extra,level); return 0; } @@ -1232,7 +1234,7 @@ size_t ConnectionMultiApproxBatchShm::handle_response_batch(unsigned char *rbuf_ buffer_read_n[level] = need; buffer_read_nbytes[level] = have; memcpy(buffer_leftover[level],rbuf_pos,have); - //fprintf(stderr,"cid %d - we don't have enough data, need %lu more bytes, have %lu (targetLen: %d) (read_bytes %ld) (extra %d) %d)\n",cid,need,have,targetLen,read_bytes,extra,level); + fprintf(stderr,"cid %d - we don't have enough data, need %lu more bytes, have %lu (targetLen: %d) (read_bytes %ld) (extra %d) %d)\n",cid,need,have,targetLen,read_bytes,extra,level); return 0; } @@ -1298,43 +1300,51 @@ size_t ConnectionMultiApproxBatchShm::fill_read_buffer(int level, int *extra) { size_t read_bytes = 0; pthread_mutex_lock(lock_in[level]); + //int len = *bipbuf_in_bytes[level]; int len = bipbuf_used(bipbuf_in[level]); while (len == 0) { - pthread_cond_wait(cond_in[level],lock_in[level]); + pthread_cond_wait(cond_in_not_empty[level],lock_in[level]); + //len = *bipbuf_in_bytes[level]; len = bipbuf_used(bipbuf_in[level]); } + unsigned int all = 0; + if (buffer_read_n[level] != 0) { uint32_t have = buffer_read_nbytes[level]; + fprintf(stderr,"already have %u\n",have); //if ((size_t)len < buffer_read_n[level]) { // pthread_mutex_unlock(lock_in[level]); // return 0; //} - unsigned char* input = bipbuf_poll(bipbuf_in[level],len); - if (!input || len == 0) { - if (!input && len > 0) - fprintf(stderr,"cid %d expected %d on level %d (already have %u)\n",cid,len,level,have); + unsigned char* input = bipbuf_peek_all(bipbuf_in[level],&all); + if (!input || all == 0) { + if (!input && all > 0) + fprintf(stderr,"cid %d expected %d on level %d (already have %u)\n",cid,all,level,have); pthread_mutex_unlock(lock_in[level]); return 0; } memcpy(buffer_read[level],buffer_leftover[level],have); buffer_read_pos[level] = buffer_read[level]; - memcpy(buffer_read_pos[level]+have,input,len); - read_bytes = len; + memcpy(buffer_read_pos[level]+have,input,all); + read_bytes = all; *extra = have; buffer_read_n[level] = 0; buffer_read_nbytes[level] = 0; } else { - unsigned char *input = bipbuf_poll(bipbuf_in[level],len); - if (!input || len == 0) { - if (!input && len > 0) - fprintf(stderr,"cid %d expected %d on level %d\n",cid,len,level); + unsigned char *input = bipbuf_peek_all(bipbuf_in[level],&all); + if (!input || all == 0) { + if (!input && all > 0) + fprintf(stderr,"cid %d expected %d on level %d\n",cid,all,level); pthread_mutex_unlock(lock_in[level]); return 0; } - read_bytes = len; + read_bytes = all; buffer_read_pos[level] = input; +#ifdef DEBUGMC + fprintf(stderr,"read %d of %d (avail: %d) on l%d\n",all,*bipbuf_in_bytes[level],len,level); +#endif //memcpy(buffer_read_pos[level],input,len); *extra = 0; @@ -1356,6 +1366,9 @@ void ConnectionMultiApproxBatchShm::read_callback1() { read_bytes = fill_read_buffer(level,&extra); if (read_bytes == 0) { + pthread_mutex_lock(lock_in[level]); + pthread_cond_signal(cond_in_not_full[level]); + pthread_mutex_unlock(lock_in[level]); return; } @@ -1468,16 +1481,19 @@ void ConnectionMultiApproxBatchShm::read_callback1() { free(evict); } nread_ops++; - if (buffer_read_pos[level][0] == 0) { - break; - } - if (buffer_read_pos[level][0] != 129) { - //fprintf(stderr,"cid %d we don't have a valid header post %u\n",cid,buffer_read_pos[level][0]); + if (buffer_read_pos[level][0] != 129 || (read_bytes - consumed_bytes == 0)) { break; } + //if (buffer_read_pos[level][0] != 129) { + // //fprintf(stderr,"cid %d we don't have a valid header post %u\n",cid,buffer_read_pos[level][0]); + // break; + //} } - - + pthread_mutex_lock(lock_in[level]); + bipbuf_poll(bipbuf_in[level],read_bytes); + *bipbuf_in_bytes[level] = *bipbuf_in_bytes[level] - read_bytes; + pthread_cond_signal(cond_in_not_full[level]); + pthread_mutex_unlock(lock_in[level]); double now = get_time(); last_tx = now; stats.log_op(op_queue_size[1]); @@ -1498,6 +1514,9 @@ void ConnectionMultiApproxBatchShm::read_callback2() { read_bytes = fill_read_buffer(level,&extra); if (read_bytes == 0) { + pthread_mutex_lock(lock_in[level]); + pthread_cond_signal(cond_in_not_full[level]); + pthread_mutex_unlock(lock_in[level]); return; } @@ -1599,11 +1618,7 @@ void ConnectionMultiApproxBatchShm::read_callback2() { DIE("not implemented"); } nread_ops++; - if (buffer_read_pos[level][0] == 0) { - break; - } - if (buffer_read_pos[level][0] != 129) { - //fprintf(stderr,"l2 cid %d we don't have a valid header post %u\n",cid,buffer_read_pos[level][0]); + if (buffer_read_pos[level][0] != 129 || (read_bytes - consumed_bytes == 0)) { break; } } @@ -1613,6 +1628,12 @@ void ConnectionMultiApproxBatchShm::read_callback2() { //if (nread_ops == 0) { // fprintf(stderr,"ugh l2 only got: %lu ops expected %lu\n",nread_ops,batch); //} + + pthread_mutex_lock(lock_in[level]); + bipbuf_poll(bipbuf_in[level],read_bytes); + *bipbuf_in_bytes[level] = *bipbuf_in_bytes[level] - read_bytes; + pthread_cond_signal(cond_in_not_full[level]); + pthread_mutex_unlock(lock_in[level]); double now = get_time(); diff --git a/SConstruct b/SConstruct index 212fb8f..f2a4e64 100644 --- a/SConstruct +++ b/SConstruct @@ -44,10 +44,10 @@ if not conf.CheckFunc('pthread_barrier_init'): env = conf.Finish() -env.Append(CFLAGS = '-O0 -Wall -g --std=c++17 -lstdc++fs') -env.Append(CPPFLAGS = '-O0 -Wall -g --std=c++17 -lstdc++fs') -#env.Append(CFLAGS = ' -O3 -Wall -g --std=c++17 -lstdc++fs') -#env.Append(CPPFLAGS = ' -O3 -Wall -g --std=c++17 -lstdc++fs') +#env.Append(CFLAGS = '-O0 -Wall -g --std=c++17 -lstdc++fs -fsanitize=address') +#env.Append(CPPFLAGS = '-O0 -Wall -g --std=c++17 -lstdc++fs -fsanitize=address') +env.Append(CFLAGS = ' -O2 -Wall -g --std=c++17 -lstdc++fs') +env.Append(CPPFLAGS = ' -O2 -Wall -g --std=c++17 -lstdc++fs') #env.Append(CFLAGS = ' -O3 -Wall -g') #env.Append(CPPFLAGS = ' -O3 -Wall -g') #env.Append(LDFLAGS = '-fsantize=address') @@ -62,7 +62,10 @@ env.Append(CPPFLAGS = '-O0 -Wall -g --std=c++17 -lstdc++fs') env.Command(['cmdline.cc', 'cmdline.h'], 'cmdline.ggo', 'gengetopt < $SOURCE') src = Split("""mutilate.cc cmdline.cc log.cc distributions.cc util.cc - Connection.cc ConnectionMulti.cc ConnectionMultiApprox.cc ConnectionMultiApproxBatch.cc ConnectionMultiApproxShm.cc Protocol.cc Generator.cc bipbuffer.cc""") + Connection.cc ConnectionMulti.cc ConnectionMultiApprox.cc ConnectionMultiApproxBatchShm.cc ConnectionMultiApproxBatch.cc ConnectionMultiApproxShm.cc Protocol.cc Generator.cc bipbuffer.cc""") + +#src = Split("""mutilate.cc cmdline.cc log.cc distributions.cc util.cc +# ConnectionMultiApprox.cc ConnectionMultiApproxBatchShm.cc Generator.cc bipbuffer.cc""") if not env['HAVE_POSIX_BARRIER']: # USE_POSIX_BARRIER: src += ['barrier.cc'] diff --git a/bipbuffer.cc b/bipbuffer.cc index 03e306f..b712617 100644 --- a/bipbuffer.cc +++ b/bipbuffer.cc @@ -69,8 +69,10 @@ int bipbuf_is_empty(const bipbuf_t* me) * ie. is the distance from A to buffer's end less than B to A? */ static void __check_for_switch_to_b(bipbuf_t* me) { - if (me->size - me->a_end < me->a_start - me->b_end) + if (me->size - me->a_end < me->a_start - me->b_end) { + //fprintf(stderr,"%p switching to b, a_start: %d, a_end: %d, b_end %d\n",me,me->a_start,me->a_end,me->b_end); me->b_inuse = 1; + } } /* TODO: DOCUMENT THESE TWO FUNCTIONS */ diff --git a/bipbuffer.h b/bipbuffer.h index 7ccf1f7..f99f148 100644 --- a/bipbuffer.h +++ b/bipbuffer.h @@ -3,6 +3,7 @@ #define BIPBUFSIZE 4*1024*1024 #include "binary_protocol.h" +#include extern "C" { typedef struct