Skip to content

Commit fdf704b

Browse files
committed
[add] Enable full percentile spectrum analysis on latency
1 parent 84c922a commit fdf704b

17 files changed

+767
-184
lines changed

.gitignore

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ aclocal.m4
22
autom4te.cache
33
config.guess
44
config.h.in
5+
config.h.in~
56
config.h
67
config.sub
78
configure
9+
compile
810
depcomp
911
install-sh
1012
ltmain.sh
@@ -19,6 +21,11 @@ config.log
1921
config.status
2022
libtool
2123
stamp-h1
22-
.idea
23-
.vscode
24+
*.DS_Store
25+
.vscode/*
26+
.idea/*
27+
28+
# memtier outputs
29+
*.hgrm
30+
*.txt
2431
__pycache__

Makefile.am

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ memtier_benchmark_SOURCES = \
4040
obj_gen.cpp obj_gen.h \
4141
item.cpp item.h \
4242
file_io.cpp file_io.h \
43-
config_types.cpp config_types.h
43+
config_types.cpp config_types.h \
44+
deps/hdr_histogram/hdr_histogram_log.c deps/hdr_histogram/hdr_histogram_log.h deps/hdr_histogram/byteorder.h \
45+
deps/hdr_histogram/hdr_histogram.c deps/hdr_histogram/hdr_histogram.h \
46+
deps/hdr_histogram/hdr_time.c deps/hdr_histogram/hdr_time.h deps/hdr_histogram/hdr_encoding.c deps/hdr_histogram/hdr_encoding.h
47+
4448
memtier_benchmark_LDADD = \
4549
$(LIBEVENT_LIBS) \
4650
$(LIBEVENT_OPENSSL_LIBS) \

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,24 @@ Also, the ratio and the key generator is per client (and not connection).
156156
In this case, setting the ratio to 1:1 does not guarantee 100% hits because
157157
the keys spread to different connections/nodes.
158158

159+
### Full latency spectrum analysis
160+
161+
For distributions that are non-normal, such as the latency, many “basic rules” of normally distributed statistics are violated. Instead of computing just the mean, which tries to express the whole distribution in a single result, we can use a sampling of the distribution at intervals -- percentiles, which tell you how many requests actually would experience that delay.
162+
163+
164+
When used for normally distributed data, the samples are usually taken at regular intervals. However, since the data does not obey to a normal distribution it would be very expensive to keep equally spaced intervals of latency records while enabling large value ranges. We can apply algorithms that can calculate a good approximation of percentiles at minimal CPU and memory cost, such as [t-digest](https://github.com/tdunning/t-digest) or [HdrHistogram](https://github.com/HdrHistogram/HdrHistogram_c). On memtier_benchmark we’ve decided to use the HdrHistogram due to its low memory footprint, high precision, zero allocation during the benchmark and constant access time.
165+
166+
167+
By default Memtier will output the 50th, 99th, and 99.9th percentiles. They are the latency thresholds at which 50%, 99%, and 99.9% of commands are faster than that particular presented value.
168+
To output different percentiles you should use the --print-percentiles option followed by the comma separated list of values ( example: `--print-percentiles 90,99,99.9,99.99` ).
169+
170+
#### Saving the full latency spectrum
171+
To save the full latencies you should use the --hdr-file-prefix option followed by the prefix name you wish the filenames to have.
172+
Each distinct command will be saved into two different files - one in .txt (textual format) and another in .hgrm (HistogramLogProcessor format).
173+
The textual format can be hard to analyze solely, but you can use an [online formatter](http://hdrhistogram.github.io/HdrHistogram/plotFiles.html) to generate visual histograms from it. The .hgrm format will be later added as input to Redislabs [mbdirector](https://github.com/redislabs/mbdirector) to enable visualization of time-domain results.
174+
175+
Sample Visual Feel of the full latency spectrum using an [online formatter](http://hdrhistogram.github.io/HdrHistogram/plotFiles.html):
176+
![alt text][sample_visual_histogram]
177+
178+
179+
[sample_visual_histogram]: ./docs/sample_visual_histogram.png "Sample Full Latency Spectrum Histogram"

client.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ bool client::setup_client(benchmark_config *config, abstract_protocol *protocol,
5656
{
5757
m_config = config;
5858
assert(m_config != NULL);
59+
unsigned long long total_num_of_clients = config->clients*config->threads;
5960

6061
// create main connection
6162
shard_connection* conn = new shard_connection(m_connections.size(), this, m_config, m_event_base, protocol);
@@ -74,7 +75,6 @@ bool client::setup_client(benchmark_config *config, abstract_protocol *protocol,
7475
// Parallel key-pattern determined according to the first command
7576
if ((config->arbitrary_commands->is_defined() && config->arbitrary_commands->at(0).key_pattern == 'P') ||
7677
(config->key_pattern[key_pattern_set]=='P')) {
77-
unsigned long long total_num_of_clients = config->clients*config->threads;
7878
unsigned long long client_index = config->next_client_idx % total_num_of_clients;
7979

8080
unsigned long long range = (config->key_maximum - config->key_minimum)/total_num_of_clients + 1;

client.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,17 @@ class client : public connections_manager {
6161
object_generator* m_obj_gen;
6262
run_stats m_stats;
6363

64-
unsigned long long m_reqs_processed; // requests processed (responses received)
65-
unsigned long long m_reqs_generated; // requests generated (wait for responses)
66-
unsigned int m_set_ratio_count; // number of sets counter (overlaps on ratio)
67-
unsigned int m_get_ratio_count; // number of gets counter (overlaps on ratio)
64+
unsigned long long m_reqs_processed; // requests processed (responses received)
65+
unsigned long long m_reqs_generated; // requests generated (wait for responses)
66+
unsigned int m_set_ratio_count; // number of sets counter (overlaps on ratio)
67+
unsigned int m_get_ratio_count; // number of gets counter (overlaps on ratio)
6868
unsigned int m_arbitrary_command_ratio_count; // number of arbitrary commands counter (overlaps on ratio)
69-
unsigned int m_executed_command_index; // current arbitrary command executed
69+
unsigned int m_executed_command_index; // current arbitrary command executed
7070

71-
unsigned long long m_tot_set_ops; // Total number of SET ops
72-
unsigned long long m_tot_wait_ops; // Total number of WAIT ops
71+
unsigned long long m_tot_set_ops; // Total number of SET ops
72+
unsigned long long m_tot_wait_ops; // Total number of WAIT ops
7373

74-
keylist *m_keylist; // used to construct multi commands
74+
keylist *m_keylist; // used to construct multi commands
7575

7676
public:
7777
client(client_group* group);

config_types.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <netdb.h>
3636

3737
#include <string>
38+
#include <iostream>
3839
#include <stdexcept>
3940
#include <climits>
4041
#include <algorithm>
@@ -87,6 +88,34 @@ config_ratio::config_ratio(const char *ratio_str) :
8788
}
8889
}
8990

91+
config_quantiles::config_quantiles(){
92+
93+
}
94+
95+
config_quantiles::config_quantiles(const char *str)
96+
{
97+
assert(str != NULL);
98+
99+
do {
100+
float quantile;
101+
char *p = NULL;
102+
quantile = strtof(str, &p);
103+
if (!p || (*p != ',' && *p != '\0')) {
104+
quantile_list.clear();
105+
return;
106+
}
107+
str = p;
108+
if (*str) str++;
109+
quantile_list.push_back(quantile);
110+
} while (*str);
111+
}
112+
113+
bool config_quantiles::is_defined(void)
114+
{
115+
return quantile_list.size() > 0;
116+
}
117+
118+
90119
config_weight_list::config_weight_list() :
91120
next_size_weight(0)
92121
{

config_types.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ struct config_ratio {
4646
bool is_defined(void) { return (a > 0 || b > 0); }
4747
};
4848

49+
struct config_quantiles {
50+
std::vector<float> quantile_list;
51+
config_quantiles();
52+
config_quantiles(const char *ratio_str);
53+
bool is_defined(void);
54+
inline std::vector<float>::iterator begin() { return quantile_list.begin(); }
55+
inline std::vector<float>::iterator end() { return quantile_list.end(); }
56+
};
57+
4958
struct config_weight_list {
5059
struct weight_item {
5160
unsigned int size;

docs/sample_visual_histogram.png

247 KB
Loading

memtier_benchmark.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,10 @@ static void config_init_defaults(struct benchmark_config *cfg)
275275
}
276276
if (!cfg->requests && !cfg->test_time)
277277
cfg->requests = 10000;
278+
if (!cfg->hdr_prefix)
279+
cfg->hdr_prefix = "";
280+
if (!cfg->print_percentiles.is_defined())
281+
cfg->print_percentiles = config_quantiles("50,99,99.9");
278282
}
279283

280284
static int generate_random_seed()
@@ -363,6 +367,7 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf
363367
o_key_median,
364368
o_show_config,
365369
o_hide_histogram,
370+
o_print_percentiles,
366371
o_distinct_client_seed,
367372
o_randomize,
368373
o_client_stats,
@@ -384,7 +389,8 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf
384389
o_tls_key,
385390
o_tls_cacert,
386391
o_tls_skip_verify,
387-
o_tls_sni
392+
o_tls_sni,
393+
o_hdr_file_prefix
388394
};
389395

390396
static struct option long_options[] = {
@@ -401,11 +407,13 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf
401407
{ "sni", 1, 0, o_tls_sni },
402408
#endif
403409
{ "out-file", 1, 0, 'o' },
410+
{ "hdr-file-prefix", 1, 0, o_hdr_file_prefix },
404411
{ "client-stats", 1, 0, o_client_stats },
405412
{ "run-count", 1, 0, 'x' },
406413
{ "debug", 0, 0, 'D' },
407414
{ "show-config", 0, 0, o_show_config },
408415
{ "hide-histogram", 0, 0, o_hide_histogram },
416+
{ "print-percentiles", 1, 0, o_print_percentiles },
409417
{ "distinct-client-seed", 0, 0, o_distinct_client_seed },
410418
{ "randomize", 0, 0, o_randomize },
411419
{ "requests", 1, 0, 'n' },
@@ -493,6 +501,9 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf
493501
case 'o':
494502
cfg->out_file = optarg;
495503
break;
504+
case o_hdr_file_prefix:
505+
cfg->hdr_prefix = optarg;
506+
break;
496507
case o_client_stats:
497508
cfg->client_stats = optarg;
498509
break;
@@ -513,6 +524,13 @@ static int config_parse_args(int argc, char *argv[], struct benchmark_config *cf
513524
case o_hide_histogram:
514525
cfg->hide_histogram++;
515526
break;
527+
case o_print_percentiles:
528+
cfg->print_percentiles = config_quantiles(optarg);
529+
if (!cfg->print_percentiles.is_defined()) {
530+
fprintf(stderr, "error: quantiles must be expressed as [0.0-100.0],[0.0-100.0](,...) .\n");
531+
return -1;
532+
}
533+
break;
516534
case o_distinct_client_seed:
517535
cfg->distinct_client_seed++;
518536
break;
@@ -858,8 +876,10 @@ void usage() {
858876
" --client-stats=FILE Produce per-client stats file\n"
859877
" --out-file=FILE Name of output file (default: stdout)\n"
860878
" --json-out-file=FILE Name of JSON output file, if not set, will not print to json\n"
879+
" --hdr-file-prefix=FILE Prefix of HDR Latency Histogram output files, if not set, will not save latency histogram files\n"
861880
" --show-config Print detailed configuration before running\n"
862881
" --hide-histogram Don't print detailed latency histogram\n"
882+
" --print-percentiles Specify which percentiles info to print on the results table (by default prints percentiles: 50,99,99.9)\n"
863883
" --cluster-mode Run client in cluster mode\n"
864884
" --help Display this help\n"
865885
" --version Display version information\n"
@@ -1455,6 +1475,7 @@ int main(int argc, char *argv[])
14551475
perror(cfg.out_file);
14561476
}
14571477
} else {
1478+
fprintf(stderr, "Writing results to stdout\n");
14581479
outfile = stdout;
14591480
}
14601481

@@ -1468,6 +1489,10 @@ int main(int argc, char *argv[])
14681489

14691490
run_stats stats = run_benchmark(run_id, &cfg, obj_gen);
14701491
all_stats.push_back(stats);
1492+
stats.save_hdr_full_run( &cfg,run_id );
1493+
stats.save_hdr_get_command( &cfg,run_id );
1494+
stats.save_hdr_set_command( &cfg,run_id );
1495+
stats.save_hdr_arbitrary_commands( &cfg,run_id );
14711496
}
14721497
//
14731498
// Print some run information
@@ -1485,7 +1510,7 @@ int main(int argc, char *argv[])
14851510
jsonhandler->write_obj("Connections per thread","%u",cfg.clients);
14861511
jsonhandler->write_obj(cfg.requests > 0 ? "Requests per client" : "Seconds","%llu",
14871512
cfg.requests > 0 ? cfg.requests : (unsigned long long)cfg.test_time);
1488-
1513+
jsonhandler->write_obj("Format version","%d",2);
14891514
jsonhandler->close_nesting();
14901515
}
14911516

memtier_benchmark.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ struct benchmark_config {
5353
int debug;
5454
int show_config;
5555
int hide_histogram;
56+
config_quantiles print_percentiles;
5657
int distinct_client_seed;
5758
int randomize;
5859
int next_client_idx;
@@ -93,6 +94,7 @@ struct benchmark_config {
9394
const char *json_out_file;
9495
bool cluster_mode;
9596
struct arbitrary_command_list* arbitrary_commands;
97+
const char *hdr_prefix;
9698
#ifdef USE_TLS
9799
bool tls;
98100
const char *tls_cert;

0 commit comments

Comments
 (0)