Skip to content

Commit 01a7657

Browse files
authored
Add --warmup and --duration parameters to valkey-benchmark (#2581)
It's handy to be able to automatically do a warmup and/or test by duration rather than request count. 🙂 I changed the real-time output a bit - not sure if that's wanted or not. (Like, would it break people's weird scripts? It'll break my weird scripts, but I know the price of writing weird fragile scripts.) ``` Prepended "Warming up " when in warmup phase: Warming up SET: rps=69211.2 (overall: 69747.5) avg_msec=0.425 (overall: 0.393) 3.8 seconds ^^^^^^^^^^ Appended running request counter when based on -n: SET: rps=70892.0 (overall: 69878.1) avg_msec=0.385 (overall: 0.398) 612482 requests ^^^^^^^^^^^^^^^ Appended running second counter when in warmup or based on --duration: SET: rps=61508.0 (overall: 61764.2) avg_msec=0.430 (overall: 0.426) 4.8 seconds ^^^^^^^^^^^ ``` To be clear, the report at the end remains unchanged. --------- Signed-off-by: Rain Valentine <[email protected]> Signed-off-by: Viktor Söderqvist <[email protected]>
1 parent b835463 commit 01a7657

File tree

2 files changed

+190
-25
lines changed

2 files changed

+190
-25
lines changed

src/valkey-benchmark.c

Lines changed: 94 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ static struct config {
102102
int numclients;
103103
_Atomic int liveclients;
104104
int requests;
105+
int duration;
106+
int warmup_duration;
107+
_Atomic int current_warmup_duration;
105108
_Atomic int requests_issued;
106109
_Atomic int requests_finished;
107110
_Atomic int previous_requests_finished;
@@ -252,6 +255,21 @@ static long long nstime(void) {
252255
return (long long)ts.tv_sec * 1000000000LL + ts.tv_nsec;
253256
}
254257

258+
static bool isBenchmarkFinished(int request_count) {
259+
/* don't end in warmup period */
260+
int warmup_duration = atomic_load_explicit(&config.current_warmup_duration, memory_order_relaxed);
261+
if (warmup_duration > 0) return false;
262+
263+
if (config.duration > 0) {
264+
/* end after the specified duration */
265+
if ((mstime() - config.start) >= (config.duration * 1000LL)) return true;
266+
} else {
267+
/* end after the specified number of requests */
268+
if (request_count >= config.requests) return true;
269+
}
270+
return false;
271+
}
272+
255273
static uint64_t dictSdsHash(const void *key) {
256274
return dictGenHashFunction((unsigned char *)key, sdslen((char *)key));
257275
}
@@ -503,7 +521,7 @@ static void freeClient(client c) {
503521
aeDeleteFileEvent(el, c->context->fd, AE_READABLE);
504522
if (c->thread_id >= 0) {
505523
int requests_finished = atomic_load_explicit(&config.requests_finished, memory_order_relaxed);
506-
if (requests_finished >= config.requests) {
524+
if (isBenchmarkFinished(requests_finished)) {
507525
aeStop(el);
508526
}
509527
}
@@ -627,7 +645,7 @@ static long long acquireTokenOrWait(int tokens) {
627645

628646
static void clientDone(client c) {
629647
int requests_finished = atomic_load_explicit(&config.requests_finished, memory_order_relaxed);
630-
if (requests_finished >= config.requests) {
648+
if (isBenchmarkFinished(requests_finished)) {
631649
freeClient(c);
632650
if (!config.num_threads && config.el) aeStop(config.el);
633651
return;
@@ -718,7 +736,7 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
718736
continue;
719737
}
720738
int requests_finished = atomic_fetch_add_explicit(&config.requests_finished, 1, memory_order_relaxed);
721-
if (requests_finished < config.requests) {
739+
if (!isBenchmarkFinished(requests_finished)) {
722740
if (config.num_threads == 0) {
723741
hdr_record_value(config.latency_histogram, // Histogram to record to
724742
(long)c->latency <= CONFIG_LATENCY_HISTOGRAM_MAX_VALUE
@@ -836,7 +854,7 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
836854
int requests_issued = atomic_fetch_add_explicit(&config.requests_issued,
837855
config.pipeline * c->seqlen,
838856
memory_order_relaxed);
839-
if (requests_issued >= config.requests) {
857+
if (isBenchmarkFinished(requests_issued)) {
840858
return;
841859
}
842860

@@ -1237,6 +1255,7 @@ static void benchmarkSequence(const char *title, char *cmd, int len, int seqlen)
12371255
config.requests_finished = 0;
12381256
config.previous_requests_finished = 0;
12391257
config.last_printed_bytes = 0;
1258+
config.current_warmup_duration = config.warmup_duration;
12401259
hdr_init(CONFIG_LATENCY_HISTOGRAM_MIN_VALUE, // Minimum value
12411260
CONFIG_LATENCY_HISTOGRAM_MAX_VALUE, // Maximum value
12421261
config.precision, // Number of significant figures
@@ -1619,7 +1638,22 @@ int parseOptions(int argc, char **argv) {
16191638
exit(0);
16201639
} else if (!strcmp(argv[i], "-n")) {
16211640
if (lastarg) goto invalid;
1641+
if (config.duration > 0) {
1642+
fprintf(stderr, "Options -n and --duration are mutually exclusive.\n");
1643+
exit(1);
1644+
}
16221645
config.requests = atoi(argv[++i]);
1646+
} else if (!strcmp(argv[i], "--duration")) {
1647+
if (lastarg) goto invalid;
1648+
if (config.requests > 0) {
1649+
fprintf(stderr, "Options -n and --duration are mutually exclusive.\n");
1650+
exit(1);
1651+
}
1652+
config.duration = atoi(argv[++i]);
1653+
} else if (!strcmp(argv[i], "--warmup")) {
1654+
if (lastarg) goto invalid;
1655+
config.warmup_duration = atoi(argv[++i]);
1656+
16231657
} else if (!strcmp(argv[i], "-k")) {
16241658
if (lastarg) goto invalid;
16251659
config.keepalive = atoi(argv[++i]);
@@ -1817,13 +1851,15 @@ int parseOptions(int argc, char **argv) {
18171851
" --cert <file> Client certificate to authenticate with.\n"
18181852
" --key <file> Private key file to authenticate with.\n"
18191853
" --tls-ciphers <list> Sets the list of preferred ciphers (TLSv1.2 and below)\n"
1820-
" in order of preference from highest to lowest separated by colon (\":\").\n"
1821-
" See the ciphers(1ssl) manpage for more information about the syntax of this string.\n"
1854+
" in order of preference from highest to lowest separated by\n"
1855+
" colon (\":\"). See the ciphers(1ssl) manpage for more\n"
1856+
" information about the syntax of this string.\n"
18221857
#ifdef TLS1_3_VERSION
18231858
" --tls-ciphersuites <list> Sets the list of preferred ciphersuites (TLSv1.3)\n"
1824-
" in order of preference from highest to lowest separated by colon (\":\").\n"
1825-
" See the ciphers(1ssl) manpage for more information about the syntax of this string,\n"
1826-
" and specifically for TLSv1.3 ciphersuites.\n"
1859+
" in order of preference from highest to lowest separated by\n"
1860+
" colon (\":\"). See the ciphers(1ssl) manpage for more\n"
1861+
" information about the syntax of this string, and\n"
1862+
" specifically for TLSv1.3 ciphersuites.\n"
18271863
#endif
18281864
#endif
18291865
"";
@@ -1870,6 +1906,11 @@ int parseOptions(int argc, char **argv) {
18701906
" Note: If --cluster is used then number of clients has to be\n"
18711907
" the same or higher than the number of nodes.\n"
18721908
" -n <requests> Total number of requests (default 100000)\n"
1909+
" --duration <seconds>\n"
1910+
" Run benchmark for specified number of seconds\n"
1911+
" (mutually exclusive with -n)\n"
1912+
" --warmup <seconds> Run benchmark for specified warmup period before\n"
1913+
" recording data\n"
18731914
" -d <size> Data size of SET/GET value in bytes (default 3)\n"
18741915
" --dbnum <db> SELECT the specified db number (default 0)\n"
18751916
" -3 Start session in RESP3 protocol mode.\n"
@@ -1901,9 +1942,9 @@ int parseOptions(int argc, char **argv) {
19011942
" use the same key.\n"
19021943
" --sequential Modifies the -r argument to replace the string __rand_int__\n"
19031944
" with 12 digit numbers sequentially instead of randomly.\n"
1904-
" __rand_1st__ through __rand_9th__ are available with independent\n"
1905-
" counters. Used to create expected number of elements with multiple\n"
1906-
" replacements.\n"
1945+
" __rand_1st__ through __rand_9th__ are available with\n"
1946+
" independent counters. Used to create expected number of\n"
1947+
" elements with multiple replacements.\n"
19071948
" example: ZADD myzset __rand_int__ element:__rand_1st__\n"
19081949
" -P <numreq> Pipeline <numreq> requests. That is, send multiple requests\n"
19091950
" before waiting for the replies. Default 1 (no pipeline).\n"
@@ -1912,7 +1953,8 @@ int parseOptions(int argc, char **argv) {
19121953
" the number of times the command sequence is sent in each\n"
19131954
" pipeline.\n",
19141955
" -q Quiet. Just show query/sec values\n"
1915-
" --precision Number of decimal places to display in latency output (default 0)\n"
1956+
" --precision Number of decimal places to display in latency output\n"
1957+
" (default 0)\n"
19161958
" --csv Output in CSV format\n"
19171959
" -l Loop. Run the tests forever\n"
19181960
" -t <tests> Only run the comma separated list of tests. The test\n"
@@ -1921,8 +1963,10 @@ int parseOptions(int argc, char **argv) {
19211963
" on the command line.\n"
19221964
" -I Idle mode. Just open N idle connections and wait.\n"
19231965
" -x Read last argument from STDIN.\n"
1924-
" --rps <requests> Limit the total number of requests per second. Default 0 (no limit)\n"
1925-
" --seed <num> Set the seed for random number generator. Default seed is based on time.\n"
1966+
" --rps <requests> Limit the total number of requests per second.\n"
1967+
" Default 0 (no limit)\n"
1968+
" --seed <num> Set the seed for random number generator.\n"
1969+
" Default seed is based on time.\n"
19261970
" --num-functions <num>\n"
19271971
" Sets the number of functions present in the Lua lib that is\n"
19281972
" loaded when running the 'function_load' test. (default 10).\n"
@@ -1957,16 +2001,28 @@ long long showThroughput(struct aeEventLoop *eventLoop, long long id, void *clie
19572001
UNUSED(eventLoop);
19582002
UNUSED(id);
19592003
benchmarkThread *thread = (benchmarkThread *)clientData;
1960-
int liveclients = atomic_load_explicit(&config.liveclients, memory_order_relaxed);
19612004
int requests_finished = atomic_load_explicit(&config.requests_finished, memory_order_relaxed);
19622005
int previous_requests_finished = atomic_load_explicit(&config.previous_requests_finished, memory_order_relaxed);
19632006
long long current_tick = mstime();
19642007

1965-
if (liveclients == 0 && requests_finished != config.requests) {
2008+
int liveclients = atomic_load_explicit(&config.liveclients, memory_order_relaxed);
2009+
if (liveclients == 0 && !isBenchmarkFinished(requests_finished)) {
19662010
fprintf(stderr, "All clients disconnected... aborting.\n");
19672011
exit(1);
19682012
}
1969-
if (config.num_threads && requests_finished >= config.requests) {
2013+
int warmup_duration = atomic_load_explicit(&config.current_warmup_duration, memory_order_relaxed);
2014+
if (warmup_duration > 0) {
2015+
if ((current_tick - config.start) >= (warmup_duration * 1000LL)) {
2016+
/* exit the warmup period, clear all stats */
2017+
atomic_store_explicit(&config.current_warmup_duration, 0, memory_order_relaxed);
2018+
2019+
config.start = current_tick;
2020+
atomic_store_explicit(&config.requests_finished, 0, memory_order_relaxed);
2021+
atomic_store_explicit(&config.requests_issued, 0, memory_order_relaxed);
2022+
atomic_store_explicit(&config.previous_requests_finished, 0, memory_order_relaxed);
2023+
hdr_reset(config.latency_histogram);
2024+
}
2025+
} else if (config.num_threads && isBenchmarkFinished(requests_finished)) {
19702026
aeStop(eventLoop);
19712027
return AE_NOMORE;
19722028
}
@@ -1991,11 +2047,21 @@ long long showThroughput(struct aeEventLoop *eventLoop, long long id, void *clie
19912047

19922048
config.previous_tick = current_tick;
19932049
atomic_store_explicit(&config.previous_requests_finished, requests_finished, memory_order_relaxed);
2050+
19942051
printf("%*s\r", config.last_printed_bytes, " "); /* ensure there is a clean line */
1995-
int printed_bytes =
1996-
printf("%s: rps=%.1f (overall: %.1f) avg_msec=%.3f (overall: %.3f)\r", config.title, instantaneous_rps, rps,
2052+
config.last_printed_bytes = 0;
2053+
if (warmup_duration > 0) {
2054+
config.last_printed_bytes += printf("Warming up ");
2055+
}
2056+
config.last_printed_bytes +=
2057+
printf("%s: rps=%.1f (overall: %.1f) avg_msec=%.3f (overall: %.3f)", config.title, instantaneous_rps, rps,
19972058
hdr_mean(config.current_sec_latency_histogram) / 1000.0f, hdr_mean(config.latency_histogram) / 1000.0f);
1998-
config.last_printed_bytes = printed_bytes;
2059+
if (warmup_duration > 0 || config.duration > 0) {
2060+
config.last_printed_bytes += printf(" %.1f seconds\r", dt);
2061+
} else {
2062+
config.last_printed_bytes += printf(" %d requests\r", requests_finished);
2063+
}
2064+
19992065
hdr_reset(config.current_sec_latency_histogram);
20002066
fflush(stdout);
20012067
return SHOW_THROUGHPUT_INTERVAL;
@@ -2067,7 +2133,10 @@ int main(int argc, char **argv) {
20672133
memset(&config.sslconfig, 0, sizeof(config.sslconfig));
20682134
config.ct = VALKEY_CONN_TCP;
20692135
config.numclients = 50;
2070-
config.requests = 100000;
2136+
config.requests = -1;
2137+
config.duration = -1;
2138+
config.warmup_duration = -1;
2139+
config.current_warmup_duration = -1;
20712140
config.liveclients = 0;
20722141
config.el = aeCreateEventLoop(1024 * 10);
20732142
aeCreateTimeEvent(config.el, 1, showThroughput, NULL, NULL);
@@ -2111,6 +2180,9 @@ int main(int argc, char **argv) {
21112180
argc -= i;
21122181
argv += i;
21132182

2183+
/* Set default for requests if not specified */
2184+
if (config.requests < 0) config.requests = 100000;
2185+
21142186
tag = "";
21152187

21162188
#ifdef USE_OPENSSL

tests/integration/valkey-benchmark.tcl

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ proc cmdstat {cmd} {
99
proc common_bench_setup {cmd} {
1010
r config resetstat
1111
r flushall
12-
if {[catch { exec {*}$cmd } error]} {
13-
set first_line [lindex [split $error "\n"] 0]
14-
puts [colorstr red "valkey-benchmark non zero code, the output is: $error"]
12+
if {[catch { exec {*}$cmd } output]} {
13+
set first_line [lindex [split $output "\n"] 0]
14+
puts [colorstr red "valkey-benchmark non zero code, the output is: $output"]
1515
fail "valkey-benchmark non zero code. first line: $first_line"
1616
}
17+
return $output
1718
}
1819

1920
# we use this extra asserts on a simple set,get test for features like uri parsing
@@ -208,6 +209,74 @@ tags {"benchmark network external:skip logreqres:skip"} {
208209
assert_match {50} [r zcard myzset]
209210
}
210211

212+
test {benchmark: warmup and duration are cumulative} {
213+
set start_time [clock clicks -millisec]
214+
set cmd [valkeybenchmark $master_host $master_port "-r 5 --warmup 1 --duration 1 -t set"]
215+
set output [common_bench_setup $cmd]
216+
set end_time [clock clicks -millisec]
217+
218+
# Verify total duration was at least 2 seconds
219+
set elapsed [expr {($end_time - $start_time)/1000.0}]
220+
assert {$elapsed >= 2 && $elapsed <= 2.25}
221+
222+
# Check reported duration
223+
lassign [regexp -inline {(\d+) requests completed in ([\d.]+) seconds} $output] -> requests duration
224+
assert {$duration < 2.0 && $duration >= 1.0}
225+
}
226+
227+
test {benchmark: warmup can be used with request count} {
228+
set start_time [clock clicks -millisec]
229+
set cmd [valkeybenchmark $master_host $master_port "-r 5 --warmup 1 -n 100 -t set"]
230+
set output [common_bench_setup $cmd]
231+
set end_time [clock clicks -millisec]
232+
233+
# Verify total duration was at least 1 seconds
234+
set elapsed [expr {($end_time - $start_time)/1000.0}]
235+
assert {$elapsed >= 1}
236+
237+
# Check reported duration and command count
238+
lassign [regexp -inline {(\d+) requests completed in ([\d.]+) seconds} $output] -> requests duration
239+
assert {$duration < 1.0}
240+
assert {$requests >= 100 && $requests < 150}
241+
}
242+
243+
test {benchmark: -n and --duration are mutually exclusive} {
244+
set cmd [valkeybenchmark $master_host $master_port "-r 5 -n 5 --duration 1 -t set"]
245+
catch { exec {*}$cmd } error
246+
assert_match "*Options -n and --duration are mutually exclusive*" $error
247+
}
248+
249+
test {benchmark: warmup applies to all tests in multi-test run} {
250+
set start_time [clock clicks -millisec]
251+
set cmd [valkeybenchmark $master_host $master_port "-r 5 --warmup 1 -n 50 -t set,get,incr"]
252+
set output [common_bench_setup $cmd]
253+
set end_time [clock clicks -millisec]
254+
255+
# Verify total duration includes warmup for all 3 tests (at least 3 seconds)
256+
set elapsed [expr {($end_time - $start_time)/1000.0}]
257+
assert {$elapsed >= 3}
258+
259+
# Verify all tests ran - with warmup, we expect more than 50 calls per command
260+
# since warmup commands are also counted in server stats
261+
assert_match {*calls=*} [cmdstat set]
262+
assert_match {*calls=*} [cmdstat get]
263+
assert_match {*calls=*} [cmdstat incr]
264+
265+
# Verify that each command was called more than the base 50 requests
266+
# due to warmup period adding extra requests
267+
set set_calls [regexp -inline {calls=(\d+)} [cmdstat set]]
268+
set get_calls [regexp -inline {calls=(\d+)} [cmdstat get]]
269+
set incr_calls [regexp -inline {calls=(\d+)} [cmdstat incr]]
270+
271+
lassign $set_calls -> set_count
272+
lassign $get_calls -> get_count
273+
lassign $incr_calls -> incr_count
274+
275+
assert {$set_count > 50}
276+
assert {$get_count > 50}
277+
assert {$incr_count > 50}
278+
}
279+
211280
test {benchmark: clients idle mode should return error when reached maxclients limit} {
212281
set cmd [valkeybenchmark $master_host $master_port "-c 10 -I"]
213282
set original_maxclients [lindex [r config get maxclients] 1]
@@ -224,6 +293,30 @@ tags {"benchmark network external:skip logreqres:skip"} {
224293
r get key
225294
} {arg}
226295

296+
test {benchmark: CSV output format} {
297+
set cmd [valkeybenchmark $master_host $master_port "-c 5 -n 10 -t set,get --csv"]
298+
set output [common_bench_setup $cmd]
299+
# CSV output should contain comma-separated values
300+
assert_match "*,*,*,*" $output
301+
# Should not contain the usual formatted output
302+
assert_no_match "*requests per second*" $output
303+
# Should not contain carriage returns or progress indicators
304+
assert_no_match "*\r*" $output
305+
assert_no_match "*rps=*" $output
306+
default_set_get_checks
307+
}
308+
309+
test {benchmark: quiet mode} {
310+
set cmd [valkeybenchmark $master_host $master_port "-c 5 -n 10 -t set,get -q"]
311+
set output [common_bench_setup $cmd]
312+
# Quiet mode should only show query/sec values (case-insensitive)
313+
assert {[string match -nocase "*requests per second*" $output]}
314+
# Should not contain detailed latency information (case-insensitive)
315+
assert {![string match -nocase "*distribution*" $output]}
316+
assert {![string match -nocase "*percentile*" $output]}
317+
default_set_get_checks
318+
}
319+
227320
# tls specific tests
228321
if {$::tls} {
229322
test {benchmark: specific tls-ciphers} {

0 commit comments

Comments
 (0)