2019-02-23 19:12:00 -05: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"
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
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"
2010-11-04 08:37:05 -04:00
# include "hiredis.h"
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"
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
2019-01-31 13:44:17 -05:00
# define MAX_LATENCY_PRECISION 3
2019-01-17 11:40:15 -05:00
# define MAX_THREADS 500
2018-09-24 11:25:28 -04:00
# define CLUSTER_SLOTS 16384
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 ;
2009-03-22 05:30:00 -04:00
int numclients ;
int liveclients ;
2011-08-17 11:06:19 -04:00
int requests ;
int requests_issued ;
int requests_finished ;
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 ;
2016-07-12 05:22:41 -04:00
int showerrors ;
2009-03-22 05:30:00 -04:00
long long start ;
long long totlatency ;
2010-11-04 09:47:15 -04:00
long long * latency ;
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 ;
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 ;
2019-02-11 11:57:20 -05:00
int is_fetching_slots ;
int is_updating_slots ;
int slots_last_update ;
2018-07-31 04:45:00 -04:00
/* Thread mutexes to be used as fallbacks by atomicvar.h */
pthread_mutex_t requests_issued_mutex ;
pthread_mutex_t requests_finished_mutex ;
pthread_mutex_t liveclients_mutex ;
2019-02-11 11:57:20 -05:00
pthread_mutex_t is_fetching_slots_mutex ;
pthread_mutex_t is_updating_slots_mutex ;
pthread_mutex_t updating_slots_mutex ;
pthread_mutex_t slots_last_update_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 */
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 ) ;
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
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 ;
}
/* _serverAssert is needed by dict */
void _serverAssert ( const char * estr , const char * file , int line ) {
fprintf ( stderr , " === ASSERTION FAILED === " ) ;
fprintf ( stderr , " ==> %s:%d '%s' is not true " , file , line , estr ) ;
* ( ( char * ) - 1 ) = ' x ' ;
}
2019-02-05 13:13:50 -05: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 ;
if ( hostsocket = = NULL )
c = redisConnect ( ip , port ) ;
else
c = redisConnectUnix ( hostsocket ) ;
2019-03-12 12:07:19 -04:00
if ( c = = NULL | | c - > err ) {
2019-02-05 13:13:50 -05:00
fprintf ( stderr , " Could not connect to Redis at " ) ;
2019-03-12 12:07:19 -04:00
char * err = ( c ! = NULL ? c - > errstr : " " ) ;
if ( hostsocket = = NULL ) fprintf ( stderr , " %s:%d: %s \n " , ip , port , err ) ;
else fprintf ( stderr , " %s: %s \n " , hostsocket , err ) ;
2019-02-05 13:13:50 -05:00
goto fail ;
}
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 ) ;
reply = ( ( redisReply * ) r ) ;
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 ) ;
freeReplyObject ( reply ) ;
redisFree ( c ) ;
zfree ( cfg ) ;
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 ;
int is_err = ( r - > type = = REDIS_REPLY_ERROR ) ;
2012-02-23 09:02:43 -05:00
2018-10-25 11:38:17 -04:00
if ( is_err & & config . showerrors ) {
2019-01-17 11:40:15 -05:00
/* TODO: static lasterr_time not thread-safe */
2016-07-12 05:22:41 -04:00
static time_t lasterr_time = 0 ;
time_t now = time ( NULL ) ;
2018-10-25 11:38:17 -04:00
if ( lasterr_time ! = now ) {
2016-07-12 05:22:41 -04:00
lasterr_time = now ;
2018-09-29 06:59:03 -04:00
if ( c - > cluster_node ) {
printf ( " Error from server %s:%d: %s \n " ,
c - > cluster_node - > ip ,
c - > cluster_node - > port ,
r - > str ) ;
} else printf ( " Error from server: %s \n " , r - > str ) ;
2016-07-12 05:22:41 -04:00
}
}
2019-03-08 05:05:02 -05:00
/* 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 ( is_err & & 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 " ,
c - > cluster_node - > ip ,
c - > cluster_node - > port ,
r - > str ) ;
2016-07-12 05:22:41 -04:00
}
2019-03-08 05:05:02 -05:00
if ( do_wait ) sleep ( 1 ) ;
if ( fetch_slots & & ! fetchClusterSlotsConfiguration ( c ) )
exit ( 1 ) ;
2016-07-12 05:22:41 -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 ;
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 ) ;
if ( requests_finished < config . requests )
2019-01-17 11:40:15 -05:00
config . latency [ requests_finished ] = c - > latency ;
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 ;
2019-01-18 11:31:32 -05:00
atomicGetIncr ( config . requests_issued , requests_issued , 1 ) ;
2018-07-31 04:45:00 -04:00
if ( requests_issued > = config . requests ) {
2011-08-17 11:06:19 -04:00
freeClient ( c ) ;
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
}
if ( sdslen ( c - > obuf ) > c - > written ) {
void * ptr = c - > obuf + c - > written ;
2015-01-18 16:46:25 -05:00
ssize_t nwritten = write ( c - > context - > fd , ptr , sdslen ( c - > obuf ) - c - > written ) ;
2009-03-22 05:30:00 -04:00
if ( nwritten = = - 1 ) {
2009-12-11 18:04:20 -05:00
if ( errno ! = EPIPE )
fprintf ( stderr , " Writing to socket: %s \n " , strerror ( errno ) ) ;
2009-03-22 05:30:00 -04:00
freeClient ( c ) ;
return ;
}
c - > written + = nwritten ;
if ( sdslen ( c - > obuf ) = = c - > written ) {
2018-07-31 04:45:00 -04:00
aeDeleteFileEvent ( el , c - > context - > fd , AE_WRITABLE ) ;
aeCreateFileEvent ( el , c - > context - > fd , AE_READABLE , readHandler , c ) ;
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 ) ;
}
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 ;
int len = redisFormatCommand ( & buf , " AUTH %s " , config . auth ) ;
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
}
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 - - ;
p + = 5 ; /* 12 is strlen("{tag}"). */
}
}
}
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-11-04 09:47:15 -04:00
static int compareLatency ( const void * a , const void * b ) {
return ( * ( long long * ) a ) - ( * ( long long * ) b ) ;
}
2019-01-31 13:44:17 -05:00
static int ipow ( int base , int exp ) {
int result = 1 ;
while ( exp ) {
if ( exp & 1 ) result * = base ;
exp / = 2 ;
base * = base ;
}
return result ;
}
2010-08-30 05:25:02 -04:00
static void showLatencyReport ( void ) {
2010-11-04 09:47:15 -04:00
int i , curlat = 0 ;
2019-02-13 16:03:31 -05:00
int usbetweenlat = ipow ( 10 , MAX_LATENCY_PRECISION - config . precision ) ;
2009-03-22 05:30:00 -04:00
float perc , reqpersec ;
2011-08-17 11:06:19 -04:00
reqpersec = ( float ) config . requests_finished / ( ( float ) config . totlatency / 1000 ) ;
2011-11-04 09:49:24 -04:00
if ( ! config . quiet & & ! config . csv ) {
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 " ) ;
2010-11-04 09:47:15 -04:00
qsort ( config . latency , config . requests , sizeof ( long long ) , compareLatency ) ;
for ( i = 0 ; i < config . requests ; i + + ) {
2019-02-14 07:19:45 -05:00
if ( config . latency [ i ] / usbetweenlat ! = curlat | |
i = = ( config . requests - 1 ) )
{
/* After the 2 milliseconds latency to have percentages split
* by decimals will just add a lot of noise to the output . */
2019-02-23 19:12:00 -05:00
if ( config . latency [ i ] > = 2000 ) {
2019-02-14 07:19:45 -05:00
config . precision = 0 ;
usbetweenlat = ipow ( 10 ,
MAX_LATENCY_PRECISION - config . precision ) ;
}
2019-02-23 19:12:00 -05:00
curlat = config . latency [ i ] / usbetweenlat ;
perc = ( ( float ) ( i + 1 ) * 100 ) / config . requests ;
printf ( " %.2f%% <= %.*f milliseconds \n " , perc , config . precision ,
curlat / pow ( 10.0 , config . precision ) ) ;
2009-03-22 05:30:00 -04:00
}
}
printf ( " %.2f requests per second \n \n " , reqpersec ) ;
2011-11-04 09:49:24 -04:00
} else if ( config . csv ) {
printf ( " \" %s \" , \" %.2f \" \n " , config . title , reqpersec ) ;
2009-03-22 05:30:00 -04:00
} else {
2010-08-30 05:25:02 -04:00
printf ( " %s: %.2f requests per second \n " , config . title , reqpersec ) ;
2009-03-22 05:30:00 -04:00
}
}
2019-03-07 05:14:03 -05:00
static void initBenchmarkThreads ( ) {
2018-07-31 04:45:00 -04:00
int i ;
2019-03-07 05:14:03 -05:00
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 ;
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 ( ) ;
}
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 ;
if ( config . hostsocket = = NULL )
ctx = redisConnect ( config . hostip , config . hostport ) ;
else
ctx = redisConnectUnix ( config . hostsocket ) ;
if ( ctx - > err ) {
fprintf ( stderr , " Could not connect to Redis at " ) ;
if ( config . hostsocket = = NULL ) {
fprintf ( stderr , " %s:%d: %s \n " , config . hostip , config . hostport ,
ctx - > errstr ) ;
} else fprintf ( stderr , " %s: %s \n " , config . hostsocket , ctx - > errstr ) ;
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 ;
if ( node - > ip = = NULL & & ip ! = NULL ) {
node - > ip = ip ;
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 ) ;
if ( config . showerrors )
printf ( " Cluster slots configuration changed, fetching new one... \n " ) ;
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 */
NULL /* val destructor */
} ;
/* 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 ) {
ctx = redisConnect ( node - > ip , node - > port ) ;
if ( ! ctx | | ctx - > err ) {
success = 0 ;
if ( ctx & & ctx - > err )
fprintf ( stderr , " REDIS CONNECTION ERROR: %s \n " , ctx - > errstr ) ;
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 ) ;
2009-03-22 05:30:00 -04:00
}
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 ] ) ;
} 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 ] ) ;
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 ;
2011-05-31 20:19:11 -04:00
config . randomkeys_keyspacelen = atoi ( argv [ + + i ] ) ;
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 " ) ) {
config . showerrors = 1 ;
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 ] ) ;
if ( config . precision < 0 ) config . precision = 0 ;
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 ;
2011-05-31 20:19:11 -04:00
} else if ( ! strcmp ( argv [ i ] , " --help " ) ) {
exit_status = 0 ;
goto usage ;
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 "
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 "
2011-11-07 05:29:37 -05:00
" -k <boolean> 1=keep alive 0=reconnect (default 1) \n "
" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD \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 "
2016-07-12 05:22:41 -04:00
" -e If server replies with errors, show them on stdout. \n "
" (no more than 1 error per second is displayed) \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 "
" -I Idle mode. Just open N idle connections and wait. \n \n "
" 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 ;
2019-01-18 11:31:32 -05:00
atomicGet ( config . liveclients , liveclients ) ;
atomicGet ( config . requests_finished , requests_finished ) ;
2010-08-30 05:25:02 -04:00
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 ;
}
2010-08-30 05:25:02 -04:00
float dt = ( float ) ( mstime ( ) - config . start ) / 1000.0 ;
2018-07-31 04:45:00 -04:00
float rps = ( float ) requests_finished / dt ;
2010-08-30 05:25:02 -04:00
printf ( " %s: %.2f \r " , config . title , rps ) ;
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 ;
2011-05-31 20:19:11 -04:00
char * data , * cmd ;
int len ;
2009-03-22 05:30:00 -04:00
client c ;
2012-11-30 09:41:09 -05:00
srandom ( time ( NULL ) ) ;
2009-03-22 05:30:00 -04:00
signal ( SIGHUP , SIG_IGN ) ;
signal ( SIGPIPE , SIG_IGN ) ;
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 ;
2016-07-12 05:22:41 -04:00
config . showerrors = 0 ;
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 . latency = NULL ;
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 ;
2019-02-14 07:19:45 -05:00
config . precision = 1 ;
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 ;
2009-03-22 05:30:00 -04:00
2011-05-31 20:19:11 -04:00
i = parseOptions ( argc , argv ) ;
argc - = i ;
argv + = i ;
2010-11-04 09:47:15 -04:00
config . latency = zmalloc ( sizeof ( long long ) * config . requests ) ;
2009-03-22 05:30:00 -04:00
2018-09-24 11:25:28 -04:00
if ( config . cluster_mode ) {
/* 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 ) ;
if ( node - > redis_config = = NULL ) exit ( 1 ) ;
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 ) ;
if ( config . redis_config = = NULL ) exit ( 1 ) ;
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 . requests_issued_mutex ) , NULL ) ;
pthread_mutex_init ( & ( config . requests_finished_mutex ) , NULL ) ;
pthread_mutex_init ( & ( config . liveclients_mutex ) , NULL ) ;
2019-02-11 11:57:20 -05:00
pthread_mutex_init ( & ( config . is_fetching_slots_mutex ) , NULL ) ;
pthread_mutex_init ( & ( config . is_updating_slots_mutex ) , NULL ) ;
pthread_mutex_init ( & ( config . updating_slots_mutex ) , NULL ) ;
pthread_mutex_init ( & ( config . slots_last_update_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 */
}
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 ) ;
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 {
2010-12-16 17:41:58 -05:00
memset ( data , ' x ' , config . datasize ) ;
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 " ) ;
benchmark ( " PING_BULK " , cmd , len ) ;
free ( cmd ) ;
}
2010-12-22 12:31:33 -05:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " set " ) ) {
2018-10-25 11:38:17 -04:00
len = redisFormatCommand ( & cmd , " SET key:{tag}:__rand_int__ %s " , 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 " ) ) {
2018-10-25 11:38:17 -04:00
len = redisFormatCommand ( & cmd , " GET key:{tag}:__rand_int__ " ) ;
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 " ) ) {
2018-10-25 11:38:17 -04:00
len = redisFormatCommand ( & cmd , " INCR counter:{tag}:__rand_int__ " ) ;
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 " ) ) {
2019-01-18 12:13:35 -05:00
len = redisFormatCommand ( & cmd , " LPUSH mylist:{tag} %s " , 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 " ) ) {
2019-01-18 12:13:35 -05:00
len = redisFormatCommand ( & cmd , " RPUSH mylist:{tag} %s " , 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 " ) ) {
2019-01-18 12:13:35 -05:00
len = redisFormatCommand ( & cmd , " LPOP mylist:{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 " ) ) {
2019-01-18 12:13:35 -05:00
len = redisFormatCommand ( & cmd , " RPOP mylist:{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 ,
2019-01-18 12:13:35 -05:00
" SADD myset:{tag} element:__rand_int__ " ) ;
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 ,
2019-02-15 06:31:00 -05:00
" HSET myhash:{tag}:__rand_int__ element:__rand_int__ %s " , 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 " ) ) {
2019-01-18 12:13:35 -05:00
len = redisFormatCommand ( & cmd , " SPOP myset:{tag} " ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " SPOP " , 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 " ) | |
test_is_selected ( " lrange_300 " ) | |
test_is_selected ( " lrange_500 " ) | |
test_is_selected ( " lrange_600 " ) )
{
2019-01-18 12:13:35 -05:00
len = redisFormatCommand ( & cmd , " LPUSH mylist:{tag} %s " , 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 " ) ) {
2019-01-18 12:13:35 -05:00
len = redisFormatCommand ( & cmd , " LRANGE mylist:{tag} 0 99 " ) ;
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 " ) ) {
2019-01-18 12:13:35 -05:00
len = redisFormatCommand ( & cmd , " LRANGE mylist:{tag} 0 299 " ) ;
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 " ) ) {
2019-01-18 12:13:35 -05:00
len = redisFormatCommand ( & cmd , " LRANGE mylist:{tag} 0 449 " ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " LRANGE_500 (first 450 elements) " , cmd , len ) ;
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 " ) ) {
2019-01-18 12:13:35 -05:00
len = redisFormatCommand ( & cmd , " LRANGE mylist:{tag} 0 599 " ) ;
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 " ) ) {
const char * argv [ 21 ] ;
argv [ 0 ] = " MSET " ;
for ( i = 1 ; i < 21 ; i + = 2 ) {
2019-01-18 12:13:35 -05:00
argv [ i ] = " key:{tag}:__rand_int__ " ;
2011-11-07 05:29:37 -05:00
argv [ i + 1 ] = data ;
}
len = redisFormatCommandArgv ( & cmd , 21 , argv , NULL ) ;
benchmark ( " MSET (10 keys) " , cmd , len ) ;
free ( cmd ) ;
}
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 ) ;
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 ;
}