@@ -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+
255273static 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
628646static 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
0 commit comments