From 295897a13501f8c28040c5493eded70a2561ec03 Mon Sep 17 00:00:00 2001 From: Oran Agra Date: Tue, 8 Jul 2014 12:30:41 +0300 Subject: [PATCH] * Version 1.2.0 * Add --data-size-pattern * Add --key-pattern now has Gaussian distribution, and also --key-stddev, --key-median * Add --data-offset * Add --hide-histogram --- CHANGES | 6 ++ client.cpp | 54 ++++++++++++--- client.h | 2 +- configure.ac | 2 +- memtier_benchmark.1 | 24 ++++++- memtier_benchmark.cpp | 125 ++++++++++++++++++++++++++++----- memtier_benchmark.h | 5 ++ obj_gen.cpp | 156 ++++++++++++++++++++++++++++++++---------- obj_gen.h | 57 +++++++++++---- protocol.cpp | 77 +++++++++++++++------ protocol.h | 4 +- 11 files changed, 403 insertions(+), 109 deletions(-) mode change 100644 => 100755 memtier_benchmark.cpp diff --git a/CHANGES b/CHANGES index 668b504a..55ec8bf7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +* Version 1.2.0 + * Add --data-size-pattern + * Add --key-pattern now has Gaussian distribution, and also --key-stddev, --key-median + * Add --data-offset + * Add --hide-histogram + * Version 1.1.0 * Add --data-verify and --verify options to allow data verification. * Add --no-expiry option to ignore expiry of imported data. diff --git a/client.cpp b/client.cpp index 8648f1e4..21ed3944 100644 --- a/client.cpp +++ b/client.cpp @@ -471,8 +471,12 @@ void client::create_request(void) // are we set or get? this depends on the ratio if (m_set_ratio_count < m_config->ratio.a) { // set command - - data_object *obj = m_obj_gen->get_object(m_config->key_pattern[0] == 'R' ? 0 : 1); + int iter = OBJECT_GENERATOR_KEY_SET_ITER; + if (m_config->key_pattern[0] == 'R') + iter = OBJECT_GENERATOR_KEY_RANDOM; + else if (m_config->key_pattern[0] == 'G') + iter = OBJECT_GENERATOR_KEY_GAUSSIAN; + data_object *obj = m_obj_gen->get_object(iter); unsigned int key_len; const char *key = obj->get_key(&key_len); unsigned int value_len; @@ -483,7 +487,7 @@ void client::create_request(void) benchmark_debug_log("SET key=[%.*s] value_len=%u expiry=%u\n", key_len, key, value_len, obj->get_expiry()); cmd_size = m_protocol->write_command_set(key, key_len, value, value_len, - obj->get_expiry()); + obj->get_expiry(), m_config->data_offset); m_pipeline.push(new client::request(rt_set, cmd_size, NULL, 1)); } else if (m_get_ratio_count < m_config->ratio.b) { @@ -500,7 +504,12 @@ void client::create_request(void) m_keylist->clear(); while (m_keylist->get_keys_count() < keys_count) { unsigned int keylen; - const char *key = m_obj_gen->get_key(m_config->key_pattern[2] == 'R' ? 0 : 2, &keylen); + int iter = OBJECT_GENERATOR_KEY_GET_ITER; + if (m_config->key_pattern[2] == 'R') + iter = OBJECT_GENERATOR_KEY_RANDOM; + else if (m_config->key_pattern[2] == 'G') + iter = OBJECT_GENERATOR_KEY_GAUSSIAN; + const char *key = m_obj_gen->get_key(iter, &keylen); assert(key != NULL); assert(keylen > 0); @@ -521,12 +530,17 @@ void client::create_request(void) m_pipeline.push(new client::request(rt_get, cmd_size, NULL, m_keylist->get_keys_count())); } else { unsigned int keylen; - const char *key = m_obj_gen->get_key(m_config->key_pattern[2] == 'R' ? 0 : 2, &keylen); + int iter = OBJECT_GENERATOR_KEY_GET_ITER; + if (m_config->key_pattern[2] == 'R') + iter = OBJECT_GENERATOR_KEY_RANDOM; + else if (m_config->key_pattern[2] == 'G') + iter = OBJECT_GENERATOR_KEY_GAUSSIAN; + const char *key = m_obj_gen->get_key(iter, &keylen); assert(key != NULL); assert(keylen > 0); benchmark_debug_log("GET key=[%.*s]\n", keylen, key); - cmd_size = m_protocol->write_command_get(key, keylen); + cmd_size = m_protocol->write_command_get(key, keylen, m_config->data_offset); m_get_ratio_count++; m_pipeline.push(new client::request(rt_get, cmd_size, NULL, 1)); @@ -734,7 +748,12 @@ void verify_client::create_request(void) if (m_set_ratio_count < m_config->ratio.a) { // Prepare a GET request that will be compared against a previous // SET request. - data_object *obj = m_obj_gen->get_object(m_config->key_pattern[0] == 'R' ? 0 : 1); + int iter = OBJECT_GENERATOR_KEY_SET_ITER; + if (m_config->key_pattern[0] == 'R') + iter = OBJECT_GENERATOR_KEY_RANDOM; + else if (m_config->key_pattern[0] == 'G') + iter = OBJECT_GENERATOR_KEY_GAUSSIAN; + data_object *obj = m_obj_gen->get_object(iter); unsigned int key_len; const char *key = obj->get_key(&key_len); unsigned int value_len; @@ -742,7 +761,7 @@ void verify_client::create_request(void) unsigned int cmd_size; m_set_ratio_count++; - cmd_size = m_protocol->write_command_get(key, key_len); + cmd_size = m_protocol->write_command_get(key, key_len, m_config->data_offset); m_pipeline.push(new verify_client::verify_request(rt_get, cmd_size, NULL, 1, key, key_len, value, value_len)); @@ -758,7 +777,12 @@ void verify_client::create_request(void) m_keylist->clear(); while (m_keylist->get_keys_count() < keys_count) { unsigned int keylen; - const char *key = m_obj_gen->get_key(m_config->key_pattern[2] == 'R' ? 0 : 2, &keylen); + int iter = OBJECT_GENERATOR_KEY_GET_ITER; + if (m_config->key_pattern[2] == 'R') + iter = OBJECT_GENERATOR_KEY_RANDOM; + else if (m_config->key_pattern[2] == 'G') + iter = OBJECT_GENERATOR_KEY_GAUSSIAN; + const char *key = m_obj_gen->get_key(iter, &keylen); assert(key != NULL); assert(keylen > 0); @@ -769,7 +793,12 @@ void verify_client::create_request(void) m_get_ratio_count += keys_count; } else { unsigned int keylen; - m_obj_gen->get_key(m_config->key_pattern[2] == 'R' ? 0 : 2, &keylen); + int iter = OBJECT_GENERATOR_KEY_GET_ITER; + if (m_config->key_pattern[2] == 'R') + iter = OBJECT_GENERATOR_KEY_RANDOM; + else if (m_config->key_pattern[2] == 'G') + iter = OBJECT_GENERATOR_KEY_GAUSSIAN; + m_obj_gen->get_key(iter, &keylen); m_get_ratio_count++; } @@ -1342,7 +1371,7 @@ void run_stats::summarize(totals& result) const result.m_bytes_sec = (double) ((result.m_bytes / 1024) / test_duration_sec); } -void run_stats::print(FILE *out) +void run_stats::print(FILE *out, bool histogram) { // aggregate all one_second_stats; we do this only if we have // one_second_stats, otherwise it means we're probably printing previously @@ -1382,6 +1411,9 @@ void run_stats::print(FILE *out) m_totals.m_latency, m_totals.m_bytes_sec); + if (!histogram) + return; + fprintf(out, "\n\n" "Request Latency Distribution\n" diff --git a/client.h b/client.h index bb2efa33..d618ff44 100644 --- a/client.h +++ b/client.h @@ -109,7 +109,7 @@ class run_stats { void merge(const run_stats& other); bool save_csv(const char *filename); void debug_dump(void); - void print(FILE *file); + void print(FILE *file, bool histogram); unsigned int get_duration(void); unsigned long int get_duration_usec(void); diff --git a/configure.ac b/configure.ac index 4dd21631..19c61d34 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,7 @@ dnl You should have received a copy of the GNU General Public License dnl along with this program. If not, see . AC_PREREQ(2.59) -AC_INIT(memtier_benchmark,1.0.2,yossigo@gmail.com) +AC_INIT(memtier_benchmark,1.2.0,yossigo@gmail.com) AC_CONFIG_SRCDIR([memtier_benchmark.cpp]) AC_CONFIG_HEADER([config.h]) AM_INIT_AUTOMAKE diff --git a/memtier_benchmark.1 b/memtier_benchmark.1 index 63c3cd3c..f8135be9 100644 --- a/memtier_benchmark.1 +++ b/memtier_benchmark.1 @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. -.TH MEMTIER_BENCHMARK "1" "August 2013" "memtier_benchmark 1.0.2" "User Commands" +.TH MEMTIER_BENCHMARK "1" "July 2014" "memtier_benchmark 1.2.0" "User Commands" .SH NAME memtier_benchmark \- NoSQL benchmark tool .SH SYNOPSIS @@ -37,6 +37,9 @@ Name of output file (default: stdout) .TP \fB\-\-show\-config\fR Print detailed configuration before running +.TP +\fB\-\-hide\-histogram\fR +Don't print detailed latency histogram .SS "Test Options:" .TP \fB\-n\fR, \fB\-\-requests\fR=\fINUMBER\fR @@ -73,6 +76,10 @@ DB number to select, when testing a redis server \fB\-d\fR \fB\-\-data\-size\fR=\fISIZE\fR Object data size (default: 32) .TP +\fB\-\-data\-offset\fR=\fIOFFSET\fR +Actual size of value will be data\-size + data\-offset +Will use SETRANGE / GETRANGE (default: 0) +.TP \fB\-R\fR \fB\-\-random\-data\fR Indicate that data should be randomized .TP @@ -82,6 +89,12 @@ Use random\-sized items in the specified range (min\-max) \fB\-\-data\-size\-list\fR=\fILIST\fR Use sizes from weight list (size1:weight1,..sizeN:weightN) .TP +\fB\-\-data\-size\-pattern\fR=\fIR\fR|S +Use together with data\-size\-range +when set to R, a random size from the defined data sizes will be used, +when set to S, the defined data sizes will be evenly distributed across +the key range, see \fB\-\-key\-maximum\fR (default R) +.TP \fB\-\-expiry\-range\fR=\fIRANGE\fR Use random expiry values from the specified range .SS "Imported Data Options:" @@ -113,6 +126,15 @@ Key ID maximum value (default: 10000000) .TP \fB\-\-key\-pattern\fR=\fIPATTERN\fR Set:Get pattern (default: R:R) +G for Gaussian distribution, R for uniform Random, S for Sequential +.TP +\fB\-\-key\-stddev\fR +The standard deviation used in the Gaussian distribution +(default is key range / 6) +.TP +\fB\-\-key\-median\fR +The median point used in the Gaussian distribution +(default is the center of the key range) .TP \fB\-\-help\fR Display this help diff --git a/memtier_benchmark.cpp b/memtier_benchmark.cpp old mode 100644 new mode 100755 index ec5c4b03..ee76cb21 --- a/memtier_benchmark.cpp +++ b/memtier_benchmark.cpp @@ -97,9 +97,11 @@ static void config_print(FILE *file, struct benchmark_config *cfg) "ratio = %u:%u\n" "pipeline = %u\n" "data_size = %u\n" + "data_offset = %u\n" "random_data = %s\n" "data_size_range = %u-%u\n" "data_size_list = %s\n" + "data_size_pattern = %s\n" "expiry_range = %u-%u\n" "data_import = %s\n" "data_verify = %s\n" @@ -109,6 +111,8 @@ static void config_print(FILE *file, struct benchmark_config *cfg) "key_minimum = %u\n" "key_maximum = %u\n" "key_pattern = %s\n" + "key_stddev = %f\n" + "key_median = %f\n" "reconnect_interval = %u\n" "multi_key_get = %u\n" "authenticate = %s\n" @@ -129,9 +133,11 @@ static void config_print(FILE *file, struct benchmark_config *cfg) cfg->ratio.a, cfg->ratio.b, cfg->pipeline, cfg->data_size, + cfg->data_offset, cfg->random_data ? "yes" : "no", cfg->data_size_range.min, cfg->data_size_range.max, cfg->data_size_list.print(tmpbuf, sizeof(tmpbuf)-1), + cfg->data_size_pattern, cfg->expiry_range.min, cfg->expiry_range.max, cfg->data_import, cfg->data_verify ? "yes" : "no", @@ -141,6 +147,8 @@ static void config_print(FILE *file, struct benchmark_config *cfg) cfg->key_minimum, cfg->key_maximum, cfg->key_pattern, + cfg->key_stddev, + cfg->key_median, cfg->reconnect_interval, cfg->multi_key_get, cfg->authenticate ? cfg->authenticate : "", @@ -178,6 +186,8 @@ static void config_init_defaults(struct benchmark_config *cfg) } if (!cfg->key_pattern) cfg->key_pattern = "R:R"; + if (!cfg->data_size_pattern) + cfg->data_size_pattern = "R"; } static int config_parse_args(int argc, char *argv[], struct benchmark_config *cfg) @@ -188,6 +198,8 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf o_pipeline, o_data_size_range, o_data_size_list, + o_data_size_pattern, + o_data_offset, o_expiry_range, o_data_import, o_data_verify, @@ -196,7 +208,10 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf o_key_minimum, o_key_maximum, o_key_pattern, + o_key_stddev, + o_key_median, o_show_config, + o_hide_histogram, o_client_stats, o_reconnect_interval, o_generate_keys, @@ -215,6 +230,7 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf { "run-count", 1, 0, 'x' }, { "debug", 0, 0, 'D' }, { "show-config", 0, 0, o_show_config }, + { "hide-histogram", 0, 0, o_hide_histogram }, { "requests", 1, 0, 'n' }, { "clients", 1, 0, 'c' }, { "threads", 1, 0, 't' }, @@ -222,9 +238,11 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf { "ratio", 1, 0, o_ratio }, { "pipeline", 1, 0, o_pipeline }, { "data-size", 1, 0, 'd' }, + { "data-offset", 1, 0, o_data_offset }, { "random-data", 0, 0, 'R' }, { "data-size-range", 1, 0, o_data_size_range }, { "data-size-list", 1, 0, o_data_size_list }, + { "data-size-pattern", 1, 0, o_data_size_pattern }, { "expiry-range", 1, 0, o_expiry_range }, { "data-import", 1, 0, o_data_import }, { "data-verify", 0, 0, o_data_verify }, @@ -234,6 +252,8 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf { "key-minimum", 1, 0, o_key_minimum }, { "key-maximum", 1, 0, o_key_maximum }, { "key-pattern", 1, 0, o_key_pattern }, + { "key-stddev", 1, 0, o_key_stddev }, + { "key-median", 1, 0, o_key_median }, { "reconnect-interval", 1, 0, o_reconnect_interval }, { "multi-key-get", 1, 0, o_multi_key_get }, { "authenticate", 1, 0, 'a' }, @@ -304,6 +324,9 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf case o_show_config: cfg->show_config++; break; + case o_hide_histogram: + cfg->hide_histogram++; + break; case 'n': endptr = NULL; cfg->requests = (unsigned int) strtoul(optarg, &endptr, 10); @@ -363,34 +386,50 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf endptr = NULL; cfg->data_size = (unsigned int) strtoul(optarg, &endptr, 10); if (!cfg->data_size || !endptr || *endptr != '\0') { - fprintf(stderr, "error: data_size must be greater than zero.\n"); + fprintf(stderr, "error: data-size must be greater than zero.\n"); return -1; } break; case 'R': cfg->random_data = true; break; + case o_data_offset: + endptr = NULL; + cfg->data_offset = (unsigned int) strtoul(optarg, &endptr, 10); + if (!endptr || *endptr != '\0') { + fprintf(stderr, "error: data-offset must be greater than or equal to zero.\n"); + return -1; + } + break; case o_data_size_range: cfg->data_size_range = config_range(optarg); if (!cfg->data_size_range.is_defined() || cfg->data_size_range.min < 1) { - fprintf(stderr, "error: data_size_range must be expressed as [1-n]-[1-n].\n"); + fprintf(stderr, "error: data-size-range must be expressed as [1-n]-[1-n].\n"); return -1; } break; case o_data_size_list: cfg->data_size_list = config_weight_list(optarg); if (!cfg->data_size_list.is_defined()) { - fprintf(stderr, "error: data_size_list must be expressed as [size1:weight1],...[sizeN:weightN].\n"); + fprintf(stderr, "error: data-size-list must be expressed as [size1:weight1],...[sizeN:weightN].\n"); return -1; } break; case o_expiry_range: cfg->expiry_range = config_range(optarg); if (!cfg->expiry_range.is_defined()) { - fprintf(stderr, "error: data_size_range must be expressed as [0-n]-[1-n].\n"); + fprintf(stderr, "error: data-size-range must be expressed as [0-n]-[1-n].\n"); return -1; } break; + case o_data_size_pattern: + cfg->data_size_pattern = optarg; + if (strlen(cfg->data_size_pattern) != 1 || + (cfg->data_size_pattern[0] != 'R' && cfg->data_size_pattern[0] != 'S')) { + fprintf(stderr, "error: data-size-pattern must be either R or S.\n"); + return -1; + } + break; case o_data_import: cfg->data_import = optarg; break; @@ -408,7 +447,7 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf endptr = NULL; cfg->key_minimum = (unsigned int) strtoul(optarg, &endptr, 10); if (cfg->key_minimum < 1 || !endptr || *endptr != '\0') { - fprintf(stderr, "error: key_minimum must be greater than zero.\n"); + fprintf(stderr, "error: key-minimum must be greater than zero.\n"); return -1; } break; @@ -416,16 +455,32 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf endptr = NULL; cfg->key_maximum = (unsigned int) strtoul(optarg, &endptr, 10); if (cfg->key_maximum< 1 || !endptr || *endptr != '\0') { - fprintf(stderr, "error: key_maximum must be greater than zero.\n"); + fprintf(stderr, "error: key-maximum must be greater than zero.\n"); + return -1; + } + break; + case o_key_stddev: + endptr = NULL; + cfg->key_stddev = (unsigned int) strtof(optarg, &endptr); + if (cfg->key_stddev<= 0 || !endptr || *endptr != '\0') { + fprintf(stderr, "error: key-stddev must be greater than zero.\n"); + return -1; + } + break; + case o_key_median: + endptr = NULL; + cfg->key_median = (unsigned int) strtof(optarg, &endptr); + if (cfg->key_median<= 0 || !endptr || *endptr != '\0') { + fprintf(stderr, "error: key-median must be greater than zero.\n"); return -1; } break; case o_key_pattern: cfg->key_pattern = optarg; if (strlen(cfg->key_pattern) != 3 || cfg->key_pattern[1] != ':' || - (cfg->key_pattern[0] != 'R' && cfg->key_pattern[0] != 'S') || - (cfg->key_pattern[2] != 'R' && cfg->key_pattern[2] != 'S')) { - fprintf(stderr, "error: key_pattern must be in the format of [S/R]:[S/R].\n"); + (cfg->key_pattern[0] != 'R' && cfg->key_pattern[0] != 'S' && cfg->key_pattern[0] != 'G') || + (cfg->key_pattern[2] != 'R' && cfg->key_pattern[2] != 'S' && cfg->key_pattern[2] != 'G')) { + fprintf(stderr, "error: key-pattern must be in the format of [S/R/G]:[S/R/G].\n"); return -1; } break; @@ -433,7 +488,7 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf endptr = NULL; cfg->reconnect_interval = (unsigned int) strtoul(optarg, &endptr, 10); if (!cfg->reconnect_interval || !endptr || *endptr != '\0') { - fprintf(stderr, "error: reconnect_interval must be greater than zero.\n"); + fprintf(stderr, "error: reconnect-interval must be greater than zero.\n"); return -1; } break; @@ -444,7 +499,7 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf endptr = NULL; cfg->multi_key_get = (unsigned int) strtoul(optarg, &endptr, 10); if (cfg->multi_key_get <= 0 || !endptr || *endptr != '\0') { - fprintf(stderr, "error: multi_key_get must be greater than zero.\n"); + fprintf(stderr, "error: multi-key-get must be greater than zero.\n"); return -1; } break; @@ -454,7 +509,7 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf case o_select_db: cfg->select_db = (int) strtoul(optarg, &endptr, 10); if (cfg->select_db < 0 || !endptr || *endptr != '\0') { - fprintf(stderr, "error: select_db must be greater or equal zero.\n"); + fprintf(stderr, "error: select-db must be greater or equal zero.\n"); return -1; } break; @@ -486,6 +541,7 @@ void usage() { " --client-stats=FILE Produce per-client stats file\n" " --out-file=FILE Name of output file (default: stdout)\n" " --show-config Print detailed configuration before running\n" + " --hide-histogram Don't print detailed latency histogram\n" "\n" "Test Options:\n" " -n, --requests=NUMBER Number of total requests per client (default: 10000)\n" @@ -502,9 +558,15 @@ void usage() { "\n" "Object Options:\n" " -d --data-size=SIZE Object data size (default: 32)\n" + " --data-offset=OFFSET Actual size of value will be data-size + data-offset\n" + " Will use SETRANGE / GETRANGE (default: 0)\n" " -R --random-data Indicate that data should be randomized\n" " --data-size-range=RANGE Use random-sized items in the specified range (min-max)\n" " --data-size-list=LIST Use sizes from weight list (size1:weight1,..sizeN:weightN)\n" + " --data-size-pattern=R|S Use together with data-size-range\n" + " when set to R, a random size from the defined data sizes will be used,\n" + " when set to S, the defined data sizes will be evenly distributed across\n" + " the key range, see --key-maximum (default R)\n" " --expiry-range=RANGE Use random expiry values from the specified range\n" "\n" "Imported Data Options:\n" @@ -519,7 +581,11 @@ void usage() { " --key-minimum=NUMBER Key ID minimum value (default: 0)\n" " --key-maximum=NUMBER Key ID maximum value (default: 10000000)\n" " --key-pattern=PATTERN Set:Get pattern (default: R:R)\n" - "\n" + " G for Gaussian distribution, R for uniform Random, S for Sequential\n" + " --key-stddev The standard deviation used in the Gaussian distribution\n" + " (default is key range / 6)\n" + " --key-median The median point used in the Gaussian distribution\n" + " (default is the center of the key range)\n" " --help Display this help\n" " --version Display version information\n" "\n" @@ -816,10 +882,19 @@ int main(int argc, char *argv[]) fprintf(stderr, "error: select-db can only be used with redis protocol.\n"); usage(); } - + if (cfg.data_offset > 0) { + if (cfg.data_offset > (1<<29)-1) { + fprintf(stderr, "error: data-offset too long\n"); + usage(); + } + if (cfg.expiry_range.min || cfg.expiry_range.max || strcmp(cfg.protocol, "redis")) { + fprintf(stderr, "error: data-offset can only be used with redis protocol, and cannot be used with expiry\n"); + usage(); + } + } if (cfg.data_size) { if (cfg.data_size_list.is_defined() || cfg.data_size_range.is_defined()) { - fprintf(stderr, "error: data-size cannot be used with data_size_list or data_size_range.\n"); + fprintf(stderr, "error: data-size cannot be used with data-size-list or data-size-range.\n"); usage(); } obj_gen->set_data_size_fixed(cfg.data_size); @@ -831,6 +906,7 @@ int main(int argc, char *argv[]) obj_gen->set_data_size_list(&cfg.data_size_list); } else if (cfg.data_size_range.is_defined()) { obj_gen->set_data_size_range(cfg.data_size_range.min, cfg.data_size_range.max); + obj_gen->set_data_size_pattern(cfg.data_size_pattern); } else if (!cfg.data_import) { fprintf(stderr, "error: data-size, data-size-list or data-size-range must be specified.\n"); usage(); @@ -844,6 +920,17 @@ int main(int argc, char *argv[]) obj_gen->set_key_prefix(cfg.key_prefix); obj_gen->set_key_range(cfg.key_minimum, cfg.key_maximum); } + if (cfg.key_stddev>0 || cfg.key_median>0) { + if (cfg.key_pattern[0]!='G' && cfg.key_pattern[2]!='G') { + fprintf(stderr, "error: key-stddev and key-median are only allowed together with key-pattern set to G.\n"); + usage(); + } + if (cfg.key_median!=0 && (cfg.key_mediancfg.key_maximum)) { + fprintf(stderr, "error: key-median must be between key-minimum and key-maximum.\n"); + usage(); + } + obj_gen->set_key_distribution(cfg.key_stddev, cfg.key_median); + } obj_gen->set_expiry_range(cfg.expiry_range.min, cfg.expiry_range.max); // Prepare output file @@ -902,12 +989,12 @@ int main(int argc, char *argv[]) fprintf(outfile, "\n\n" "BEST RUN RESULTS\n" "========================================================================\n"); - best->print(outfile); + best->print(outfile, !cfg.hide_histogram); fprintf(outfile, "\n\n" "WORST RUN RESULTS\n" "========================================================================\n"); - worst->print(outfile); + worst->print(outfile, !cfg.hide_histogram); fprintf(outfile, "\n\n" "AGGREGATED AVERAGE RESULTS (%u runs)\n" @@ -915,9 +1002,9 @@ int main(int argc, char *argv[]) run_stats average; average.aggregate_average(all_stats); - average.print(outfile); + average.print(outfile, !cfg.hide_histogram); } else { - all_stats.begin()->print(outfile); + all_stats.begin()->print(outfile, !cfg.hide_histogram); } } diff --git a/memtier_benchmark.h b/memtier_benchmark.h index e28bb680..8e59a113 100644 --- a/memtier_benchmark.h +++ b/memtier_benchmark.h @@ -41,6 +41,7 @@ struct benchmark_config { unsigned int run_count; int debug; int show_config; + int hide_histogram; unsigned int requests; unsigned int clients; unsigned int threads; @@ -48,9 +49,11 @@ struct benchmark_config { config_ratio ratio; unsigned int pipeline; unsigned int data_size; + unsigned int data_offset; bool random_data; struct config_range data_size_range; config_weight_list data_size_list; + const char *data_size_pattern; struct config_range expiry_range; const char *data_import; int data_verify; @@ -59,6 +62,8 @@ struct benchmark_config { const char *key_prefix; unsigned int key_minimum; unsigned int key_maximum; + double key_stddev; + double key_median; const char *key_pattern; unsigned int reconnect_interval; int multi_key_get; diff --git a/obj_gen.cpp b/obj_gen.cpp index e7a081eb..8a59f30f 100644 --- a/obj_gen.cpp +++ b/obj_gen.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #ifdef HAVE_ASSERT_H #include @@ -33,8 +34,84 @@ #include "obj_gen.h" #include "memtier_benchmark.h" +random_generator::random_generator() +{ +#ifdef HAVE_RANDOM_R + memset(&m_data_blob, 0, sizeof(m_data_blob)); + memset(m_state_array, 0, sizeof(m_state_array)); + + int ret = initstate_r(1, m_state_array, sizeof(m_state_array), &m_data_blob); + assert(ret == 0); +#elif (defined HAVE_DRAND48) + memset(&m_data_blob, 0, sizeof(m_data_blob)); +#endif +} + +unsigned int random_generator::get_random() +{ + int rn; +#ifdef HAVE_RANDOM_R + int ret = random_r(&m_data_blob, &rn);//max is RAND_MAX + assert(ret == 0); +#elif (defined HAVE_DRAND48) + rn = nrand48(m_data_blob); // max is 1<<31 +#else + #error no random function +#endif + return rn; +} + +unsigned int random_generator::get_random_max() +{ +#ifdef HAVE_RANDOM_R + return RAND_MAX; +#elif (defined HAVE_DRAND48) + return 1<<31; +#else + #error no random function +#endif +} + +//returns a value surrounding 0 +double gaussian_noise::gaussian_distribution(const double &stddev) +{ + // Box–Muller transform (Marsaglia polar method) + if (m_hasSpare) { + m_hasSpare = false; + return stddev * m_spare; + } + + m_hasSpare = true; + double u, v, s; + do { + u = (get_random() / ((double) get_random_max())) * 2 - 1; + v = (get_random() / ((double) get_random_max())) * 2 - 1; + s = u * u + v * v; + } while(s >= 1 || s == 0); + + s = sqrt(-2.0 * log(s) / s); + m_spare = v * s; + return stddev * u * s; +} + +int gaussian_noise::gaussian_distribution_range(double stddev, double median, int min, int max) +{ + int len = max-min; + double val; + if (median==0) + median = len/2.0+min + 0.5; + if (stddev==0) + stddev = len/6.0; + assert(median>min && medianmax+1); + return val; +} + object_generator::object_generator() : m_data_size_type(data_size_unknown), + m_data_size_pattern(NULL), m_random_data(false), m_expiry_min(0), m_expiry_max(0), @@ -44,7 +121,6 @@ object_generator::object_generator() : m_value_buffer(NULL), m_random_fd(-1) { - random_init(); for (int i = 0; i < OBJECT_GENERATOR_KEY_ITERATORS; i++) m_next_key[i] = 0; @@ -54,12 +130,14 @@ object_generator::object_generator() : object_generator::object_generator(const object_generator& copy) : m_data_size_type(copy.m_data_size_type), m_data_size(copy.m_data_size), + m_data_size_pattern(copy.m_data_size_pattern), m_random_data(copy.m_random_data), m_expiry_min(copy.m_expiry_min), m_expiry_max(copy.m_expiry_max), m_key_prefix(copy.m_key_prefix), m_key_min(copy.m_key_min), m_key_max(copy.m_key_max), + m_key_stddev(copy.m_key_stddev), m_value_buffer(NULL), m_random_fd(-1) @@ -69,7 +147,6 @@ object_generator::object_generator(const object_generator& copy) : m_data_size.size_list = new config_weight_list(*m_data_size.size_list); } - random_init(); alloc_value_buffer(); for (int i = 0; i < OBJECT_GENERATOR_KEY_ITERATORS; i++) m_next_key[i] = 0; @@ -181,6 +258,11 @@ void object_generator::set_data_size_list(config_weight_list* size_list) alloc_value_buffer(); } +void object_generator::set_data_size_pattern(const char* pattern) +{ + m_data_size_pattern = pattern; +} + void object_generator::set_expiry_range(unsigned int expiry_min, unsigned int expiry_max) { m_expiry_min = expiry_min; @@ -198,69 +280,62 @@ void object_generator::set_key_range(unsigned int key_min, unsigned int key_max) m_key_max = key_max; } -void object_generator::random_init(void) +void object_generator::set_key_distribution(double key_stddev, double key_median) { - memset(&m_random_data_blob, 0, sizeof(m_random_data_blob)); -#ifdef HAVE_RANDOM_R - memset(m_random_state_array, 0, sizeof(m_random_state_array)); - - int ret = initstate_r(1, m_random_state_array, sizeof(m_random_state_array), &m_random_data_blob); - assert(ret == 0); -#endif + m_key_stddev = key_stddev; + m_key_median = key_median; } // return a random number between r_min and r_max unsigned int object_generator::random_range(unsigned int r_min, unsigned int r_max) { - int rn; - -#ifdef HAVE_RANDOM_R - int ret = random_r(&m_random_data_blob, &rn); - assert(ret == 0); -#else -#ifdef HAVE_DRAND48 - rn = nrand48(m_random_data_blob); -#endif -#endif - + int rn = m_random.get_random(); return ((unsigned int) rn % (r_max - r_min + 1)) + r_min; } -unsigned int object_generator::get_key_index(unsigned int iter) +// return a random number between r_min and r_max using normal distribution according to r_stddev +unsigned int object_generator::normal_distribution(unsigned int r_min, unsigned int r_max, double r_stddev, double r_median) { - assert(iter <= OBJECT_GENERATOR_KEY_ITERATORS); + return m_random.gaussian_distribution_range(r_stddev, r_median, r_min, r_max); +} + +unsigned int object_generator::get_key_index(int iter) +{ + assert(iter < OBJECT_GENERATOR_KEY_ITERATORS && iter >= OBJECT_GENERATOR_KEY_GAUSSIAN); unsigned int k; - if (!iter) { + if (iter==OBJECT_GENERATOR_KEY_RANDOM) { k = random_range(m_key_min, m_key_max); + } else if(iter==OBJECT_GENERATOR_KEY_GAUSSIAN) { + k = normal_distribution(m_key_min, m_key_max, m_key_stddev, m_key_median); } else { - if (m_next_key[iter-1] < m_key_min) - m_next_key[iter-1] = m_key_min; - k = m_next_key[iter-1]; + if (m_next_key[iter] < m_key_min) + m_next_key[iter] = m_key_min; + k = m_next_key[iter]; - m_next_key[iter-1]++; - if (m_next_key[iter-1] > m_key_max) - m_next_key[iter-1] = m_key_min; + m_next_key[iter]++; + if (m_next_key[iter] > m_key_max) + m_next_key[iter] = m_key_min; } return k; } -const char* object_generator::get_key(unsigned int iter, unsigned int *len) +const char* object_generator::get_key(int iter, unsigned int *len) { - unsigned int k = get_key_index(iter); unsigned int l; + m_key_index = get_key_index(iter); // format key l = snprintf(m_key_buffer, sizeof(m_key_buffer)-1, - "%s%u", m_key_prefix, k); + "%s%u", m_key_prefix, m_key_index); if (len != NULL) *len = l; return m_key_buffer; } -data_object* object_generator::get_object(unsigned int iter) +data_object* object_generator::get_object(int iter) { // compute key (void) get_key(iter, NULL); @@ -270,8 +345,13 @@ data_object* object_generator::get_object(unsigned int iter) if (m_data_size_type == data_size_fixed) { new_size = m_data_size.size_fixed; } else if (m_data_size_type == data_size_range) { - new_size = random_range(m_data_size.size_range.size_min > 0 ? m_data_size.size_range.size_min : 1, - m_data_size.size_range.size_max); + if (m_data_size_pattern && *m_data_size_pattern=='S') { + float a = (m_key_index-m_key_min)/(float)(m_key_max-m_key_min); + new_size = (m_data_size.size_range.size_max-m_data_size.size_range.size_min)*a + m_data_size.size_range.size_min; + } else { + new_size = random_range(m_data_size.size_range.size_min > 0 ? m_data_size.size_range.size_min : 1, + m_data_size.size_range.size_max); + } } else if (m_data_size_type == data_size_weighted) { new_size = m_data_size.size_list->get_next_size(); } else { @@ -456,7 +536,7 @@ import_object_generator* import_object_generator::clone(void) return new import_object_generator(*this); } -const char* import_object_generator::get_key(unsigned int iter, unsigned int *len) +const char* import_object_generator::get_key(int iter, unsigned int *len) { if (m_keys == NULL) { return object_generator::get_key(iter, len); @@ -466,7 +546,7 @@ const char* import_object_generator::get_key(unsigned int iter, unsigned int *le } } -data_object* import_object_generator::get_object(unsigned int iter) +data_object* import_object_generator::get_object(int iter) { memcache_item *i = m_reader.read_item(); diff --git a/obj_gen.h b/obj_gen.h index 57025e4d..e297e4d0 100644 --- a/obj_gen.h +++ b/obj_gen.h @@ -25,6 +25,30 @@ struct random_data; struct config_weight_list; +class random_generator { +public: + random_generator(); + unsigned int get_random(); + unsigned int get_random_max(); +private: +#ifdef HAVE_RANDOM_R + struct random_data m_data_blob; + char m_state_array[512]; +#elif (defined HAVE_DRAND48) + unsigned short m_data_blob[3]; +#endif +}; + +class gaussian_noise: public random_generator { +public: + gaussian_noise() { m_hasSpare = false; } + int gaussian_distribution_range(double stddev, double median, int min, int max); +private: + double gaussian_distribution(const double &stddev); + bool m_hasSpare; + double m_spare; +}; + class data_object { protected: const char *m_key; @@ -45,7 +69,11 @@ class data_object { unsigned int get_expiry(void); }; -#define OBJECT_GENERATOR_KEY_ITERATORS 2 +#define OBJECT_GENERATOR_KEY_ITERATORS 2 /* number of iterators */ +#define OBJECT_GENERATOR_KEY_SET_ITER 1 +#define OBJECT_GENERATOR_KEY_GET_ITER 0 +#define OBJECT_GENERATOR_KEY_RANDOM -1 +#define OBJECT_GENERATOR_KEY_GAUSSIAN -2 class object_generator { public: @@ -60,33 +88,30 @@ class object_generator { } size_range; config_weight_list* size_list; } m_data_size; + const char *m_data_size_pattern; bool m_random_data; unsigned int m_expiry_min; unsigned int m_expiry_max; const char *m_key_prefix; unsigned int m_key_min; unsigned int m_key_max; + double m_key_stddev; + double m_key_median; data_object m_object; unsigned int m_next_key[OBJECT_GENERATOR_KEY_ITERATORS]; + unsigned int m_key_index; char m_key_buffer[250]; char *m_value_buffer; int m_random_fd; - -#ifdef HAVE_RANDOM_R - struct random_data m_random_data_blob; - char m_random_state_array[512]; -#else -#ifdef HAVE_DRAND48 - unsigned short m_random_data_blob[3]; -#endif -#endif + gaussian_noise m_random; void alloc_value_buffer(void); void random_init(void); unsigned int random_range(unsigned int r_min, unsigned int r_max); - unsigned int get_key_index(unsigned int iter); + unsigned int normal_distribution(unsigned int r_min, unsigned int r_max, double r_stddev, double r_median); + unsigned int get_key_index(int iter); public: object_generator(); object_generator(const object_generator& copy); @@ -97,12 +122,14 @@ class object_generator { void set_data_size_fixed(unsigned int size); void set_data_size_range(unsigned int size_min, unsigned int size_max); void set_data_size_list(config_weight_list* data_size_list); + void set_data_size_pattern(const char* pattern); void set_expiry_range(unsigned int expiry_min, unsigned int expiry_max); void set_key_prefix(const char *key_prefix); void set_key_range(unsigned int key_min, unsigned int key_max); + void set_key_distribution(double key_stddev, double key_median); - virtual const char* get_key(unsigned int iter, unsigned int *len); - virtual data_object* get_object(unsigned int iter); + virtual const char* get_key(int iter, unsigned int *len); + virtual data_object* get_object(int iter); }; class imported_keylist; @@ -138,8 +165,8 @@ class import_object_generator : public object_generator { virtual ~import_object_generator(); virtual import_object_generator* clone(void); - virtual const char* get_key(unsigned int iter, unsigned int *len); - virtual data_object* get_object(unsigned int iter); + virtual const char* get_key(int iter, unsigned int *len); + virtual data_object* get_object(int iter); bool open_file(void); }; diff --git a/protocol.cpp b/protocol.cpp index 33f84e1a..f6e6c23e 100644 --- a/protocol.cpp +++ b/protocol.cpp @@ -152,8 +152,8 @@ class redis_protocol : public abstract_protocol { virtual redis_protocol* clone(void) { return new redis_protocol(); } virtual int select_db(int db); virtual int authenticate(const char *credentials); - virtual int write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry); - virtual int write_command_get(const char *key, int key_len); + virtual int write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry, unsigned int offset); + virtual int write_command_get(const char *key, int key_len, unsigned int offset); virtual int write_command_multi_get(const keylist *keylist); virtual int parse_response(void); }; @@ -189,7 +189,7 @@ int redis_protocol::authenticate(const char *credentials) return size; } -int redis_protocol::write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry) +int redis_protocol::write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry, unsigned int offset) { assert(key != NULL); assert(key_len > 0); @@ -197,7 +197,7 @@ int redis_protocol::write_command_set(const char *key, int key_len, const char * assert(value_len > 0); int size = 0; - if (!expiry) { + if (!expiry && !offset) { size = evbuffer_add_printf(m_write_buf, "*3\r\n" "$3\r\n" @@ -208,6 +208,22 @@ int redis_protocol::write_command_set(const char *key, int key_len, const char * size += evbuffer_add_printf(m_write_buf, "\r\n" "$%u\r\n", value_len); + } else if(offset) { + char offset_str[30]; + snprintf(offset_str, sizeof(offset_str)-1, "%u", offset); + + size = evbuffer_add_printf(m_write_buf, + "*4\r\n" + "$8\r\n" + "SETRANGE\r\n" + "$%u\r\n", key_len); + evbuffer_add(m_write_buf, key, key_len); + size += key_len; + size += evbuffer_add_printf(m_write_buf, + "\r\n" + "$%u\r\n" + "%s\r\n" + "$%u\r\n", (unsigned int) strlen(offset_str), offset_str, value_len); } else { char expiry_str[30]; snprintf(expiry_str, sizeof(expiry_str)-1, "%u", expiry); @@ -238,20 +254,39 @@ int redis_protocol::write_command_multi_get(const keylist *keylist) assert(0); } -int redis_protocol::write_command_get(const char *key, int key_len) +int redis_protocol::write_command_get(const char *key, int key_len, unsigned int offset) { assert(key != NULL); assert(key_len > 0); int size = 0; - size = evbuffer_add_printf(m_write_buf, - "*2\r\n" - "$3\r\n" - "GET\r\n" - "$%u\r\n", key_len); - evbuffer_add(m_write_buf, key, key_len); - evbuffer_add(m_write_buf, "\r\n", 2); - size += key_len + 2; + if (!offset) { + size = evbuffer_add_printf(m_write_buf, + "*2\r\n" + "$3\r\n" + "GET\r\n" + "$%u\r\n", key_len); + evbuffer_add(m_write_buf, key, key_len); + evbuffer_add(m_write_buf, "\r\n", 2); + size += key_len + 2; + } else { + char offset_str[30]; + snprintf(offset_str, sizeof(offset_str)-1, "%u", offset); + + size = evbuffer_add_printf(m_write_buf, + "*4\r\n" + "$8\r\n" + "GETRANGE\r\n" + "$%u\r\n", key_len); + evbuffer_add(m_write_buf, key, key_len); + size += key_len; + size += evbuffer_add_printf(m_write_buf, + "\r\n" + "$%u\r\n" + "%s\r\n" + "$2\r\n" + "-1\r\n", (unsigned int) strlen(offset_str), offset_str); + } return size; } @@ -352,8 +387,8 @@ class memcache_text_protocol : public abstract_protocol { virtual memcache_text_protocol* clone(void) { return new memcache_text_protocol(); } virtual int select_db(int db); virtual int authenticate(const char *credentials); - virtual int write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry); - virtual int write_command_get(const char *key, int key_len); + virtual int write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry, unsigned int offset); + virtual int write_command_get(const char *key, int key_len, unsigned int offset); virtual int write_command_multi_get(const keylist *keylist); virtual int parse_response(void); }; @@ -368,7 +403,7 @@ int memcache_text_protocol::authenticate(const char *credentials) assert(0); } -int memcache_text_protocol::write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry) +int memcache_text_protocol::write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry, unsigned int offset) { assert(key != NULL); assert(key_len > 0); @@ -385,7 +420,7 @@ int memcache_text_protocol::write_command_set(const char *key, int key_len, cons return size; } -int memcache_text_protocol::write_command_get(const char *key, int key_len) +int memcache_text_protocol::write_command_get(const char *key, int key_len, unsigned int offset) { assert(key != NULL); assert(key_len > 0); @@ -537,8 +572,8 @@ class memcache_binary_protocol : public abstract_protocol { virtual memcache_binary_protocol* clone(void) { return new memcache_binary_protocol(); } virtual int select_db(int db); virtual int authenticate(const char *credentials); - virtual int write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry); - virtual int write_command_get(const char *key, int key_len); + virtual int write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry, unsigned int offset); + virtual int write_command_get(const char *key, int key_len, unsigned int offset); virtual int write_command_multi_get(const keylist *keylist); virtual int parse_response(void); }; @@ -585,7 +620,7 @@ int memcache_binary_protocol::authenticate(const char *credentials) return sizeof(req) + user_len + passwd_len + 2 + sizeof(mechanism) - 1; } -int memcache_binary_protocol::write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry) +int memcache_binary_protocol::write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry, unsigned int offset) { assert(key != NULL); assert(key_len > 0); @@ -610,7 +645,7 @@ int memcache_binary_protocol::write_command_set(const char *key, int key_len, co return sizeof(req) + key_len + value_len; } -int memcache_binary_protocol::write_command_get(const char *key, int key_len) +int memcache_binary_protocol::write_command_get(const char *key, int key_len, unsigned int offset) { assert(key != NULL); assert(key_len > 0); diff --git a/protocol.h b/protocol.h index 4aef0ce1..2de923f5 100644 --- a/protocol.h +++ b/protocol.h @@ -94,8 +94,8 @@ class abstract_protocol { virtual int select_db(int db) = 0; virtual int authenticate(const char *credentials) = 0; - virtual int write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry) = 0; - virtual int write_command_get(const char *key, int key_len) = 0; + virtual int write_command_set(const char *key, int key_len, const char *value, int value_len, int expiry, unsigned int offset) = 0; + virtual int write_command_get(const char *key, int key_len, unsigned int offset) = 0; virtual int write_command_multi_get(const keylist *keylist) = 0; virtual int parse_response() = 0;