2020-07-10 09:37:11 -04:00
/* Redis benchmark utility.
2009-03-22 05:30:00 -04:00
*
2012-11-08 12:25:23 -05:00
* Copyright ( c ) 2009 - 2012 , Salvatore Sanfilippo < antirez at gmail dot com >
2009-03-22 05:30:00 -04:00
* 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 .
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission .
*
* 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 OWNER 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 .
*/
2009-05-19 12:39:58 -04:00
# include "fmacros.h"
2020-10-26 02:04:59 -04:00
# include "version.h"
2009-05-19 12:39:58 -04:00
2009-03-22 05:30:00 -04:00
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <unistd.h>
# include <errno.h>
2012-11-30 09:41:09 -05:00
# include <time.h>
2009-03-22 05:30:00 -04:00
# include <sys/time.h>
# include <signal.h>
# include <assert.h>
2019-01-31 13:44:17 -05:00
# include <math.h>
2018-07-31 04:45:00 -04:00
# include <pthread.h>
2009-03-22 05:30:00 -04:00
2020-08-15 16:13:23 -04:00
# include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
2015-07-14 11:33:30 -04:00
# include <sds.h> /* Use hiredis sds. */
2009-03-22 05:30:00 -04:00
# include "ae.h"
2020-10-28 02:00:54 -04:00
# include <hiredis.h>
# ifdef USE_OPENSSL
# include <openssl/ssl.h>
# include <openssl/err.h>
# include <hiredis_ssl.h>
# endif
2009-03-22 05:30:00 -04:00
# include "adlist.h"
2019-02-11 11:57:20 -05:00
# include "dict.h"
2009-03-22 05:30:00 -04:00
# include "zmalloc.h"
2018-07-31 04:45:00 -04:00
# include "atomicvar.h"
2018-09-24 11:25:28 -04:00
# include "crc16_slottable.h"
2020-08-25 14:21:29 -04:00
# include "hdr_histogram.h"
2020-10-28 02:00:54 -04:00
# include "cli_common.h"
2020-12-23 08:52:07 -05:00
# include "mt19937-64.h"
2009-03-22 05:30:00 -04:00
2015-07-27 03:41:48 -04:00
# define UNUSED(V) ((void) V)
2013-08-07 09:58:51 -04:00
# define RANDPTR_INITIAL_SIZE 8
2020-08-25 14:21:29 -04:00
# define DEFAULT_LATENCY_PRECISION 3
# define MAX_LATENCY_PRECISION 4
2019-01-17 11:40:15 -05:00
# define MAX_THREADS 500
2018-09-24 11:25:28 -04:00
# define CLUSTER_SLOTS 16384
2020-08-25 14:21:29 -04:00
# define CONFIG_LATENCY_HISTOGRAM_MIN_VALUE 10L /* >= 10 usecs */
# define CONFIG_LATENCY_HISTOGRAM_MAX_VALUE 3000000L /* <= 30 secs(us precision) */
# define CONFIG_LATENCY_HISTOGRAM_INSTANT_MAX_VALUE 3000000L /* <= 3 secs(us precision) */
2018-07-31 04:45:00 -04:00
# define CLIENT_GET_EVENTLOOP(c) \
( c - > thread_id > = 0 ? config . threads [ c - > thread_id ] - > el : config . el )
struct benchmarkThread ;
2018-09-24 11:25:28 -04:00
struct clusterNode ;
2019-02-05 13:13:50 -05:00
struct redisConfig ;
2009-03-22 05:30:00 -04:00
static struct config {
2011-05-31 20:15:42 -04:00
aeEventLoop * el ;
const char * hostip ;
int hostport ;
const char * hostsocket ;
2020-10-28 02:00:54 -04:00
int tls ;
struct cliSSLconfig sslconfig ;
2009-03-22 05:30:00 -04:00
int numclients ;
Implement redisAtomic to replace _Atomic C11 builtin (#7707)
Redis 6.0 introduces I/O threads, it is so cool and efficient, we use C11
_Atomic to establish inter-thread synchronization without mutex. But the
compiler that must supports C11 _Atomic can compile redis code, that brings a
lot of inconvenience since some common platforms can't support by default such
as CentOS7, so we want to implement redis atomic type to make it more portable.
We have implemented our atomic variable for redis that only has 'relaxed'
operations in src/atomicvar.h, so we implement some operations with
'sequentially-consistent', just like the default behavior of C11 _Atomic that
can establish inter-thread synchronization. And we replace all uses of C11
_Atomic with redis atomic variable.
Our implementation of redis atomic variable uses C11 _Atomic, __atomic or
__sync macros if available, it supports most common platforms, and we will
detect automatically which feature we use. In Makefile we use a dummy file to
detect if the compiler supports C11 _Atomic. Now for gcc, we can compile redis
code theoretically if your gcc version is not less than 4.1.2(starts to support
__sync_xxx operations). Otherwise, we remove use mutex fallback to implement
redis atomic variable for performance and test. You will get compiling errors
if your compiler doesn't support all features of above.
For cover redis atomic variable tests, we add other CI jobs that build redis on
CentOS6 and CentOS7 and workflow daily jobs that run the tests on them.
For them, we just install gcc by default in order to cover different compiler
versions, gcc is 4.4.7 by default installation on CentOS6 and 4.8.5 on CentOS7.
We restore the feature that we can test redis with Helgrind to find data race
errors. But you need install Valgrind in the default path configuration firstly
before running your tests, since we use macros in helgrind.h to tell Helgrind
inter-thread happens-before relationship explicitly for avoiding false positives.
Please open an issue on github if you find data race errors relate to this commit.
Unrelated:
- Fix redefinition of typedef 'RedisModuleUserChangedFunc'
For some old version compilers, they will report errors or warnings, if we
re-define function type.
2020-09-17 09:01:45 -04:00
redisAtomic int liveclients ;
2011-08-17 11:06:19 -04:00
int requests ;
Implement redisAtomic to replace _Atomic C11 builtin (#7707)
Redis 6.0 introduces I/O threads, it is so cool and efficient, we use C11
_Atomic to establish inter-thread synchronization without mutex. But the
compiler that must supports C11 _Atomic can compile redis code, that brings a
lot of inconvenience since some common platforms can't support by default such
as CentOS7, so we want to implement redis atomic type to make it more portable.
We have implemented our atomic variable for redis that only has 'relaxed'
operations in src/atomicvar.h, so we implement some operations with
'sequentially-consistent', just like the default behavior of C11 _Atomic that
can establish inter-thread synchronization. And we replace all uses of C11
_Atomic with redis atomic variable.
Our implementation of redis atomic variable uses C11 _Atomic, __atomic or
__sync macros if available, it supports most common platforms, and we will
detect automatically which feature we use. In Makefile we use a dummy file to
detect if the compiler supports C11 _Atomic. Now for gcc, we can compile redis
code theoretically if your gcc version is not less than 4.1.2(starts to support
__sync_xxx operations). Otherwise, we remove use mutex fallback to implement
redis atomic variable for performance and test. You will get compiling errors
if your compiler doesn't support all features of above.
For cover redis atomic variable tests, we add other CI jobs that build redis on
CentOS6 and CentOS7 and workflow daily jobs that run the tests on them.
For them, we just install gcc by default in order to cover different compiler
versions, gcc is 4.4.7 by default installation on CentOS6 and 4.8.5 on CentOS7.
We restore the feature that we can test redis with Helgrind to find data race
errors. But you need install Valgrind in the default path configuration firstly
before running your tests, since we use macros in helgrind.h to tell Helgrind
inter-thread happens-before relationship explicitly for avoiding false positives.
Please open an issue on github if you find data race errors relate to this commit.
Unrelated:
- Fix redefinition of typedef 'RedisModuleUserChangedFunc'
For some old version compilers, they will report errors or warnings, if we
re-define function type.
2020-09-17 09:01:45 -04:00
redisAtomic int requests_issued ;
redisAtomic int requests_finished ;
redisAtomic int previous_requests_finished ;
2020-08-25 14:21:29 -04:00
int last_printed_bytes ;
long long previous_tick ;
2009-03-22 05:30:00 -04:00
int keysize ;
int datasize ;
2009-05-09 03:25:59 -04:00
int randomkeys ;
2009-05-10 18:36:12 -04:00
int randomkeys_keyspacelen ;
2009-03-22 05:30:00 -04:00
int keepalive ;
2012-02-23 09:02:43 -05:00
int pipeline ;
2009-03-22 05:30:00 -04:00
long long start ;
long long totlatency ;
2011-05-31 20:15:42 -04:00
const char * title ;
2009-03-22 05:30:00 -04:00
list * clients ;
int quiet ;
2011-11-04 09:49:24 -04:00
int csv ;
2009-03-22 05:30:00 -04:00
int loop ;
2009-11-23 12:50:39 -05:00
int idlemode ;
2013-08-06 12:50:54 -04:00
int dbnum ;
sds dbnumstr ;
2011-11-07 05:29:37 -05:00
char * tests ;
2013-05-09 10:30:20 -04:00
char * auth ;
2020-05-04 11:09:21 -04:00
const char * user ;
2019-01-31 13:44:17 -05:00
int precision ;
2018-07-31 04:45:00 -04:00
int num_threads ;
struct benchmarkThread * * threads ;
2018-09-24 11:25:28 -04:00
int cluster_mode ;
int cluster_node_count ;
struct clusterNode * * cluster_nodes ;
2019-02-05 13:13:50 -05:00
struct redisConfig * redis_config ;
2020-08-25 14:21:29 -04:00
struct hdr_histogram * latency_histogram ;
struct hdr_histogram * current_sec_latency_histogram ;
Implement redisAtomic to replace _Atomic C11 builtin (#7707)
Redis 6.0 introduces I/O threads, it is so cool and efficient, we use C11
_Atomic to establish inter-thread synchronization without mutex. But the
compiler that must supports C11 _Atomic can compile redis code, that brings a
lot of inconvenience since some common platforms can't support by default such
as CentOS7, so we want to implement redis atomic type to make it more portable.
We have implemented our atomic variable for redis that only has 'relaxed'
operations in src/atomicvar.h, so we implement some operations with
'sequentially-consistent', just like the default behavior of C11 _Atomic that
can establish inter-thread synchronization. And we replace all uses of C11
_Atomic with redis atomic variable.
Our implementation of redis atomic variable uses C11 _Atomic, __atomic or
__sync macros if available, it supports most common platforms, and we will
detect automatically which feature we use. In Makefile we use a dummy file to
detect if the compiler supports C11 _Atomic. Now for gcc, we can compile redis
code theoretically if your gcc version is not less than 4.1.2(starts to support
__sync_xxx operations). Otherwise, we remove use mutex fallback to implement
redis atomic variable for performance and test. You will get compiling errors
if your compiler doesn't support all features of above.
For cover redis atomic variable tests, we add other CI jobs that build redis on
CentOS6 and CentOS7 and workflow daily jobs that run the tests on them.
For them, we just install gcc by default in order to cover different compiler
versions, gcc is 4.4.7 by default installation on CentOS6 and 4.8.5 on CentOS7.
We restore the feature that we can test redis with Helgrind to find data race
errors. But you need install Valgrind in the default path configuration firstly
before running your tests, since we use macros in helgrind.h to tell Helgrind
inter-thread happens-before relationship explicitly for avoiding false positives.
Please open an issue on github if you find data race errors relate to this commit.
Unrelated:
- Fix redefinition of typedef 'RedisModuleUserChangedFunc'
For some old version compilers, they will report errors or warnings, if we
re-define function type.
2020-09-17 09:01:45 -04:00
redisAtomic int is_fetching_slots ;
redisAtomic int is_updating_slots ;
redisAtomic int slots_last_update ;
2019-07-22 12:45:47 -04:00
int enable_tracking ;
2018-07-31 04:45:00 -04:00
pthread_mutex_t liveclients_mutex ;
2019-02-11 11:57:20 -05:00
pthread_mutex_t is_updating_slots_mutex ;
2009-03-22 05:30:00 -04:00
} config ;
typedef struct _client {
2010-11-04 08:37:05 -04:00
redisContext * context ;
2009-03-22 05:30:00 -04:00
sds obuf ;
2013-08-07 09:58:51 -04:00
char * * randptr ; /* Pointers to :rand: strings inside the command buf */
size_t randlen ; /* Number of pointers in client->randptr */
size_t randfree ; /* Number of unused pointers in client->randptr */
2018-09-29 06:59:03 -04:00
char * * stagptr ; /* Pointers to slot hashtags (cluster mode only) */
size_t staglen ; /* Number of pointers in client->stagptr */
size_t stagfree ; /* Number of unused pointers in client->stagptr */
2015-01-18 16:46:25 -05:00
size_t written ; /* Bytes of 'obuf' already written */
2013-08-07 09:58:51 -04:00
long long start ; /* Start time of a request */
long long latency ; /* Request latency */
int pending ; /* Number of pending requests (replies to consume) */
2014-12-01 16:42:40 -05:00
int prefix_pending ; /* If non-zero, number of pending prefix commands. Commands
such as auth and select are prefixed to the pipeline of
benchmark commands and discarded after the first send . */
int prefixlen ; /* Size in bytes of the pending prefix commands */
2018-07-31 04:45:00 -04:00
int thread_id ;
2018-09-29 06:59:03 -04:00
struct clusterNode * cluster_node ;
2019-02-11 11:57:20 -05:00
int slots_last_update ;
2009-03-22 05:30:00 -04:00
} * client ;
2018-07-31 04:45:00 -04:00
/* Threads. */
typedef struct benchmarkThread {
int index ;
pthread_t thread ;
aeEventLoop * el ;
} benchmarkThread ;
2018-09-24 11:25:28 -04:00
/* Cluster. */
typedef struct clusterNode {
char * ip ;
int port ;
sds name ;
int flags ;
sds replicate ; /* Master ID if node is a slave */
2018-10-25 11:38:17 -04:00
int * slots ;
int slots_count ;
2019-02-11 11:57:20 -05:00
int current_slot_index ;
int * updated_slots ; /* Used by updateClusterSlotsConfiguration */
int updated_slots_count ; /* Used by updateClusterSlotsConfiguration */
2018-09-24 11:25:28 -04:00
int replicas_count ;
sds * migrating ; /* An array of sds where even strings are slots and odd
* strings are the destination node IDs . */
sds * importing ; /* An array of sds where even strings are slots and odd
* strings are the source node IDs . */
int migrating_count ; /* Length of the migrating array (migrating slots*2) */
int importing_count ; /* Length of the importing array (importing slots*2) */
2019-02-05 13:13:50 -05:00
struct redisConfig * redis_config ;
2018-09-24 11:25:28 -04:00
} clusterNode ;
2018-07-31 04:45:00 -04:00
2019-02-05 13:13:50 -05:00
typedef struct redisConfig {
sds save ;
sds appendonly ;
} redisConfig ;
2009-03-22 05:30:00 -04:00
/* Prototypes */
2020-10-26 02:04:59 -04:00
char * redisGitSHA1 ( void ) ;
char * redisGitDirty ( void ) ;
2009-03-22 05:30:00 -04:00
static void writeHandler ( aeEventLoop * el , int fd , void * privdata , int mask ) ;
static void createMissingClients ( client c ) ;
2018-07-31 04:45:00 -04:00
static benchmarkThread * createBenchmarkThread ( int index ) ;
static void freeBenchmarkThread ( benchmarkThread * thread ) ;
static void freeBenchmarkThreads ( ) ;
static void * execBenchmarkThread ( void * ptr ) ;
2018-09-24 11:25:28 -04:00
static clusterNode * createClusterNode ( char * ip , int port ) ;
2019-02-05 13:13:50 -05:00
static redisConfig * getRedisConfig ( const char * ip , int port ,
const char * hostsocket ) ;
2020-07-10 09:37:11 -04:00
static redisContext * getRedisContext ( const char * ip , int port ,
const char * hostsocket ) ;
2019-02-05 13:13:50 -05:00
static void freeRedisConfig ( redisConfig * cfg ) ;
2019-02-11 11:57:20 -05:00
static int fetchClusterSlotsConfiguration ( client c ) ;
static void updateClusterSlotsConfiguration ( ) ;
2018-07-31 04:45:00 -04:00
int showThroughput ( struct aeEventLoop * eventLoop , long long id ,
void * clientData ) ;
2009-03-22 05:30:00 -04:00
2020-10-26 02:04:59 -04:00
static sds benchmarkVersion ( void ) {
sds version ;
version = sdscatprintf ( sdsempty ( ) , " %s " , REDIS_VERSION ) ;
/* Add git commit and working tree status when available */
if ( strtoll ( redisGitSHA1 ( ) , NULL , 16 ) ) {
version = sdscatprintf ( version , " (git:%s " , redisGitSHA1 ( ) ) ;
if ( strtoll ( redisGitDirty ( ) , NULL , 10 ) )
version = sdscatprintf ( version , " -dirty " ) ;
version = sdscat ( version , " ) " ) ;
}
return version ;
}
2019-02-11 11:57:20 -05:00
/* Dict callbacks */
static uint64_t dictSdsHash ( const void * key ) ;
static int dictSdsKeyCompare ( void * privdata , const void * key1 ,
const void * key2 ) ;
2009-03-22 05:30:00 -04:00
/* Implementation */
2010-11-04 09:47:15 -04:00
static long long ustime ( void ) {
struct timeval tv ;
long long ust ;
gettimeofday ( & tv , NULL ) ;
ust = ( ( long ) tv . tv_sec ) * 1000000 ;
ust + = tv . tv_usec ;
return ust ;
}
2009-03-22 05:30:00 -04:00
static long long mstime ( void ) {
struct timeval tv ;
long long mst ;
gettimeofday ( & tv , NULL ) ;
2012-12-20 09:20:55 -05:00
mst = ( ( long long ) tv . tv_sec ) * 1000 ;
2009-03-22 05:30:00 -04:00
mst + = tv . tv_usec / 1000 ;
return mst ;
}
2019-02-11 11:57:20 -05:00
static uint64_t dictSdsHash ( const void * key ) {
return dictGenHashFunction ( ( unsigned char * ) key , sdslen ( ( char * ) key ) ) ;
}
static int dictSdsKeyCompare ( void * privdata , const void * key1 ,
const void * key2 )
{
int l1 , l2 ;
DICT_NOTUSED ( privdata ) ;
l1 = sdslen ( ( sds ) key1 ) ;
l2 = sdslen ( ( sds ) key2 ) ;
if ( l1 ! = l2 ) return 0 ;
return memcmp ( key1 , key2 , l1 ) = = 0 ;
}
2020-07-10 09:37:11 -04:00
static redisContext * getRedisContext ( const char * ip , int port ,
const char * hostsocket )
2019-02-05 13:13:50 -05:00
{
2020-07-10 09:37:11 -04:00
redisContext * ctx = NULL ;
redisReply * reply = NULL ;
2019-02-05 13:13:50 -05:00
if ( hostsocket = = NULL )
2020-07-10 09:37:11 -04:00
ctx = redisConnect ( ip , port ) ;
2019-02-05 13:13:50 -05:00
else
2020-07-10 09:37:11 -04:00
ctx = redisConnectUnix ( hostsocket ) ;
if ( ctx = = NULL | | ctx - > err ) {
2019-02-05 13:13:50 -05:00
fprintf ( stderr , " Could not connect to Redis at " ) ;
2020-07-10 09:37:11 -04:00
char * err = ( ctx ! = NULL ? ctx - > errstr : " " ) ;
if ( hostsocket = = NULL )
fprintf ( stderr , " %s:%d: %s \n " , ip , port , err ) ;
2020-05-04 11:09:21 -04:00
else
2020-07-10 09:37:11 -04:00
fprintf ( stderr , " %s: %s \n " , hostsocket , err ) ;
goto cleanup ;
}
2020-10-28 02:00:54 -04:00
if ( config . tls = = 1 ) {
const char * err = NULL ;
if ( cliSecureConnection ( ctx , config . sslconfig , & err ) = = REDIS_ERR & & err ) {
fprintf ( stderr , " Could not negotiate a TLS connection: %s \n " , err ) ;
goto cleanup ;
}
}
2020-07-10 09:37:11 -04:00
if ( config . auth = = NULL )
return ctx ;
if ( config . user = = NULL )
reply = redisCommand ( ctx , " AUTH %s " , config . auth ) ;
else
reply = redisCommand ( ctx , " AUTH %s %s " , config . user , config . auth ) ;
if ( reply ! = NULL ) {
2019-04-26 07:47:07 -04:00
if ( reply - > type = = REDIS_REPLY_ERROR ) {
2020-07-10 09:37:11 -04:00
if ( hostsocket = = NULL )
fprintf ( stderr , " Node %s:%d replied with error: \n %s \n " , ip , port , reply - > str ) ;
else
fprintf ( stderr , " Node %s replied with error: \n %s \n " , hostsocket , reply - > str ) ;
2021-04-28 02:51:07 -04:00
freeReplyObject ( reply ) ;
redisFree ( ctx ) ;
exit ( 1 ) ;
2019-04-26 07:47:07 -04:00
}
2020-07-10 09:37:11 -04:00
freeReplyObject ( reply ) ;
return ctx ;
2019-04-26 07:47:07 -04:00
}
2020-07-10 09:37:11 -04:00
fprintf ( stderr , " ERROR: failed to fetch reply from " ) ;
if ( hostsocket = = NULL )
fprintf ( stderr , " %s:%d \n " , ip , port ) ;
else
fprintf ( stderr , " %s \n " , hostsocket ) ;
cleanup :
freeReplyObject ( reply ) ;
redisFree ( ctx ) ;
return NULL ;
}
2019-04-26 07:47:07 -04:00
2020-10-28 02:00:54 -04:00
2020-07-10 09:37:11 -04:00
static redisConfig * getRedisConfig ( const char * ip , int port ,
const char * hostsocket )
{
redisConfig * cfg = zcalloc ( sizeof ( * cfg ) ) ;
if ( ! cfg ) return NULL ;
redisContext * c = NULL ;
redisReply * reply = NULL , * sub_reply = NULL ;
c = getRedisContext ( ip , port , hostsocket ) ;
if ( c = = NULL ) {
freeRedisConfig ( cfg ) ;
return NULL ;
}
2019-02-05 13:13:50 -05:00
redisAppendCommand ( c , " CONFIG GET %s " , " save " ) ;
redisAppendCommand ( c , " CONFIG GET %s " , " appendonly " ) ;
int i = 0 ;
void * r = NULL ;
for ( ; i < 2 ; i + + ) {
int res = redisGetReply ( c , & r ) ;
if ( reply ) freeReplyObject ( reply ) ;
2020-05-05 11:09:45 -04:00
reply = res = = REDIS_OK ? ( ( redisReply * ) r ) : NULL ;
2019-02-05 13:13:50 -05:00
if ( res ! = REDIS_OK | | ! r ) goto fail ;
if ( reply - > type = = REDIS_REPLY_ERROR ) {
fprintf ( stderr , " ERROR: %s \n " , reply - > str ) ;
goto fail ;
}
if ( reply - > type ! = REDIS_REPLY_ARRAY | | reply - > elements < 2 ) goto fail ;
sub_reply = reply - > element [ 1 ] ;
char * value = sub_reply - > str ;
if ( ! value ) value = " " ;
switch ( i ) {
case 0 : cfg - > save = sdsnew ( value ) ; break ;
case 1 : cfg - > appendonly = sdsnew ( value ) ; break ;
}
}
2019-03-12 12:07:19 -04:00
freeReplyObject ( reply ) ;
redisFree ( c ) ;
2019-02-05 13:13:50 -05:00
return cfg ;
fail :
fprintf ( stderr , " ERROR: failed to fetch CONFIG from " ) ;
2019-03-12 12:07:19 -04:00
if ( hostsocket = = NULL ) fprintf ( stderr , " %s:%d \n " , ip , port ) ;
else fprintf ( stderr , " %s \n " , hostsocket ) ;
2021-04-28 02:51:07 -04:00
int abort_test = 0 ;
if ( ! strncmp ( reply - > str , " NOAUTH " , 5 ) | |
! strncmp ( reply - > str , " WRONGPASS " , 9 ) | |
! strncmp ( reply - > str , " NOPERM " , 5 ) )
abort_test = 1 ;
2019-03-12 12:07:19 -04:00
freeReplyObject ( reply ) ;
redisFree ( c ) ;
2020-05-18 22:10:57 -04:00
freeRedisConfig ( cfg ) ;
2021-04-28 02:51:07 -04:00
if ( abort_test ) exit ( 1 ) ;
2019-02-05 13:13:50 -05:00
return NULL ;
}
static void freeRedisConfig ( redisConfig * cfg ) {
if ( cfg - > save ) sdsfree ( cfg - > save ) ;
if ( cfg - > appendonly ) sdsfree ( cfg - > appendonly ) ;
zfree ( cfg ) ;
}
2009-03-22 05:30:00 -04:00
static void freeClient ( client c ) {
2018-07-31 04:45:00 -04:00
aeEventLoop * el = CLIENT_GET_EVENTLOOP ( c ) ;
2009-03-22 05:30:00 -04:00
listNode * ln ;
2018-07-31 04:45:00 -04:00
aeDeleteFileEvent ( el , c - > context - > fd , AE_WRITABLE ) ;
aeDeleteFileEvent ( el , c - > context - > fd , AE_READABLE ) ;
2019-01-17 11:40:15 -05:00
if ( c - > thread_id > = 0 ) {
int requests_finished = 0 ;
atomicGet ( config . requests_finished , requests_finished ) ;
if ( requests_finished > = config . requests ) {
aeStop ( el ) ;
}
}
2010-11-04 08:37:05 -04:00
redisFree ( c - > context ) ;
2009-03-22 05:30:00 -04:00
sdsfree ( c - > obuf ) ;
2013-08-07 09:59:59 -04:00
zfree ( c - > randptr ) ;
2018-09-29 06:59:03 -04:00
zfree ( c - > stagptr ) ;
2009-03-22 05:30:00 -04:00
zfree ( c ) ;
2018-07-31 04:45:00 -04:00
if ( config . num_threads ) pthread_mutex_lock ( & ( config . liveclients_mutex ) ) ;
2009-03-22 05:30:00 -04:00
config . liveclients - - ;
ln = listSearchKey ( config . clients , c ) ;
assert ( ln ! = NULL ) ;
listDelNode ( config . clients , ln ) ;
2018-07-31 04:45:00 -04:00
if ( config . num_threads ) pthread_mutex_unlock ( & ( config . liveclients_mutex ) ) ;
2009-03-22 05:30:00 -04:00
}
static void freeAllClients ( void ) {
listNode * ln = config . clients - > head , * next ;
while ( ln ) {
next = ln - > next ;
freeClient ( ln - > value ) ;
ln = next ;
}
}
static void resetClient ( client c ) {
2018-07-31 04:45:00 -04:00
aeEventLoop * el = CLIENT_GET_EVENTLOOP ( c ) ;
aeDeleteFileEvent ( el , c - > context - > fd , AE_WRITABLE ) ;
aeDeleteFileEvent ( el , c - > context - > fd , AE_READABLE ) ;
aeCreateFileEvent ( el , c - > context - > fd , AE_WRITABLE , writeHandler , c ) ;
2009-03-22 05:30:00 -04:00
c - > written = 0 ;
2012-02-23 09:02:43 -05:00
c - > pending = config . pipeline ;
2009-03-22 05:30:00 -04:00
}
2009-05-10 18:36:12 -04:00
static void randomizeClientKey ( client c ) {
2013-08-08 08:31:54 -04:00
size_t i ;
2009-05-10 18:36:12 -04:00
2010-12-23 05:04:44 -05:00
for ( i = 0 ; i < c - > randlen ; i + + ) {
2013-08-08 08:31:54 -04:00
char * p = c - > randptr [ i ] + 11 ;
2019-03-07 05:30:09 -05:00
size_t r = 0 ;
if ( config . randomkeys_keyspacelen ! = 0 )
r = random ( ) % config . randomkeys_keyspacelen ;
2013-08-08 08:31:54 -04:00
size_t j ;
for ( j = 0 ; j < 12 ; j + + ) {
* p = ' 0 ' + r % 10 ;
r / = 10 ;
p - - ;
}
2010-12-16 18:19:32 -05:00
}
2009-05-10 18:36:12 -04:00
}
2018-09-29 06:59:03 -04:00
static void setClusterKeyHashTag ( client c ) {
assert ( c - > thread_id > = 0 ) ;
clusterNode * node = c - > cluster_node ;
assert ( node ) ;
2018-10-25 11:38:17 -04:00
assert ( node - > current_slot_index < node - > slots_count ) ;
2019-02-11 11:57:20 -05:00
int is_updating_slots = 0 ;
atomicGet ( config . is_updating_slots , is_updating_slots ) ;
/* If updateClusterSlotsConfiguration is updating the slots array,
* call updateClusterSlotsConfiguration is order to block the thread
* since the mutex is locked . When the slots will be updated by the
* thread that ' s actually performing the update , the execution of
* updateClusterSlotsConfiguration won ' t actually do anything , since
* the updated_slots_count array will be already NULL . */
if ( is_updating_slots ) updateClusterSlotsConfiguration ( ) ;
2018-10-25 11:38:17 -04:00
int slot = node - > slots [ node - > current_slot_index ] ;
2018-09-29 06:59:03 -04:00
const char * tag = crc16_slot_table [ slot ] ;
int taglen = strlen ( tag ) ;
size_t i ;
for ( i = 0 ; i < c - > staglen ; i + + ) {
char * p = c - > stagptr [ i ] + 1 ;
p [ 0 ] = tag [ 0 ] ;
p [ 1 ] = ( taglen > = 2 ? tag [ 1 ] : ' } ' ) ;
p [ 2 ] = ( taglen = = 3 ? tag [ 2 ] : ' } ' ) ;
}
}
2009-03-22 05:30:00 -04:00
static void clientDone ( client c ) {
2018-07-31 04:45:00 -04:00
int requests_finished = 0 ;
2019-01-18 11:31:32 -05:00
atomicGet ( config . requests_finished , requests_finished ) ;
2018-10-25 11:38:17 -04:00
if ( requests_finished > = config . requests ) {
2009-03-22 05:30:00 -04:00
freeClient ( c ) ;
2019-01-17 11:40:15 -05:00
if ( ! config . num_threads & & config . el ) aeStop ( config . el ) ;
2009-03-22 05:30:00 -04:00
return ;
}
if ( config . keepalive ) {
resetClient ( c ) ;
} else {
2018-07-31 04:45:00 -04:00
if ( config . num_threads ) pthread_mutex_lock ( & ( config . liveclients_mutex ) ) ;
2009-03-22 05:30:00 -04:00
config . liveclients - - ;
createMissingClients ( c ) ;
config . liveclients + + ;
2018-07-31 04:45:00 -04:00
if ( config . num_threads )
pthread_mutex_unlock ( & ( config . liveclients_mutex ) ) ;
2009-03-22 05:30:00 -04:00
freeClient ( c ) ;
}
}
2010-11-04 08:37:05 -04:00
static void readHandler ( aeEventLoop * el , int fd , void * privdata , int mask ) {
2009-03-22 05:30:00 -04:00
client c = privdata ;
2010-11-04 08:37:05 -04:00
void * reply = NULL ;
2015-07-27 03:41:48 -04:00
UNUSED ( el ) ;
UNUSED ( fd ) ;
UNUSED ( mask ) ;
2009-03-22 05:30:00 -04:00
2010-11-04 09:47:15 -04:00
/* Calculate latency only for the first read event. This means that the
* server already sent the reply and we need to parse it . Parsing overhead
* is not part of the latency , so calculate it only once , here . */
if ( c - > latency < 0 ) c - > latency = ustime ( ) - ( c - > start ) ;
2010-11-04 08:37:05 -04:00
if ( redisBufferRead ( c - > context ) ! = REDIS_OK ) {
fprintf ( stderr , " Error: %s \n " , c - > context - > errstr ) ;
exit ( 1 ) ;
} else {
2012-02-23 09:02:43 -05:00
while ( c - > pending ) {
if ( redisGetReply ( c - > context , & reply ) ! = REDIS_OK ) {
fprintf ( stderr , " Error: %s \n " , c - > context - > errstr ) ;
2010-12-16 17:35:02 -05:00
exit ( 1 ) ;
}
2012-02-23 09:02:43 -05:00
if ( reply ! = NULL ) {
if ( reply = = ( void * ) REDIS_REPLY_ERROR ) {
fprintf ( stderr , " Unexpected error reply, exiting... \n " ) ;
exit ( 1 ) ;
}
2018-10-25 11:38:17 -04:00
redisReply * r = reply ;
2021-04-28 02:51:07 -04:00
if ( r - > type = = REDIS_REPLY_ERROR ) {
/* Try to update slots configuration if reply error is
* MOVED / ASK / CLUSTERDOWN and the key ( s ) used by the command
* contain ( s ) the slot hash tag .
* If the error is not topology - update related then we
* immediately exit to avoid false results . */
if ( c - > cluster_node & & c - > staglen ) {
int fetch_slots = 0 , do_wait = 0 ;
if ( ! strncmp ( r - > str , " MOVED " , 5 ) | | ! strncmp ( r - > str , " ASK " , 3 ) )
fetch_slots = 1 ;
else if ( ! strncmp ( r - > str , " CLUSTERDOWN " , 11 ) ) {
/* Usually the cluster is able to recover itself after
* a CLUSTERDOWN error , so try to sleep one second
* before requesting the new configuration . */
fetch_slots = 1 ;
do_wait = 1 ;
printf ( " Error from server %s:%d: %s. \n " ,
2018-09-29 06:59:03 -04:00
c - > cluster_node - > ip ,
c - > cluster_node - > port ,
r - > str ) ;
2021-04-28 02:51:07 -04:00
}
if ( do_wait ) sleep ( 1 ) ;
if ( fetch_slots & & ! fetchClusterSlotsConfiguration ( c ) )
exit ( 1 ) ;
} else {
if ( c - > cluster_node ) {
printf ( " Error from server %s:%d: %s \n " ,
c - > cluster_node - > ip ,
c - > cluster_node - > port ,
r - > str ) ;
2018-09-29 06:59:03 -04:00
} else printf ( " Error from server: %s \n " , r - > str ) ;
2019-03-08 05:05:02 -05:00
exit ( 1 ) ;
2021-04-28 02:51:07 -04:00
}
2018-10-25 11:38:17 -04:00
}
2012-02-26 04:01:27 -05:00
freeReplyObject ( reply ) ;
2014-12-01 17:54:49 -05:00
/* This is an OK for prefix commands such as auth and select.*/
2014-12-01 16:42:40 -05:00
if ( c - > prefix_pending > 0 ) {
c - > prefix_pending - - ;
2013-08-06 12:50:54 -04:00
c - > pending - - ;
2014-12-01 17:54:49 -05:00
/* Discard prefix commands on first response.*/
2014-12-01 16:42:40 -05:00
if ( c - > prefixlen > 0 ) {
size_t j ;
sdsrange ( c - > obuf , c - > prefixlen , - 1 ) ;
/* We also need to fix the pointers to the strings
* we need to randomize . */
for ( j = 0 ; j < c - > randlen ; j + + )
c - > randptr [ j ] - = c - > prefixlen ;
2021-02-14 07:42:41 -05:00
/* Fix the pointers to the slot hash tags */
for ( j = 0 ; j < c - > staglen ; j + + )
c - > stagptr [ j ] - = c - > prefixlen ;
2014-12-01 16:42:40 -05:00
c - > prefixlen = 0 ;
}
2016-07-12 05:22:41 -04:00
continue ;
2013-08-06 12:50:54 -04:00
}
2018-10-25 11:38:17 -04:00
int requests_finished = 0 ;
atomicGetIncr ( config . requests_finished , requests_finished , 1 ) ;
2020-08-25 14:21:29 -04:00
if ( requests_finished < config . requests ) {
if ( config . num_threads = = 0 ) {
hdr_record_value (
config . latency_histogram , // Histogram to record to
( long ) c - > latency < = CONFIG_LATENCY_HISTOGRAM_MAX_VALUE ? ( long ) c - > latency : CONFIG_LATENCY_HISTOGRAM_MAX_VALUE ) ; // Value to record
hdr_record_value (
config . current_sec_latency_histogram , // Histogram to record to
( long ) c - > latency < = CONFIG_LATENCY_HISTOGRAM_INSTANT_MAX_VALUE ? ( long ) c - > latency : CONFIG_LATENCY_HISTOGRAM_INSTANT_MAX_VALUE ) ; // Value to record
} else {
hdr_record_value_atomic (
config . latency_histogram , // Histogram to record to
( long ) c - > latency < = CONFIG_LATENCY_HISTOGRAM_MAX_VALUE ? ( long ) c - > latency : CONFIG_LATENCY_HISTOGRAM_MAX_VALUE ) ; // Value to record
hdr_record_value_atomic (
config . current_sec_latency_histogram , // Histogram to record to
( long ) c - > latency < = CONFIG_LATENCY_HISTOGRAM_INSTANT_MAX_VALUE ? ( long ) c - > latency : CONFIG_LATENCY_HISTOGRAM_INSTANT_MAX_VALUE ) ; // Value to record
}
}
2012-02-23 09:02:43 -05:00
c - > pending - - ;
2012-10-10 05:08:43 -04:00
if ( c - > pending = = 0 ) {
clientDone ( c ) ;
break ;
}
2012-02-23 09:02:43 -05:00
} else {
break ;
}
2010-11-04 09:47:15 -04:00
}
2009-11-17 10:57:35 -05:00
}
2009-03-22 05:30:00 -04:00
}
2010-11-04 08:37:05 -04:00
static void writeHandler ( aeEventLoop * el , int fd , void * privdata , int mask ) {
2009-03-22 05:30:00 -04:00
client c = privdata ;
2015-07-27 03:41:48 -04:00
UNUSED ( el ) ;
UNUSED ( fd ) ;
UNUSED ( mask ) ;
2009-03-22 05:30:00 -04:00
2011-08-17 11:06:19 -04:00
/* Initialize request when nothing was written. */
2010-12-23 05:22:40 -05:00
if ( c - > written = = 0 ) {
2011-08-17 11:06:19 -04:00
/* Enforce upper bound to number of requests. */
2018-07-31 04:45:00 -04:00
int requests_issued = 0 ;
2020-10-26 02:04:59 -04:00
atomicGetIncr ( config . requests_issued , requests_issued , config . pipeline ) ;
2018-07-31 04:45:00 -04:00
if ( requests_issued > = config . requests ) {
2011-08-17 11:06:19 -04:00
return ;
}
/* Really initialize: randomize keys and set start time. */
2010-12-23 05:22:40 -05:00
if ( config . randomkeys ) randomizeClientKey ( c ) ;
2018-09-29 06:59:03 -04:00
if ( config . cluster_mode & & c - > staglen > 0 ) setClusterKeyHashTag ( c ) ;
2019-02-11 11:57:20 -05:00
atomicGet ( config . slots_last_update , c - > slots_last_update ) ;
2010-11-04 09:47:15 -04:00
c - > start = ustime ( ) ;
c - > latency = - 1 ;
2009-03-22 05:30:00 -04:00
}
2020-10-28 02:00:54 -04:00
const ssize_t buflen = sdslen ( c - > obuf ) ;
const ssize_t writeLen = buflen - c - > written ;
if ( writeLen > 0 ) {
2009-03-22 05:30:00 -04:00
void * ptr = c - > obuf + c - > written ;
2020-10-28 02:00:54 -04:00
while ( 1 ) {
/* Optimistically try to write before checking if the file descriptor
* is actually writable . At worst we get EAGAIN . */
const ssize_t nwritten = cliWriteConn ( c - > context , ptr , writeLen ) ;
if ( nwritten ! = writeLen ) {
if ( nwritten = = - 1 & & errno ! = EAGAIN ) {
if ( errno ! = EPIPE )
fprintf ( stderr , " Error writing to the server: %s \n " , strerror ( errno ) ) ;
freeClient ( c ) ;
return ;
}
} else {
aeDeleteFileEvent ( el , c - > context - > fd , AE_WRITABLE ) ;
aeCreateFileEvent ( el , c - > context - > fd , AE_READABLE , readHandler , c ) ;
return ;
}
2009-03-22 05:30:00 -04:00
}
}
}
2013-08-08 10:42:08 -04:00
/* Create a benchmark client, configured to send the command passed as 'cmd' of
* ' len ' bytes .
*
* The command is copied N times in the client output buffer ( that is reused
* again and again to send the request to the server ) accordingly to the configured
* pipeline size .
*
* Also an initial SELECT command is prepended in order to make sure the right
* database is selected , if needed . The initial SELECT will be discarded as soon
* as the first reply is received .
*
* To create a client from scratch , the ' from ' pointer is set to NULL . If instead
* we want to create a client using another client as reference , the ' from ' pointer
* points to the client to use as reference . In such a case the following
* information is take from the ' from ' client :
*
* 1 ) The command line to use .
* 2 ) The offsets of the __rand_int__ elements inside the command line , used
* for arguments randomization .
*
2014-12-01 16:42:40 -05:00
* Even when cloning another client , prefix commands are applied if needed . */
2018-07-31 04:45:00 -04:00
static client createClient ( char * cmd , size_t len , client from , int thread_id ) {
2012-02-23 09:02:43 -05:00
int j ;
2018-09-24 11:25:28 -04:00
int is_cluster_client = ( config . cluster_mode & & thread_id > = 0 ) ;
2009-03-22 05:30:00 -04:00
client c = zmalloc ( sizeof ( struct _client ) ) ;
2012-02-23 09:02:43 -05:00
2019-01-17 11:40:15 -05:00
const char * ip = NULL ;
int port = 0 ;
2018-09-29 06:59:03 -04:00
c - > cluster_node = NULL ;
2018-09-24 11:25:28 -04:00
if ( config . hostsocket = = NULL | | is_cluster_client ) {
if ( ! is_cluster_client ) {
ip = config . hostip ;
port = config . hostport ;
} else {
2019-01-17 11:40:15 -05:00
int node_idx = 0 ;
if ( config . num_threads < config . cluster_node_count )
node_idx = config . liveclients % config . cluster_node_count ;
else
node_idx = thread_id % config . cluster_node_count ;
clusterNode * node = config . cluster_nodes [ node_idx ] ;
2019-02-05 13:13:50 -05:00
assert ( node ! = NULL ) ;
2018-09-24 11:25:28 -04:00
ip = ( const char * ) node - > ip ;
port = node - > port ;
2018-09-29 06:59:03 -04:00
c - > cluster_node = node ;
2018-09-24 11:25:28 -04:00
}
c - > context = redisConnectNonBlock ( ip , port ) ;
2010-11-04 08:37:05 -04:00
} else {
c - > context = redisConnectUnixNonBlock ( config . hostsocket ) ;
2009-03-22 05:30:00 -04:00
}
2010-11-04 08:37:05 -04:00
if ( c - > context - > err ) {
fprintf ( stderr , " Could not connect to Redis at " ) ;
2018-09-29 06:59:03 -04:00
if ( config . hostsocket = = NULL | | is_cluster_client )
fprintf ( stderr , " %s:%d: %s \n " , ip , port , c - > context - > errstr ) ;
2010-11-04 08:37:05 -04:00
else
fprintf ( stderr , " %s: %s \n " , config . hostsocket , c - > context - > errstr ) ;
exit ( 1 ) ;
}
2020-10-28 02:00:54 -04:00
if ( config . tls = = 1 ) {
const char * err = NULL ;
if ( cliSecureConnection ( c - > context , config . sslconfig , & err ) = = REDIS_ERR & & err ) {
fprintf ( stderr , " Could not negotiate a TLS connection: %s \n " , err ) ;
exit ( 1 ) ;
}
}
2018-07-31 04:45:00 -04:00
c - > thread_id = thread_id ;
2012-08-21 11:31:44 -04:00
/* Suppress hiredis cleanup of unused buffers for max speed. */
c - > context - > reader - > maxbuf = 0 ;
2013-08-06 12:50:54 -04:00
2013-08-08 10:42:08 -04:00
/* Build the request buffer:
* Queue N requests accordingly to the pipeline size , or simply clone
* the example client buffer . */
2012-02-23 09:02:43 -05:00
c - > obuf = sdsempty ( ) ;
2014-12-01 16:42:40 -05:00
/* Prefix the request buffer with AUTH and/or SELECT commands, if applicable.
* These commands are discarded after the first response , so if the client is
* reused the commands will not be used again . */
c - > prefix_pending = 0 ;
2013-05-09 10:30:20 -04:00
if ( config . auth ) {
char * buf = NULL ;
2020-05-04 11:09:21 -04:00
int len ;
if ( config . user = = NULL )
len = redisFormatCommand ( & buf , " AUTH %s " , config . auth ) ;
else
len = redisFormatCommand ( & buf , " AUTH %s %s " ,
config . user , config . auth ) ;
2013-05-09 10:30:20 -04:00
c - > obuf = sdscatlen ( c - > obuf , buf , len ) ;
free ( buf ) ;
2014-12-01 16:42:40 -05:00
c - > prefix_pending + + ;
2013-05-09 10:30:20 -04:00
}
2019-07-22 12:45:47 -04:00
if ( config . enable_tracking ) {
char * buf = NULL ;
int len = redisFormatCommand ( & buf , " CLIENT TRACKING on " ) ;
c - > obuf = sdscatlen ( c - > obuf , buf , len ) ;
free ( buf ) ;
c - > prefix_pending + + ;
}
2013-08-08 10:42:08 -04:00
/* If a DB number different than zero is selected, prefix our request
* buffer with the SELECT command , that will be discarded the first
* time the replies are received , so if the client is reused the
* SELECT command will not be used again . */
2019-01-18 11:31:32 -05:00
if ( config . dbnum ! = 0 & & ! is_cluster_client ) {
2013-08-06 12:50:54 -04:00
c - > obuf = sdscatprintf ( c - > obuf , " *2 \r \n $6 \r \n SELECT \r \n $%d \r \n %s \r \n " ,
( int ) sdslen ( config . dbnumstr ) , config . dbnumstr ) ;
2014-12-01 16:42:40 -05:00
c - > prefix_pending + + ;
2013-08-06 12:50:54 -04:00
}
2014-12-01 16:42:40 -05:00
c - > prefixlen = sdslen ( c - > obuf ) ;
2013-08-08 10:42:08 -04:00
/* Append the request itself. */
if ( from ) {
c - > obuf = sdscatlen ( c - > obuf ,
2014-12-01 16:42:40 -05:00
from - > obuf + from - > prefixlen ,
sdslen ( from - > obuf ) - from - > prefixlen ) ;
2013-08-08 10:42:08 -04:00
} else {
for ( j = 0 ; j < config . pipeline ; j + + )
c - > obuf = sdscatlen ( c - > obuf , cmd , len ) ;
}
2013-05-09 10:30:20 -04:00
2009-03-22 05:30:00 -04:00
c - > written = 0 ;
2014-12-01 16:42:40 -05:00
c - > pending = config . pipeline + c - > prefix_pending ;
2013-08-08 10:42:08 -04:00
c - > randptr = NULL ;
c - > randlen = 0 ;
2018-09-29 06:59:03 -04:00
c - > stagptr = NULL ;
c - > staglen = 0 ;
2010-12-23 05:04:44 -05:00
/* Find substrings in the output buffer that need to be randomized. */
if ( config . randomkeys ) {
2013-08-08 10:42:08 -04:00
if ( from ) {
c - > randlen = from - > randlen ;
c - > randfree = 0 ;
c - > randptr = zmalloc ( sizeof ( char * ) * c - > randlen ) ;
/* copy the offsets. */
2014-08-13 05:44:38 -04:00
for ( j = 0 ; j < ( int ) c - > randlen ; j + + ) {
2013-08-08 10:42:08 -04:00
c - > randptr [ j ] = c - > obuf + ( from - > randptr [ j ] - from - > obuf ) ;
/* Adjust for the different select prefix length. */
2014-12-01 16:42:40 -05:00
c - > randptr [ j ] + = c - > prefixlen - from - > prefixlen ;
2013-08-08 10:42:08 -04:00
}
} else {
char * p = c - > obuf ;
c - > randlen = 0 ;
c - > randfree = RANDPTR_INITIAL_SIZE ;
c - > randptr = zmalloc ( sizeof ( char * ) * c - > randfree ) ;
while ( ( p = strstr ( p , " __rand_int__ " ) ) ! = NULL ) {
if ( c - > randfree = = 0 ) {
c - > randptr = zrealloc ( c - > randptr , sizeof ( char * ) * c - > randlen * 2 ) ;
c - > randfree + = c - > randlen ;
}
c - > randptr [ c - > randlen + + ] = p ;
c - > randfree - - ;
p + = 12 ; /* 12 is strlen("__rand_int__). */
2013-08-07 09:58:51 -04:00
}
2010-12-23 05:04:44 -05:00
}
}
2018-09-29 06:59:03 -04:00
/* If cluster mode is enabled, set slot hashtags pointers. */
if ( config . cluster_mode ) {
if ( from ) {
c - > staglen = from - > staglen ;
c - > stagfree = 0 ;
c - > stagptr = zmalloc ( sizeof ( char * ) * c - > staglen ) ;
/* copy the offsets. */
for ( j = 0 ; j < ( int ) c - > staglen ; j + + ) {
c - > stagptr [ j ] = c - > obuf + ( from - > stagptr [ j ] - from - > obuf ) ;
/* Adjust for the different select prefix length. */
c - > stagptr [ j ] + = c - > prefixlen - from - > prefixlen ;
}
} else {
char * p = c - > obuf ;
c - > staglen = 0 ;
c - > stagfree = RANDPTR_INITIAL_SIZE ;
c - > stagptr = zmalloc ( sizeof ( char * ) * c - > stagfree ) ;
while ( ( p = strstr ( p , " {tag} " ) ) ! = NULL ) {
if ( c - > stagfree = = 0 ) {
c - > stagptr = zrealloc ( c - > stagptr ,
2019-01-17 11:40:15 -05:00
sizeof ( char * ) * c - > staglen * 2 ) ;
2018-09-29 06:59:03 -04:00
c - > stagfree + = c - > staglen ;
}
c - > stagptr [ c - > staglen + + ] = p ;
c - > stagfree - - ;
2020-10-20 12:52:05 -04:00
p + = 5 ; /* 5 is strlen("{tag}"). */
2018-09-29 06:59:03 -04:00
}
}
}
2018-07-31 04:45:00 -04:00
aeEventLoop * el = NULL ;
if ( thread_id < 0 ) el = config . el ;
else {
benchmarkThread * thread = config . threads [ thread_id ] ;
el = thread - > el ;
}
2014-10-27 02:02:52 -04:00
if ( config . idlemode = = 0 )
2018-07-31 04:45:00 -04:00
aeCreateFileEvent ( el , c - > context - > fd , AE_WRITABLE , writeHandler , c ) ;
2009-03-22 05:30:00 -04:00
listAddNodeTail ( config . clients , c ) ;
2019-02-05 13:18:25 -05:00
atomicIncr ( config . liveclients , 1 ) ;
2019-02-11 11:57:20 -05:00
atomicGet ( config . slots_last_update , c - > slots_last_update ) ;
2009-03-22 05:30:00 -04:00
return c ;
}
static void createMissingClients ( client c ) {
2010-12-18 04:58:50 -05:00
int n = 0 ;
2009-03-22 05:30:00 -04:00
while ( config . liveclients < config . numclients ) {
2018-07-31 04:45:00 -04:00
int thread_id = - 1 ;
if ( config . num_threads )
thread_id = config . liveclients % config . num_threads ;
createClient ( NULL , 0 , c , thread_id ) ;
2010-12-18 04:58:50 -05:00
/* Listen backlog is quite limited on most systems */
if ( + + n > 64 ) {
usleep ( 50000 ) ;
n = 0 ;
}
2009-03-22 05:30:00 -04:00
}
}
2010-08-30 05:25:02 -04:00
static void showLatencyReport ( void ) {
2009-03-22 05:30:00 -04:00
2020-08-25 14:21:29 -04:00
const float reqpersec = ( float ) config . requests_finished / ( ( float ) config . totlatency / 1000.0f ) ;
const float p0 = ( ( float ) hdr_min ( config . latency_histogram ) ) / 1000.0f ;
const float p50 = hdr_value_at_percentile ( config . latency_histogram , 50.0 ) / 1000.0f ;
const float p95 = hdr_value_at_percentile ( config . latency_histogram , 95.0 ) / 1000.0f ;
const float p99 = hdr_value_at_percentile ( config . latency_histogram , 99.0 ) / 1000.0f ;
const float p100 = ( ( float ) hdr_max ( config . latency_histogram ) ) / 1000.0f ;
const float avg = hdr_mean ( config . latency_histogram ) / 1000.0f ;
2011-11-04 09:49:24 -04:00
if ( ! config . quiet & & ! config . csv ) {
2020-08-25 14:21:29 -04:00
printf ( " %*s \r " , config . last_printed_bytes , " " ) ; // ensure there is a clean line
2010-08-30 05:25:02 -04:00
printf ( " ====== %s ====== \n " , config . title ) ;
2011-08-17 11:06:19 -04:00
printf ( " %d requests completed in %.2f seconds \n " , config . requests_finished ,
2009-03-22 05:30:00 -04:00
( float ) config . totlatency / 1000 ) ;
printf ( " %d parallel clients \n " , config . numclients ) ;
printf ( " %d bytes payload \n " , config . datasize ) ;
printf ( " keep alive: %d \n " , config . keepalive ) ;
2019-02-05 13:13:50 -05:00
if ( config . cluster_mode ) {
printf ( " cluster mode: yes (%d masters) \n " ,
config . cluster_node_count ) ;
int m ;
for ( m = 0 ; m < config . cluster_node_count ; m + + ) {
clusterNode * node = config . cluster_nodes [ m ] ;
redisConfig * cfg = node - > redis_config ;
if ( cfg = = NULL ) continue ;
printf ( " node [%d] configuration: \n " , m ) ;
printf ( " save: %s \n " ,
sdslen ( cfg - > save ) ? cfg - > save : " NONE " ) ;
printf ( " appendonly: %s \n " , cfg - > appendonly ) ;
}
} else {
if ( config . redis_config ) {
printf ( " host configuration \" save \" : %s \n " ,
config . redis_config - > save ) ;
printf ( " host configuration \" appendonly \" : %s \n " ,
config . redis_config - > appendonly ) ;
}
}
2018-07-31 04:45:00 -04:00
printf ( " multi-thread: %s \n " , ( config . num_threads ? " yes " : " no " ) ) ;
if ( config . num_threads )
printf ( " threads: %d \n " , config . num_threads ) ;
2019-02-05 13:13:50 -05:00
2009-03-22 05:30:00 -04:00
printf ( " \n " ) ;
2020-08-25 14:21:29 -04:00
printf ( " Latency by percentile distribution: \n " ) ;
struct hdr_iter iter ;
long long previous_cumulative_count = - 1 ;
const long long total_count = config . latency_histogram - > total_count ;
hdr_iter_percentile_init ( & iter , config . latency_histogram , 1 ) ;
struct hdr_iter_percentiles * percentiles = & iter . specifics . percentiles ;
while ( hdr_iter_next ( & iter ) )
{
const double value = iter . highest_equivalent_value / 1000.0f ;
const double percentile = percentiles - > percentile ;
const long long cumulative_count = iter . cumulative_count ;
if ( previous_cumulative_count ! = cumulative_count | | cumulative_count = = total_count ) {
printf ( " %3.3f%% <= %.3f milliseconds (cumulative count %lld) \n " , percentile , value , cumulative_count ) ;
2009-03-22 05:30:00 -04:00
}
2020-08-25 14:21:29 -04:00
previous_cumulative_count = cumulative_count ;
2009-03-22 05:30:00 -04:00
}
2020-08-25 14:21:29 -04:00
printf ( " \n " ) ;
printf ( " Cumulative distribution of latencies: \n " ) ;
previous_cumulative_count = - 1 ;
hdr_iter_linear_init ( & iter , config . latency_histogram , 100 ) ;
while ( hdr_iter_next ( & iter ) )
{
const double value = iter . highest_equivalent_value / 1000.0f ;
const long long cumulative_count = iter . cumulative_count ;
const double percentile = ( ( double ) cumulative_count / ( double ) total_count ) * 100.0 ;
if ( previous_cumulative_count ! = cumulative_count | | cumulative_count = = total_count ) {
printf ( " %3.3f%% <= %.3f milliseconds (cumulative count %lld) \n " , percentile , value , cumulative_count ) ;
}
/* After the 2 milliseconds latency to have percentages split
* by decimals will just add a lot of noise to the output . */
if ( iter . highest_equivalent_value > 2000 ) {
hdr_iter_linear_set_value_units_per_bucket ( & iter , 1000 ) ;
}
previous_cumulative_count = cumulative_count ;
}
printf ( " \n " ) ;
printf ( " Summary: \n " ) ;
printf ( " throughput summary: %.2f requests per second \n " , reqpersec ) ;
printf ( " latency summary (msec): \n " ) ;
printf ( " %9s %9s %9s %9s %9s %9s \n " , " avg " , " min " , " p50 " , " p95 " , " p99 " , " max " ) ;
printf ( " %9.3f %9.3f %9.3f %9.3f %9.3f %9.3f \n " , avg , p0 , p50 , p95 , p99 , p100 ) ;
2011-11-04 09:49:24 -04:00
} else if ( config . csv ) {
2020-08-25 14:21:29 -04:00
printf ( " \" %s \" , \" %.2f \" , \" %.3f \" , \" %.3f \" , \" %.3f \" , \" %.3f \" , \" %.3f \" , \" %.3f \" \n " , config . title , reqpersec , avg , p0 , p50 , p95 , p99 , p100 ) ;
2009-03-22 05:30:00 -04:00
} else {
2020-08-25 14:21:29 -04:00
printf ( " %*s \r " , config . last_printed_bytes , " " ) ; // ensure there is a clean line
printf ( " %s: %.2f requests per second, p50=%.3f msec \n " , config . title , reqpersec , p50 ) ;
2009-03-22 05:30:00 -04:00
}
}
2019-03-07 05:14:03 -05:00
static void initBenchmarkThreads ( ) {
int i ;
if ( config . threads ) freeBenchmarkThreads ( ) ;
config . threads = zmalloc ( config . num_threads * sizeof ( benchmarkThread * ) ) ;
for ( i = 0 ; i < config . num_threads ; i + + ) {
benchmarkThread * thread = createBenchmarkThread ( i ) ;
config . threads [ i ] = thread ;
}
}
static void startBenchmarkThreads ( ) {
2018-07-31 04:45:00 -04:00
int i ;
2019-03-07 05:14:03 -05:00
for ( i = 0 ; i < config . num_threads ; i + + ) {
benchmarkThread * t = config . threads [ i ] ;
if ( pthread_create ( & ( t - > thread ) , NULL , execBenchmarkThread , t ) ) {
fprintf ( stderr , " FATAL: Failed to start thread %d. \n " , i ) ;
exit ( 1 ) ;
}
}
for ( i = 0 ; i < config . num_threads ; i + + )
pthread_join ( config . threads [ i ] - > thread , NULL ) ;
}
static void benchmark ( char * title , char * cmd , int len ) {
2010-12-22 12:31:33 -05:00
client c ;
2010-08-30 05:25:02 -04:00
config . title = title ;
2011-08-17 11:06:19 -04:00
config . requests_issued = 0 ;
config . requests_finished = 0 ;
2020-08-25 14:21:29 -04:00
config . previous_requests_finished = 0 ;
config . last_printed_bytes = 0 ;
hdr_init (
CONFIG_LATENCY_HISTOGRAM_MIN_VALUE , // Minimum value
CONFIG_LATENCY_HISTOGRAM_MAX_VALUE , // Maximum value
config . precision , // Number of significant figures
& config . latency_histogram ) ; // Pointer to initialise
hdr_init (
CONFIG_LATENCY_HISTOGRAM_MIN_VALUE , // Minimum value
CONFIG_LATENCY_HISTOGRAM_INSTANT_MAX_VALUE , // Maximum value
config . precision , // Number of significant figures
& config . current_sec_latency_histogram ) ; // Pointer to initialise
2009-03-22 05:30:00 -04:00
2019-03-07 05:14:03 -05:00
if ( config . num_threads ) initBenchmarkThreads ( ) ;
2018-07-31 04:45:00 -04:00
int thread_id = config . num_threads > 0 ? 0 : - 1 ;
c = createClient ( cmd , len , NULL , thread_id ) ;
2010-12-22 12:31:33 -05:00
createMissingClients ( c ) ;
config . start = mstime ( ) ;
2018-07-31 04:45:00 -04:00
if ( ! config . num_threads ) aeMain ( config . el ) ;
2019-03-07 05:14:03 -05:00
else startBenchmarkThreads ( ) ;
2009-03-22 05:30:00 -04:00
config . totlatency = mstime ( ) - config . start ;
2010-12-22 12:31:33 -05:00
2010-08-30 05:25:02 -04:00
showLatencyReport ( ) ;
2009-03-22 05:30:00 -04:00
freeAllClients ( ) ;
2018-07-31 04:45:00 -04:00
if ( config . threads ) freeBenchmarkThreads ( ) ;
2020-08-25 14:21:29 -04:00
if ( config . current_sec_latency_histogram ) hdr_close ( config . current_sec_latency_histogram ) ;
if ( config . latency_histogram ) hdr_close ( config . latency_histogram ) ;
2018-07-31 04:45:00 -04:00
}
2019-02-05 13:13:50 -05:00
/* Thread functions. */
2018-07-31 04:45:00 -04:00
static benchmarkThread * createBenchmarkThread ( int index ) {
benchmarkThread * thread = zmalloc ( sizeof ( * thread ) ) ;
if ( thread = = NULL ) return NULL ;
thread - > index = index ;
thread - > el = aeCreateEventLoop ( 1024 * 10 ) ;
aeCreateTimeEvent ( thread - > el , 1 , showThroughput , NULL , NULL ) ;
return thread ;
}
static void freeBenchmarkThread ( benchmarkThread * thread ) {
if ( thread - > el ) aeDeleteEventLoop ( thread - > el ) ;
zfree ( thread ) ;
}
static void freeBenchmarkThreads ( ) {
int i = 0 ;
for ( ; i < config . num_threads ; i + + ) {
benchmarkThread * thread = config . threads [ i ] ;
if ( thread ) freeBenchmarkThread ( thread ) ;
}
zfree ( config . threads ) ;
config . threads = NULL ;
}
static void * execBenchmarkThread ( void * ptr ) {
benchmarkThread * thread = ( benchmarkThread * ) ptr ;
aeMain ( thread - > el ) ;
return NULL ;
2009-03-22 05:30:00 -04:00
}
2019-02-05 13:13:50 -05:00
/* Cluster helper functions. */
2018-09-24 11:25:28 -04:00
static clusterNode * createClusterNode ( char * ip , int port ) {
clusterNode * node = zmalloc ( sizeof ( * node ) ) ;
if ( ! node ) return NULL ;
node - > ip = ip ;
node - > port = port ;
node - > name = NULL ;
node - > flags = 0 ;
node - > replicate = NULL ;
node - > replicas_count = 0 ;
2018-10-25 11:38:17 -04:00
node - > slots = zmalloc ( CLUSTER_SLOTS * sizeof ( int ) ) ;
node - > slots_count = 0 ;
node - > current_slot_index = 0 ;
2019-02-11 11:57:20 -05:00
node - > updated_slots = NULL ;
node - > updated_slots_count = 0 ;
2018-09-24 11:25:28 -04:00
node - > migrating = NULL ;
node - > importing = NULL ;
node - > migrating_count = 0 ;
node - > importing_count = 0 ;
2019-02-05 13:13:50 -05:00
node - > redis_config = NULL ;
2018-09-24 11:25:28 -04:00
return node ;
}
static void freeClusterNode ( clusterNode * node ) {
int i ;
if ( node - > name ) sdsfree ( node - > name ) ;
if ( node - > replicate ) sdsfree ( node - > replicate ) ;
if ( node - > migrating ! = NULL ) {
for ( i = 0 ; i < node - > migrating_count ; i + + ) sdsfree ( node - > migrating [ i ] ) ;
zfree ( node - > migrating ) ;
}
if ( node - > importing ! = NULL ) {
for ( i = 0 ; i < node - > importing_count ; i + + ) sdsfree ( node - > importing [ i ] ) ;
zfree ( node - > importing ) ;
}
2018-09-29 06:59:03 -04:00
/* If the node is not the reference node, that uses the address from
* config . hostip and config . hostport , then the node ip has been
* allocated by fetchClusterConfiguration , so it must be freed . */
if ( node - > ip & & strcmp ( node - > ip , config . hostip ) ! = 0 ) sdsfree ( node - > ip ) ;
2019-02-05 13:13:50 -05:00
if ( node - > redis_config ! = NULL ) freeRedisConfig ( node - > redis_config ) ;
2018-10-25 11:38:17 -04:00
zfree ( node - > slots ) ;
2018-09-24 11:25:28 -04:00
zfree ( node ) ;
}
static void freeClusterNodes ( ) {
int i = 0 ;
for ( ; i < config . cluster_node_count ; i + + ) {
clusterNode * n = config . cluster_nodes [ i ] ;
if ( n ) freeClusterNode ( n ) ;
}
zfree ( config . cluster_nodes ) ;
2019-02-05 13:13:50 -05:00
config . cluster_nodes = NULL ;
2018-09-24 11:25:28 -04:00
}
static clusterNode * * addClusterNode ( clusterNode * node ) {
int count = config . cluster_node_count + 1 ;
config . cluster_nodes = zrealloc ( config . cluster_nodes ,
count * sizeof ( * node ) ) ;
if ( ! config . cluster_nodes ) return NULL ;
config . cluster_nodes [ config . cluster_node_count + + ] = node ;
return config . cluster_nodes ;
}
static int fetchClusterConfiguration ( ) {
int success = 1 ;
redisContext * ctx = NULL ;
redisReply * reply = NULL ;
2020-07-10 09:37:11 -04:00
ctx = getRedisContext ( config . hostip , config . hostport , config . hostsocket ) ;
if ( ctx = = NULL ) {
2018-09-24 11:25:28 -04:00
exit ( 1 ) ;
}
clusterNode * firstNode = createClusterNode ( ( char * ) config . hostip ,
config . hostport ) ;
if ( ! firstNode ) { success = 0 ; goto cleanup ; }
reply = redisCommand ( ctx , " CLUSTER NODES " ) ;
success = ( reply ! = NULL ) ;
if ( ! success ) goto cleanup ;
success = ( reply - > type ! = REDIS_REPLY_ERROR ) ;
if ( ! success ) {
2019-01-18 11:31:32 -05:00
if ( config . hostsocket = = NULL ) {
fprintf ( stderr , " Cluster node %s:%d replied with error: \n %s \n " ,
config . hostip , config . hostport , reply - > str ) ;
} else {
fprintf ( stderr , " Cluster node %s replied with error: \n %s \n " ,
config . hostsocket , reply - > str ) ;
}
2018-09-24 11:25:28 -04:00
goto cleanup ;
}
char * lines = reply - > str , * p , * line ;
while ( ( p = strstr ( lines , " \n " ) ) ! = NULL ) {
* p = ' \0 ' ;
line = lines ;
lines = p + 1 ;
char * name = NULL , * addr = NULL , * flags = NULL , * master_id = NULL ;
int i = 0 ;
while ( ( p = strchr ( line , ' ' ) ) ! = NULL ) {
* p = ' \0 ' ;
char * token = line ;
line = p + 1 ;
switch ( i + + ) {
case 0 : name = token ; break ;
case 1 : addr = token ; break ;
case 2 : flags = token ; break ;
case 3 : master_id = token ; break ;
}
if ( i = = 8 ) break ; // Slots
}
if ( ! flags ) {
fprintf ( stderr , " Invalid CLUSTER NODES reply: missing flags. \n " ) ;
success = 0 ;
goto cleanup ;
}
int myself = ( strstr ( flags , " myself " ) ! = NULL ) ;
int is_replica = ( strstr ( flags , " slave " ) ! = NULL | |
( master_id ! = NULL & & master_id [ 0 ] ! = ' - ' ) ) ;
if ( is_replica ) continue ;
2019-01-18 11:31:32 -05:00
if ( addr = = NULL ) {
fprintf ( stderr , " Invalid CLUSTER NODES reply: missing addr. \n " ) ;
success = 0 ;
goto cleanup ;
}
2018-09-24 11:25:28 -04:00
clusterNode * node = NULL ;
2019-01-18 11:31:32 -05:00
char * ip = NULL ;
int port = 0 ;
char * paddr = strchr ( addr , ' : ' ) ;
if ( paddr ! = NULL ) {
2018-09-24 11:25:28 -04:00
* paddr = ' \0 ' ;
2019-01-18 11:31:32 -05:00
ip = addr ;
2018-09-24 11:25:28 -04:00
addr = paddr + 1 ;
/* If internal bus is specified, then just drop it. */
if ( ( paddr = strchr ( addr , ' @ ' ) ) ! = NULL ) * paddr = ' \0 ' ;
2019-01-18 11:31:32 -05:00
port = atoi ( addr ) ;
}
if ( myself ) {
node = firstNode ;
2020-12-17 03:22:13 -05:00
if ( ip ! = NULL & & strcmp ( node - > ip , ip ) ! = 0 ) {
node - > ip = sdsnew ( ip ) ;
2019-01-18 11:31:32 -05:00
node - > port = port ;
}
} else {
2018-09-29 06:59:03 -04:00
node = createClusterNode ( sdsnew ( ip ) , port ) ;
2018-09-24 11:25:28 -04:00
}
if ( node = = NULL ) {
success = 0 ;
goto cleanup ;
}
2019-01-18 11:31:32 -05:00
if ( name ! = NULL ) node - > name = sdsnew ( name ) ;
2018-09-24 11:25:28 -04:00
if ( i = = 8 ) {
int remaining = strlen ( line ) ;
while ( remaining > 0 ) {
p = strchr ( line , ' ' ) ;
if ( p = = NULL ) p = line + remaining ;
remaining - = ( p - line ) ;
char * slotsdef = line ;
* p = ' \0 ' ;
if ( remaining ) {
line = p + 1 ;
remaining - - ;
} else line = p ;
char * dash = NULL ;
if ( slotsdef [ 0 ] = = ' [ ' ) {
slotsdef + + ;
if ( ( p = strstr ( slotsdef , " ->- " ) ) ) { // Migrating
* p = ' \0 ' ;
p + = 3 ;
char * closing_bracket = strchr ( p , ' ] ' ) ;
if ( closing_bracket ) * closing_bracket = ' \0 ' ;
sds slot = sdsnew ( slotsdef ) ;
sds dst = sdsnew ( p ) ;
node - > migrating_count + = 2 ;
node - > migrating =
zrealloc ( node - > migrating ,
( node - > migrating_count * sizeof ( sds ) ) ) ;
node - > migrating [ node - > migrating_count - 2 ] =
slot ;
node - > migrating [ node - > migrating_count - 1 ] =
dst ;
} else if ( ( p = strstr ( slotsdef , " -<- " ) ) ) { //Importing
* p = ' \0 ' ;
p + = 3 ;
char * closing_bracket = strchr ( p , ' ] ' ) ;
if ( closing_bracket ) * closing_bracket = ' \0 ' ;
sds slot = sdsnew ( slotsdef ) ;
sds src = sdsnew ( p ) ;
node - > importing_count + = 2 ;
node - > importing = zrealloc ( node - > importing ,
( node - > importing_count * sizeof ( sds ) ) ) ;
node - > importing [ node - > importing_count - 2 ] =
slot ;
node - > importing [ node - > importing_count - 1 ] =
src ;
}
} else if ( ( dash = strchr ( slotsdef , ' - ' ) ) ! = NULL ) {
p = dash ;
int start , stop ;
* p = ' \0 ' ;
start = atoi ( slotsdef ) ;
stop = atoi ( p + 1 ) ;
while ( start < = stop ) {
int slot = start + + ;
2018-10-25 11:38:17 -04:00
node - > slots [ node - > slots_count + + ] = slot ;
2018-09-24 11:25:28 -04:00
}
} else if ( p > slotsdef ) {
int slot = atoi ( slotsdef ) ;
2018-10-25 11:38:17 -04:00
node - > slots [ node - > slots_count + + ] = slot ;
2018-09-24 11:25:28 -04:00
}
}
}
2018-10-25 11:38:17 -04:00
if ( node - > slots_count = = 0 ) {
2018-09-29 06:59:03 -04:00
printf ( " WARNING: master node %s:%d has no slots, skipping... \n " ,
node - > ip , node - > port ) ;
continue ;
}
if ( ! addClusterNode ( node ) ) {
success = 0 ;
goto cleanup ;
}
2018-09-24 11:25:28 -04:00
}
cleanup :
if ( ctx ) redisFree ( ctx ) ;
if ( ! success ) {
if ( config . cluster_nodes ) freeClusterNodes ( ) ;
}
if ( reply ) freeReplyObject ( reply ) ;
return success ;
}
2019-02-11 11:57:20 -05:00
/* Request the current cluster slots configuration by calling CLUSTER SLOTS
* and atomically update the slots after a successful reply . */
static int fetchClusterSlotsConfiguration ( client c ) {
UNUSED ( c ) ;
int success = 1 , is_fetching_slots = 0 , last_update = 0 ;
size_t i ;
atomicGet ( config . slots_last_update , last_update ) ;
if ( c - > slots_last_update < last_update ) {
c - > slots_last_update = last_update ;
return - 1 ;
}
redisReply * reply = NULL ;
atomicGetIncr ( config . is_fetching_slots , is_fetching_slots , 1 ) ;
if ( is_fetching_slots ) return - 1 ; //TODO: use other codes || errno ?
atomicSet ( config . is_fetching_slots , 1 ) ;
2021-04-28 02:51:07 -04:00
printf ( " WARNING: Cluster slots configuration changed, fetching new one... \n " ) ;
2019-02-11 11:57:20 -05:00
const char * errmsg = " Failed to update cluster slots configuration " ;
static dictType dtype = {
dictSdsHash , /* hash function */
NULL , /* key dup */
NULL , /* val dup */
dictSdsKeyCompare , /* key compare */
NULL , /* key destructor */
Limit the main db and expires dictionaries to expand (#7954)
As we know, redis may reject user's requests or evict some keys if
used memory is over maxmemory. Dictionaries expanding may make
things worse, some big dictionaries, such as main db and expires dict,
may eat huge memory at once for allocating a new big hash table and be
far more than maxmemory after expanding.
There are related issues: #4213 #4583
More details, when expand dict in redis, we will allocate a new big
ht[1] that generally is double of ht[0], The size of ht[1] will be
very big if ht[0] already is big. For db dict, if we have more than
64 million keys, we need to cost 1GB for ht[1] when dict expands.
If the sum of used memory and new hash table of dict needed exceeds
maxmemory, we shouldn't allow the dict to expand. Because, if we
enable keys eviction, we still couldn't add much more keys after
eviction and rehashing, what's worse, redis will keep less keys when
redis only remains a little memory for storing new hash table instead
of users' data. Moreover users can't write data in redis if disable
keys eviction.
What this commit changed ?
Add a new member function expandAllowed for dict type, it provide a way
for caller to allow expand or not. We expose two parameters for this
function: more memory needed for expanding and dict current load factor,
users can implement a function to make a decision by them.
For main db dict and expires dict type, these dictionaries may be very
big and cost huge memory for expanding, so we implement a judgement
function: we can stop dict to expand provisionally if used memory will
be over maxmemory after dict expands, but to guarantee the performance
of redis, we still allow dict to expand if dict load factor exceeds the
safe load factor.
Add test cases to verify we don't allow main db to expand when left
memory is not enough, so that avoid keys eviction.
Other changes:
For new hash table size when expand. Before this commit, the size is
that double used of dict and later _dictNextPower. Actually we aim to
control a dict load factor between 0.5 and 1.0. Now we replace *2 with
+1, since the first check is that used >= size, the outcome of before
will usually be the same as _dictNextPower(used+1). The only case where
it'll differ is when dict_can_resize is false during fork, so that later
the _dictNextPower(used*2) will cause the dict to jump to *4 (i.e.
_dictNextPower(1025*2) will return 4096).
Fix rehash test cases due to changing algorithm of new hash table size
when expand.
2020-12-06 04:53:04 -05:00
NULL , /* val destructor */
NULL /* allow to expand */
2019-02-11 11:57:20 -05:00
} ;
/* printf("[%d] fetchClusterSlotsConfiguration\n", c->thread_id); */
dict * masters = dictCreate ( & dtype , NULL ) ;
redisContext * ctx = NULL ;
for ( i = 0 ; i < ( size_t ) config . cluster_node_count ; i + + ) {
clusterNode * node = config . cluster_nodes [ i ] ;
assert ( node - > ip ! = NULL ) ;
assert ( node - > name ! = NULL ) ;
assert ( node - > port ) ;
/* Use first node as entry point to connect to. */
if ( ctx = = NULL ) {
2020-07-10 09:37:11 -04:00
ctx = getRedisContext ( node - > ip , node - > port , NULL ) ;
if ( ! ctx ) {
2019-02-11 11:57:20 -05:00
success = 0 ;
goto cleanup ;
}
}
if ( node - > updated_slots ! = NULL )
zfree ( node - > updated_slots ) ;
node - > updated_slots = NULL ;
node - > updated_slots_count = 0 ;
dictReplace ( masters , node - > name , node ) ;
}
reply = redisCommand ( ctx , " CLUSTER SLOTS " ) ;
if ( reply = = NULL | | reply - > type = = REDIS_REPLY_ERROR ) {
success = 0 ;
if ( reply )
fprintf ( stderr , " %s \n CLUSTER SLOTS ERROR: %s \n " , errmsg , reply - > str ) ;
goto cleanup ;
}
assert ( reply - > type = = REDIS_REPLY_ARRAY ) ;
for ( i = 0 ; i < reply - > elements ; i + + ) {
redisReply * r = reply - > element [ i ] ;
2019-04-08 11:39:22 -04:00
assert ( r - > type = = REDIS_REPLY_ARRAY ) ;
2019-02-11 11:57:20 -05:00
assert ( r - > elements > = 3 ) ;
int from , to , slot ;
from = r - > element [ 0 ] - > integer ;
to = r - > element [ 1 ] - > integer ;
redisReply * nr = r - > element [ 2 ] ;
assert ( nr - > type = = REDIS_REPLY_ARRAY & & nr - > elements > = 3 ) ;
assert ( nr - > element [ 2 ] - > str ! = NULL ) ;
sds name = sdsnew ( nr - > element [ 2 ] - > str ) ;
dictEntry * entry = dictFind ( masters , name ) ;
if ( entry = = NULL ) {
success = 0 ;
fprintf ( stderr , " %s: could not find node with ID %s in current "
" configuration. \n " , errmsg , name ) ;
if ( name ) sdsfree ( name ) ;
goto cleanup ;
}
sdsfree ( name ) ;
clusterNode * node = dictGetVal ( entry ) ;
if ( node - > updated_slots = = NULL )
node - > updated_slots = zcalloc ( CLUSTER_SLOTS * sizeof ( int ) ) ;
for ( slot = from ; slot < = to ; slot + + )
node - > updated_slots [ node - > updated_slots_count + + ] = slot ;
}
updateClusterSlotsConfiguration ( ) ;
cleanup :
freeReplyObject ( reply ) ;
redisFree ( ctx ) ;
dictRelease ( masters ) ;
atomicSet ( config . is_fetching_slots , 0 ) ;
return success ;
}
/* Atomically update the new slots configuration. */
static void updateClusterSlotsConfiguration ( ) {
pthread_mutex_lock ( & config . is_updating_slots_mutex ) ;
atomicSet ( config . is_updating_slots , 1 ) ;
int i ;
for ( i = 0 ; i < config . cluster_node_count ; i + + ) {
clusterNode * node = config . cluster_nodes [ i ] ;
if ( node - > updated_slots ! = NULL ) {
int * oldslots = node - > slots ;
node - > slots = node - > updated_slots ;
node - > slots_count = node - > updated_slots_count ;
node - > current_slot_index = 0 ;
node - > updated_slots = NULL ;
node - > updated_slots_count = 0 ;
zfree ( oldslots ) ;
}
}
atomicSet ( config . is_updating_slots , 0 ) ;
atomicIncr ( config . slots_last_update , 1 ) ;
pthread_mutex_unlock ( & config . is_updating_slots_mutex ) ;
}
2020-05-18 06:18:20 -04:00
/* Generate random data for redis benchmark. See #7196. */
static void genBenchmarkRandomData ( char * data , int count ) {
static uint32_t state = 1234 ;
int i = 0 ;
while ( count - - ) {
state = ( state * 1103515245 + 12345 ) ;
data [ i + + ] = ' 0 ' + ( ( state > > 16 ) & 63 ) ;
}
}
2011-05-31 20:19:11 -04:00
/* Returns number of consumed options. */
int parseOptions ( int argc , const char * * argv ) {
2009-03-22 05:30:00 -04:00
int i ;
2011-05-31 20:19:11 -04:00
int lastarg ;
int exit_status = 1 ;
2009-03-22 05:30:00 -04:00
for ( i = 1 ; i < argc ; i + + ) {
2011-05-31 20:19:11 -04:00
lastarg = ( i = = ( argc - 1 ) ) ;
if ( ! strcmp ( argv [ i ] , " -c " ) ) {
if ( lastarg ) goto invalid ;
config . numclients = atoi ( argv [ + + i ] ) ;
2020-10-26 02:04:59 -04:00
} else if ( ! strcmp ( argv [ i ] , " -v " ) | | ! strcmp ( argv [ i ] , " --version " ) ) {
sds version = benchmarkVersion ( ) ;
printf ( " redis-benchmark %s \n " , version ) ;
sdsfree ( version ) ;
exit ( 0 ) ;
2011-05-31 20:19:11 -04:00
} else if ( ! strcmp ( argv [ i ] , " -n " ) ) {
if ( lastarg ) goto invalid ;
config . requests = atoi ( argv [ + + i ] ) ;
} else if ( ! strcmp ( argv [ i ] , " -k " ) ) {
if ( lastarg ) goto invalid ;
config . keepalive = atoi ( argv [ + + i ] ) ;
} else if ( ! strcmp ( argv [ i ] , " -h " ) ) {
if ( lastarg ) goto invalid ;
config . hostip = strdup ( argv [ + + i ] ) ;
} else if ( ! strcmp ( argv [ i ] , " -p " ) ) {
if ( lastarg ) goto invalid ;
config . hostport = atoi ( argv [ + + i ] ) ;
} else if ( ! strcmp ( argv [ i ] , " -s " ) ) {
if ( lastarg ) goto invalid ;
config . hostsocket = strdup ( argv [ + + i ] ) ;
2013-05-09 10:30:20 -04:00
} else if ( ! strcmp ( argv [ i ] , " -a " ) ) {
if ( lastarg ) goto invalid ;
config . auth = strdup ( argv [ + + i ] ) ;
2020-05-04 11:09:21 -04:00
} else if ( ! strcmp ( argv [ i ] , " --user " ) ) {
if ( lastarg ) goto invalid ;
config . user = argv [ + + i ] ;
2011-05-31 20:19:11 -04:00
} else if ( ! strcmp ( argv [ i ] , " -d " ) ) {
if ( lastarg ) goto invalid ;
config . datasize = atoi ( argv [ + + i ] ) ;
2009-03-22 05:30:00 -04:00
if ( config . datasize < 1 ) config . datasize = 1 ;
2011-11-03 10:53:40 -04:00
if ( config . datasize > 1024 * 1024 * 1024 ) config . datasize = 1024 * 1024 * 1024 ;
2012-02-23 09:02:43 -05:00
} else if ( ! strcmp ( argv [ i ] , " -P " ) ) {
if ( lastarg ) goto invalid ;
config . pipeline = atoi ( argv [ + + i ] ) ;
if ( config . pipeline < = 0 ) config . pipeline = 1 ;
2011-05-31 20:19:11 -04:00
} else if ( ! strcmp ( argv [ i ] , " -r " ) ) {
if ( lastarg ) goto invalid ;
2019-03-07 05:30:09 -05:00
const char * next = argv [ + + i ] , * p = next ;
if ( * p = = ' - ' ) {
p + + ;
if ( * p < ' 0 ' | | * p > ' 9 ' ) goto invalid ;
}
2009-05-09 03:25:59 -04:00
config . randomkeys = 1 ;
2019-04-23 08:08:14 -04:00
config . randomkeys_keyspacelen = atoi ( next ) ;
2009-05-10 18:36:12 -04:00
if ( config . randomkeys_keyspacelen < 0 )
config . randomkeys_keyspacelen = 0 ;
2009-03-22 05:30:00 -04:00
} else if ( ! strcmp ( argv [ i ] , " -q " ) ) {
config . quiet = 1 ;
2011-11-04 09:49:24 -04:00
} else if ( ! strcmp ( argv [ i ] , " --csv " ) ) {
config . csv = 1 ;
2009-03-22 05:30:00 -04:00
} else if ( ! strcmp ( argv [ i ] , " -l " ) ) {
config . loop = 1 ;
2009-11-23 12:50:39 -05:00
} else if ( ! strcmp ( argv [ i ] , " -I " ) ) {
config . idlemode = 1 ;
2016-07-12 05:22:41 -04:00
} else if ( ! strcmp ( argv [ i ] , " -e " ) ) {
2021-04-28 02:51:07 -04:00
printf ( " WARNING: -e option has been deprecated. "
" We now immediatly exit on error to avoid false results. \n " ) ;
2011-11-07 05:29:37 -05:00
} else if ( ! strcmp ( argv [ i ] , " -t " ) ) {
if ( lastarg ) goto invalid ;
/* We get the list of tests to run as a string in the form
* get , set , lrange , . . . , test_N . Then we add a comma before and
* after the string in order to make sure that searching
* for " ,testname, " will always get a match if the test is
* enabled . */
config . tests = sdsnew ( " , " ) ;
config . tests = sdscat ( config . tests , ( char * ) argv [ + + i ] ) ;
config . tests = sdscat ( config . tests , " , " ) ;
sdstolower ( config . tests ) ;
2013-08-06 12:50:54 -04:00
} else if ( ! strcmp ( argv [ i ] , " --dbnum " ) ) {
if ( lastarg ) goto invalid ;
config . dbnum = atoi ( argv [ + + i ] ) ;
config . dbnumstr = sdsfromlonglong ( config . dbnum ) ;
2019-01-31 13:44:17 -05:00
} else if ( ! strcmp ( argv [ i ] , " --precision " ) ) {
if ( lastarg ) goto invalid ;
config . precision = atoi ( argv [ + + i ] ) ;
2020-08-25 14:21:29 -04:00
if ( config . precision < 0 ) config . precision = DEFAULT_LATENCY_PRECISION ;
2019-01-31 13:44:17 -05:00
if ( config . precision > MAX_LATENCY_PRECISION ) config . precision = MAX_LATENCY_PRECISION ;
2018-07-31 04:45:00 -04:00
} else if ( ! strcmp ( argv [ i ] , " --threads " ) ) {
if ( lastarg ) goto invalid ;
config . num_threads = atoi ( argv [ + + i ] ) ;
if ( config . num_threads > MAX_THREADS ) {
printf ( " WARNING: too many threads, limiting threads to %d. \n " ,
MAX_THREADS ) ;
config . num_threads = MAX_THREADS ;
} else if ( config . num_threads < 0 ) config . num_threads = 0 ;
2018-09-24 11:25:28 -04:00
} else if ( ! strcmp ( argv [ i ] , " --cluster " ) ) {
config . cluster_mode = 1 ;
2019-07-22 12:45:47 -04:00
} else if ( ! strcmp ( argv [ i ] , " --enable-tracking " ) ) {
config . enable_tracking = 1 ;
2011-05-31 20:19:11 -04:00
} else if ( ! strcmp ( argv [ i ] , " --help " ) ) {
exit_status = 0 ;
goto usage ;
2020-10-28 02:00:54 -04:00
# ifdef USE_OPENSSL
} else if ( ! strcmp ( argv [ i ] , " --tls " ) ) {
config . tls = 1 ;
} else if ( ! strcmp ( argv [ i ] , " --sni " ) ) {
if ( lastarg ) goto invalid ;
config . sslconfig . sni = strdup ( argv [ + + i ] ) ;
} else if ( ! strcmp ( argv [ i ] , " --cacertdir " ) ) {
if ( lastarg ) goto invalid ;
config . sslconfig . cacertdir = strdup ( argv [ + + i ] ) ;
} else if ( ! strcmp ( argv [ i ] , " --cacert " ) ) {
if ( lastarg ) goto invalid ;
config . sslconfig . cacert = strdup ( argv [ + + i ] ) ;
2021-02-07 05:36:56 -05:00
} else if ( ! strcmp ( argv [ i ] , " --insecure " ) ) {
config . sslconfig . skip_cert_verify = 1 ;
2020-10-28 02:00:54 -04:00
} else if ( ! strcmp ( argv [ i ] , " --cert " ) ) {
if ( lastarg ) goto invalid ;
config . sslconfig . cert = strdup ( argv [ + + i ] ) ;
} else if ( ! strcmp ( argv [ i ] , " --key " ) ) {
if ( lastarg ) goto invalid ;
config . sslconfig . key = strdup ( argv [ + + i ] ) ;
2020-11-04 07:49:15 -05:00
} else if ( ! strcmp ( argv [ i ] , " --tls-ciphers " ) ) {
if ( lastarg ) goto invalid ;
config . sslconfig . ciphers = strdup ( argv [ + + i ] ) ;
# ifdef TLS1_3_VERSION
} else if ( ! strcmp ( argv [ i ] , " --tls-ciphersuites " ) ) {
if ( lastarg ) goto invalid ;
config . sslconfig . ciphersuites = strdup ( argv [ + + i ] ) ;
# endif
2020-10-28 02:00:54 -04:00
# endif
2009-03-22 05:30:00 -04:00
} else {
2011-05-31 20:19:11 -04:00
/* Assume the user meant to provide an option when the arg starts
* with a dash . We ' re done otherwise and should use the remainder
* as the command and arguments for running the benchmark . */
if ( argv [ i ] [ 0 ] = = ' - ' ) goto invalid ;
return i ;
2009-03-22 05:30:00 -04:00
}
}
2011-05-31 20:19:11 -04:00
return i ;
invalid :
printf ( " Invalid option \" %s \" or option argument missing \n \n " , argv [ i ] ) ;
usage :
2011-11-07 05:29:37 -05:00
printf (
2016-11-10 21:33:48 -05:00
" Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests>] [-k <boolean>] \n \n "
2011-11-07 05:29:37 -05:00
" -h <hostname> Server hostname (default 127.0.0.1) \n "
" -p <port> Server port (default 6379) \n "
" -s <socket> Server socket (overrides host and port) \n "
2013-05-09 10:30:20 -04:00
" -a <password> Password for Redis Auth \n "
2020-05-04 11:09:21 -04:00
" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a. \n "
2011-11-07 05:29:37 -05:00
" -c <clients> Number of parallel connections (default 50) \n "
2014-11-28 03:23:39 -05:00
" -n <requests> Total number of requests (default 100000) \n "
2017-01-27 22:20:23 -05:00
" -d <size> Data size of SET/GET value in bytes (default 3) \n "
" --dbnum <db> SELECT the specified db number (default 0) \n "
2019-03-01 12:15:44 -05:00
" --threads <num> Enable multi-thread mode. \n "
" --cluster Enable cluster mode. \n "
2019-07-22 12:45:47 -04:00
" --enable-tracking Send CLIENT TRACKING on before starting benchmark. \n "
2011-11-07 05:29:37 -05:00
" -k <boolean> 1=keep alive 0=reconnect (default 1) \n "
2020-08-08 11:08:27 -04:00
" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD, \n "
" random members and scores for ZADD. \n "
2013-10-28 13:13:39 -04:00
" Using this option the benchmark will expand the string __rand_int__ \n "
" inside an argument with a 12 digits number in the specified range \n "
" from 0 to keyspacelen-1. The substitution changes every time a command \n "
" is executed. Default tests use this to hit random keys in the \n "
" specified range. \n "
2012-02-23 09:02:43 -05:00
" -P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline). \n "
2011-11-07 05:29:37 -05:00
" -q Quiet. Just show query/sec values \n "
2019-01-31 13:44:17 -05:00
" --precision Number of decimal places to display in latency output (default 0) \n "
2011-11-07 05:29:37 -05:00
" --csv Output in CSV format \n "
" -l Loop. Run the tests forever \n "
" -t <tests> Only run the comma separated list of tests. The test \n "
" names are the same as the ones produced as output. \n "
2020-10-26 02:04:59 -04:00
" -I Idle mode. Just open N idle connections and wait. \n "
2020-10-28 02:00:54 -04:00
# ifdef USE_OPENSSL
" --tls Establish a secure TLS connection. \n "
" --sni <host> Server name indication for TLS. \n "
" --cacert <file> CA Certificate file to verify with. \n "
" --cacertdir <dir> Directory where trusted CA certificates are stored. \n "
" If neither cacert nor cacertdir are specified, the default \n "
" system-wide trusted root certs configuration will apply. \n "
2021-02-07 05:36:56 -05:00
" --insecure Allow insecure TLS connection by skipping cert validation. \n "
2020-10-28 02:00:54 -04:00
" --cert <file> Client certificate to authenticate with. \n "
" --key <file> Private key file to authenticate with. \n "
2020-11-04 07:49:15 -05:00
" --tls-ciphers <list> Sets the list of prefered ciphers (TLSv1.2 and below) \n "
" in order of preference from highest to lowest separated by colon ( \" : \" ). \n "
" See the ciphers(1ssl) manpage for more information about the syntax of this string. \n "
# ifdef TLS1_3_VERSION
" --tls-ciphersuites <list> Sets the list of prefered ciphersuites (TLSv1.3) \n "
" in order of preference from highest to lowest separated by colon ( \" : \" ). \n "
" See the ciphers(1ssl) manpage for more information about the syntax of this string, \n "
" and specifically for TLSv1.3 ciphersuites. \n "
# endif
2020-10-28 02:00:54 -04:00
# endif
2020-10-26 02:04:59 -04:00
" --help Output this help and exit. \n "
" --version Output version and exit. \n \n "
2011-11-07 05:29:37 -05:00
" Examples: \n \n "
" Run the benchmark with the default configuration against 127.0.0.1:6379: \n "
" $ redis-benchmark \n \n "
" Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1: \n "
" $ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20 \n \n "
" Fill 127.0.0.1:6379 with about 1 million keys only using the SET test: \n "
" $ redis-benchmark -t set -n 1000000 -r 100000000 \n \n "
" Benchmark 127.0.0.1:6379 for a few commands producing CSV output: \n "
" $ redis-benchmark -t ping,set,get -n 100000 --csv \n \n "
2013-08-08 10:42:08 -04:00
" Benchmark a specific command line: \n "
" $ redis-benchmark -r 10000 -n 10000 eval 'return redis.call( \" ping \" )' 0 \n \n "
2012-01-31 05:43:32 -05:00
" Fill a list with 10000 random elements: \n "
2013-08-08 10:42:08 -04:00
" $ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__ \n \n "
" On user specified command lines __rand_int__ is replaced with a random integer \n "
" with a range of values selected by the -r option. \n "
2011-11-07 05:29:37 -05:00
) ;
2011-05-31 20:19:11 -04:00
exit ( exit_status ) ;
2009-03-22 05:30:00 -04:00
}
2010-08-30 05:25:02 -04:00
int showThroughput ( struct aeEventLoop * eventLoop , long long id , void * clientData ) {
2015-07-27 03:41:48 -04:00
UNUSED ( eventLoop ) ;
UNUSED ( id ) ;
UNUSED ( clientData ) ;
2018-07-31 04:45:00 -04:00
int liveclients = 0 ;
int requests_finished = 0 ;
2020-08-25 14:21:29 -04:00
int previous_requests_finished = 0 ;
long long current_tick = mstime ( ) ;
2019-01-18 11:31:32 -05:00
atomicGet ( config . liveclients , liveclients ) ;
atomicGet ( config . requests_finished , requests_finished ) ;
2020-08-25 14:21:29 -04:00
atomicGet ( config . previous_requests_finished , previous_requests_finished ) ;
2018-07-31 04:45:00 -04:00
if ( liveclients = = 0 & & requests_finished ! = config . requests ) {
2014-08-13 10:58:54 -04:00
fprintf ( stderr , " All clients disconnected... aborting. \n " ) ;
2014-07-04 11:52:14 -04:00
exit ( 1 ) ;
2014-12-11 09:16:20 -05:00
}
2019-01-17 11:40:15 -05:00
if ( config . num_threads & & requests_finished > = config . requests ) {
aeStop ( eventLoop ) ;
return AE_NOMORE ;
}
2011-11-04 09:49:24 -04:00
if ( config . csv ) return 250 ;
2014-10-27 02:02:52 -04:00
if ( config . idlemode = = 1 ) {
printf ( " clients: %d \r " , config . liveclients ) ;
fflush ( stdout ) ;
return 250 ;
}
2020-08-25 14:21:29 -04:00
const float dt = ( float ) ( current_tick - config . start ) / 1000.0 ;
const float rps = ( float ) requests_finished / dt ;
const float instantaneous_dt = ( float ) ( current_tick - config . previous_tick ) / 1000.0 ;
const float instantaneous_rps = ( float ) ( requests_finished - previous_requests_finished ) / instantaneous_dt ;
config . previous_tick = current_tick ;
atomicSet ( config . previous_requests_finished , requests_finished ) ;
2021-03-09 07:50:04 -05:00
int printed_bytes = printf ( " %s: rps=%.1f (overall: %.1f) avg_msec=%.3f (overall: %.3f) \r " , config . title , instantaneous_rps , rps , hdr_mean ( config . current_sec_latency_histogram ) / 1000.0f , hdr_mean ( config . latency_histogram ) / 1000.0f ) ;
if ( printed_bytes > config . last_printed_bytes ) {
config . last_printed_bytes = printed_bytes ;
}
2020-08-25 14:21:29 -04:00
hdr_reset ( config . current_sec_latency_histogram ) ;
2010-08-30 05:25:02 -04:00
fflush ( stdout ) ;
return 250 ; /* every 250ms */
}
2011-11-07 05:29:37 -05:00
/* Return true if the named test was selected using the -t command line
* switch , or if all the tests are selected ( no - t passed by user ) . */
int test_is_selected ( char * name ) {
char buf [ 256 ] ;
int l = strlen ( name ) ;
if ( config . tests = = NULL ) return 1 ;
buf [ 0 ] = ' , ' ;
memcpy ( buf + 1 , name , l ) ;
buf [ l + 1 ] = ' , ' ;
buf [ l + 2 ] = ' \0 ' ;
return strstr ( config . tests , buf ) ! = NULL ;
}
2011-05-31 20:15:42 -04:00
int main ( int argc , const char * * argv ) {
2010-12-16 17:41:58 -05:00
int i ;
2020-10-20 12:52:05 -04:00
char * data , * cmd , * tag ;
2011-05-31 20:19:11 -04:00
int len ;
2009-03-22 05:30:00 -04:00
client c ;
2020-12-12 20:27:35 -05:00
srandom ( time ( NULL ) ^ getpid ( ) ) ;
2020-12-23 08:52:07 -05:00
init_genrand64 ( ustime ( ) ^ getpid ( ) ) ;
2009-03-22 05:30:00 -04:00
signal ( SIGHUP , SIG_IGN ) ;
signal ( SIGPIPE , SIG_IGN ) ;
2021-02-07 05:36:56 -05:00
memset ( & config . sslconfig , 0 , sizeof ( config . sslconfig ) ) ;
2009-03-22 05:30:00 -04:00
config . numclients = 50 ;
2014-11-28 03:23:39 -05:00
config . requests = 100000 ;
2009-03-22 05:30:00 -04:00
config . liveclients = 0 ;
2011-12-15 05:42:40 -05:00
config . el = aeCreateEventLoop ( 1024 * 10 ) ;
2010-08-30 05:25:02 -04:00
aeCreateTimeEvent ( config . el , 1 , showThroughput , NULL , NULL ) ;
2009-03-22 05:30:00 -04:00
config . keepalive = 1 ;
config . datasize = 3 ;
2012-02-23 09:02:43 -05:00
config . pipeline = 1 ;
2009-05-09 03:25:59 -04:00
config . randomkeys = 0 ;
2009-05-10 18:36:12 -04:00
config . randomkeys_keyspacelen = 0 ;
2009-03-22 05:30:00 -04:00
config . quiet = 0 ;
2011-11-04 09:49:24 -04:00
config . csv = 0 ;
2009-03-22 05:30:00 -04:00
config . loop = 0 ;
2009-11-23 12:50:39 -05:00
config . idlemode = 0 ;
2009-03-22 05:30:00 -04:00
config . clients = listCreate ( ) ;
config . hostip = " 127.0.0.1 " ;
config . hostport = 6379 ;
2010-08-01 16:55:24 -04:00
config . hostsocket = NULL ;
2011-11-07 05:29:37 -05:00
config . tests = NULL ;
2013-08-06 12:50:54 -04:00
config . dbnum = 0 ;
2013-05-09 10:30:20 -04:00
config . auth = NULL ;
2020-08-25 14:21:29 -04:00
config . precision = DEFAULT_LATENCY_PRECISION ;
2018-07-31 04:45:00 -04:00
config . num_threads = 0 ;
config . threads = NULL ;
2018-09-24 11:25:28 -04:00
config . cluster_mode = 0 ;
config . cluster_node_count = 0 ;
config . cluster_nodes = NULL ;
2019-02-05 13:13:50 -05:00
config . redis_config = NULL ;
2019-02-11 11:57:20 -05:00
config . is_fetching_slots = 0 ;
config . is_updating_slots = 0 ;
config . slots_last_update = 0 ;
2019-07-22 12:45:47 -04:00
config . enable_tracking = 0 ;
2009-03-22 05:30:00 -04:00
2011-05-31 20:19:11 -04:00
i = parseOptions ( argc , argv ) ;
argc - = i ;
argv + = i ;
2020-10-20 12:52:05 -04:00
tag = " " ;
2020-10-28 02:00:54 -04:00
# ifdef USE_OPENSSL
if ( config . tls ) {
cliSecureInit ( ) ;
}
# endif
2018-09-24 11:25:28 -04:00
if ( config . cluster_mode ) {
2020-10-20 12:52:05 -04:00
// We only include the slot placeholder {tag} if cluster mode is enabled
tag = " :{tag} " ;
2018-09-24 11:25:28 -04:00
/* Fetch cluster configuration. */
if ( ! fetchClusterConfiguration ( ) | | ! config . cluster_nodes ) {
if ( ! config . hostsocket ) {
fprintf ( stderr , " Failed to fetch cluster configuration from "
" %s:%d \n " , config . hostip , config . hostport ) ;
} else {
fprintf ( stderr , " Failed to fetch cluster configuration from "
" %s \n " , config . hostsocket ) ;
}
exit ( 1 ) ;
}
if ( config . cluster_node_count < = 1 ) {
fprintf ( stderr , " Invalid cluster: %d node(s). \n " ,
config . cluster_node_count ) ;
exit ( 1 ) ;
}
printf ( " Cluster has %d master nodes: \n \n " , config . cluster_node_count ) ;
int i = 0 ;
for ( ; i < config . cluster_node_count ; i + + ) {
clusterNode * node = config . cluster_nodes [ i ] ;
if ( ! node ) {
fprintf ( stderr , " Invalid cluster node #%d \n " , i ) ;
exit ( 1 ) ;
}
2019-02-05 13:13:50 -05:00
printf ( " Master %d: " , i ) ;
2018-09-24 11:25:28 -04:00
if ( node - > name ) printf ( " %s " , node - > name ) ;
printf ( " %s:%d \n " , node - > ip , node - > port ) ;
2019-02-05 13:13:50 -05:00
node - > redis_config = getRedisConfig ( node - > ip , node - > port , NULL ) ;
2019-06-05 10:34:55 -04:00
if ( node - > redis_config = = NULL ) {
fprintf ( stderr , " WARN: could not fetch node CONFIG %s:%d \n " ,
node - > ip , node - > port ) ;
}
2018-09-24 11:25:28 -04:00
}
2019-02-05 13:13:50 -05:00
printf ( " \n " ) ;
2019-01-17 11:40:15 -05:00
/* Automatically set thread number to node count if not specified
* by the user . */
if ( config . num_threads = = 0 )
config . num_threads = config . cluster_node_count ;
2019-02-05 13:13:50 -05:00
} else {
config . redis_config =
getRedisConfig ( config . hostip , config . hostport , config . hostsocket ) ;
2021-04-25 07:00:35 -04:00
if ( config . redis_config = = NULL ) {
2019-06-05 10:34:55 -04:00
fprintf ( stderr , " WARN: could not fetch server CONFIG \n " ) ;
2021-04-25 07:00:35 -04:00
}
2018-09-24 11:25:28 -04:00
}
2018-07-31 04:45:00 -04:00
if ( config . num_threads > 0 ) {
pthread_mutex_init ( & ( config . liveclients_mutex ) , NULL ) ;
2019-02-11 11:57:20 -05:00
pthread_mutex_init ( & ( config . is_updating_slots_mutex ) , NULL ) ;
2018-07-31 04:45:00 -04:00
}
2009-03-22 05:30:00 -04:00
if ( config . keepalive = = 0 ) {
2009-10-03 04:54:27 -04:00
printf ( " WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' for Linux and 'sudo sysctl -w net.inet.tcp.msl=1000' for Mac OS X in order to use a lot of clients/requests \n " ) ;
2009-03-22 05:30:00 -04:00
}
2009-11-23 12:50:39 -05:00
if ( config . idlemode ) {
printf ( " Creating %d idle connections and waiting forever (Ctrl+C when done) \n " , config . numclients ) ;
2019-03-07 05:14:03 -05:00
int thread_id = - 1 , use_threads = ( config . num_threads > 0 ) ;
if ( use_threads ) {
thread_id = 0 ;
initBenchmarkThreads ( ) ;
}
c = createClient ( " " , 0 , NULL , thread_id ) ; /* will never receive a reply */
2009-11-23 12:50:39 -05:00
createMissingClients ( c ) ;
2019-03-07 05:14:03 -05:00
if ( use_threads ) startBenchmarkThreads ( ) ;
else aeMain ( config . el ) ;
2009-11-23 12:50:39 -05:00
/* and will wait for every */
}
2020-08-25 14:21:29 -04:00
if ( config . csv ) {
printf ( " \" test \" , \" rps \" , \" avg_latency_ms \" , \" min_latency_ms \" , \" p50_latency_ms \" , \" p95_latency_ms \" , \" p99_latency_ms \" , \" max_latency_ms \" \n " ) ;
}
2011-05-31 20:19:11 -04:00
/* Run benchmark with command in the remainder of the arguments. */
if ( argc ) {
sds title = sdsnew ( argv [ 0 ] ) ;
for ( i = 1 ; i < argc ; i + + ) {
title = sdscatlen ( title , " " , 1 ) ;
2011-05-31 20:38:39 -04:00
title = sdscatlen ( title , ( char * ) argv [ i ] , strlen ( argv [ i ] ) ) ;
2011-05-31 20:19:11 -04:00
}
do {
len = redisFormatCommandArgv ( & cmd , argc , argv , NULL ) ;
2020-08-25 14:21:29 -04:00
// adjust the datasize to the parsed command
config . datasize = len ;
2011-05-31 20:19:11 -04:00
benchmark ( title , cmd , len ) ;
free ( cmd ) ;
} while ( config . loop ) ;
2010-12-16 18:19:32 -05:00
2019-02-05 13:13:50 -05:00
if ( config . redis_config ! = NULL ) freeRedisConfig ( config . redis_config ) ;
2011-05-31 20:19:11 -04:00
return 0 ;
}
/* Run default benchmark suite. */
2014-11-27 20:50:17 -05:00
data = zmalloc ( config . datasize + 1 ) ;
2011-05-31 20:19:11 -04:00
do {
2020-05-18 06:18:20 -04:00
genBenchmarkRandomData ( data , config . datasize ) ;
2010-12-16 17:41:58 -05:00
data [ config . datasize ] = ' \0 ' ;
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " ping_inline " ) | | test_is_selected ( " ping " ) )
benchmark ( " PING_INLINE " , " PING \r \n " , 6 ) ;
2010-02-06 07:39:07 -05:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " ping_mbulk " ) | | test_is_selected ( " ping " ) ) {
len = redisFormatCommand ( & cmd , " PING " ) ;
2020-12-02 06:17:25 -05:00
benchmark ( " PING_MBULK " , cmd , len ) ;
2011-11-07 05:29:37 -05:00
free ( cmd ) ;
}
2010-12-22 12:31:33 -05:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " set " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " SET key%s:__rand_int__ %s " , tag , data ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " SET " , cmd , len ) ;
free ( cmd ) ;
2010-12-22 12:39:52 -05:00
}
2010-10-15 12:17:06 -04:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " get " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " GET key%s:__rand_int__ " , tag ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " GET " , cmd , len ) ;
free ( cmd ) ;
}
2009-03-22 05:30:00 -04:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " incr " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " INCR counter%s:__rand_int__ " , tag ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " INCR " , cmd , len ) ;
free ( cmd ) ;
}
2009-03-22 05:30:00 -04:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " lpush " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " LPUSH mylist%s %s " , tag , data ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " LPUSH " , cmd , len ) ;
free ( cmd ) ;
}
2009-03-22 05:30:00 -04:00
2014-11-25 12:19:58 -05:00
if ( test_is_selected ( " rpush " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " RPUSH mylist%s %s " , tag , data ) ;
2014-11-25 12:19:58 -05:00
benchmark ( " RPUSH " , cmd , len ) ;
free ( cmd ) ;
}
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " lpop " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " LPOP mylist%s " , tag ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " LPOP " , cmd , len ) ;
free ( cmd ) ;
}
2009-03-22 05:30:00 -04:00
2014-11-25 12:19:58 -05:00
if ( test_is_selected ( " rpop " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " RPOP mylist%s " , tag ) ;
2014-11-25 12:19:58 -05:00
benchmark ( " RPOP " , cmd , len ) ;
free ( cmd ) ;
}
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " sadd " ) ) {
len = redisFormatCommand ( & cmd ,
2020-10-20 12:52:05 -04:00
" SADD myset%s element:__rand_int__ " , tag ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " SADD " , cmd , len ) ;
2017-06-19 03:41:11 -04:00
free ( cmd ) ;
}
if ( test_is_selected ( " hset " ) ) {
len = redisFormatCommand ( & cmd ,
2020-10-20 12:52:05 -04:00
" HSET myhash%s element:__rand_int__ %s " , tag , data ) ;
2017-06-19 03:41:11 -04:00
benchmark ( " HSET " , cmd , len ) ;
2011-11-07 05:29:37 -05:00
free ( cmd ) ;
}
2009-03-22 05:30:00 -04:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " spop " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " SPOP myset%s " , tag ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " SPOP " , cmd , len ) ;
free ( cmd ) ;
}
2010-03-04 17:05:12 -05:00
2020-08-06 03:36:28 -04:00
if ( test_is_selected ( " zadd " ) ) {
char * score = " 0 " ;
if ( config . randomkeys ) score = " __rand_int__ " ;
len = redisFormatCommand ( & cmd ,
2020-10-20 12:52:05 -04:00
" ZADD myzset%s %s element:__rand_int__ " , tag , score ) ;
2020-08-06 03:36:28 -04:00
benchmark ( " ZADD " , cmd , len ) ;
free ( cmd ) ;
}
2020-08-08 11:08:27 -04:00
if ( test_is_selected ( " zpopmin " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " ZPOPMIN myzset%s " , tag ) ;
2020-08-08 11:08:27 -04:00
benchmark ( " ZPOPMIN " , cmd , len ) ;
2020-08-06 03:36:28 -04:00
free ( cmd ) ;
}
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " lrange " ) | |
test_is_selected ( " lrange_100 " ) | |
test_is_selected ( " lrange_300 " ) | |
test_is_selected ( " lrange_500 " ) | |
test_is_selected ( " lrange_600 " ) )
{
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " LPUSH mylist%s %s " , tag , data ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " LPUSH (needed to benchmark LRANGE) " , cmd , len ) ;
free ( cmd ) ;
}
2010-03-04 17:05:12 -05:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " lrange " ) | | test_is_selected ( " lrange_100 " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " LRANGE mylist%s 0 99 " , tag ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " LRANGE_100 (first 100 elements) " , cmd , len ) ;
free ( cmd ) ;
}
2009-11-17 10:57:35 -05:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " lrange " ) | | test_is_selected ( " lrange_300 " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " LRANGE mylist%s 0 299 " , tag ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " LRANGE_300 (first 300 elements) " , cmd , len ) ;
free ( cmd ) ;
}
2009-11-17 10:57:35 -05:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " lrange " ) | | test_is_selected ( " lrange_500 " ) ) {
2021-04-28 09:03:24 -04:00
len = redisFormatCommand ( & cmd , " LRANGE mylist%s 0 499 " , tag ) ;
benchmark ( " LRANGE_500 (first 500 elements) " , cmd , len ) ;
2011-11-07 05:29:37 -05:00
free ( cmd ) ;
}
2009-11-18 13:02:20 -05:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " lrange " ) | | test_is_selected ( " lrange_600 " ) ) {
2020-10-20 12:52:05 -04:00
len = redisFormatCommand ( & cmd , " LRANGE mylist%s 0 599 " , tag ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " LRANGE_600 (first 600 elements) " , cmd , len ) ;
free ( cmd ) ;
}
2009-11-18 13:41:25 -05:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " mset " ) ) {
2020-10-20 12:52:05 -04:00
const char * cmd_argv [ 21 ] ;
cmd_argv [ 0 ] = " MSET " ;
sds key_placeholder = sdscatprintf ( sdsnew ( " " ) , " key%s:__rand_int__ " , tag ) ;
2011-11-07 05:29:37 -05:00
for ( i = 1 ; i < 21 ; i + = 2 ) {
2020-10-20 12:52:05 -04:00
cmd_argv [ i ] = key_placeholder ;
cmd_argv [ i + 1 ] = data ;
2011-11-07 05:29:37 -05:00
}
2020-10-20 12:52:05 -04:00
len = redisFormatCommandArgv ( & cmd , 21 , cmd_argv , NULL ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " MSET (10 keys) " , cmd , len ) ;
free ( cmd ) ;
2020-10-20 12:52:05 -04:00
sdsfree ( key_placeholder ) ;
2011-11-07 05:29:37 -05:00
}
2009-11-18 13:41:25 -05:00
2011-11-04 09:49:24 -04:00
if ( ! config . csv ) printf ( " \n " ) ;
2009-03-22 05:30:00 -04:00
} while ( config . loop ) ;
2021-04-28 09:03:24 -04:00
zfree ( data ) ;
2019-02-05 13:13:50 -05:00
if ( config . redis_config ! = NULL ) freeRedisConfig ( config . redis_config ) ;
2009-03-22 05:30:00 -04:00
return 0 ;
}