2009-03-22 05:30:00 -04:00
/* Redis benchmark utility.
*
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>
# include "ae.h"
2010-11-04 08:37:05 -04:00
# include "hiredis.h"
2009-03-22 05:30:00 -04:00
# include "sds.h"
# include "adlist.h"
# include "zmalloc.h"
# define REDIS_NOTUSED(V) ((void) V)
2013-08-07 09:58:51 -04:00
# define RANDPTR_INITIAL_SIZE 8
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 ;
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 ;
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 */
unsigned int written ; /* Bytes of 'obuf' already written */
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 */
2009-03-22 05:30:00 -04:00
} * client ;
/* Prototypes */
static void writeHandler ( aeEventLoop * el , int fd , void * privdata , int mask ) ;
static void createMissingClients ( client c ) ;
/* 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 ;
}
static void freeClient ( client c ) {
listNode * ln ;
2010-11-04 08:37:05 -04:00
aeDeleteFileEvent ( config . el , c - > context - > fd , AE_WRITABLE ) ;
aeDeleteFileEvent ( config . el , c - > context - > fd , AE_READABLE ) ;
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 ) ;
2009-03-22 05:30:00 -04:00
zfree ( c ) ;
config . liveclients - - ;
ln = listSearchKey ( config . clients , c ) ;
assert ( ln ! = NULL ) ;
listDelNode ( config . clients , ln ) ;
}
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 ) {
2010-11-04 08:37:05 -04:00
aeDeleteFileEvent ( config . el , c - > context - > fd , AE_WRITABLE ) ;
aeDeleteFileEvent ( config . el , c - > context - > fd , AE_READABLE ) ;
aeCreateFileEvent ( config . 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 ;
size_t r = random ( ) % config . randomkeys_keyspacelen ;
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
}
2009-03-22 05:30:00 -04:00
static void clientDone ( client c ) {
2011-08-17 11:06:19 -04:00
if ( config . requests_finished = = config . requests ) {
2009-03-22 05:30:00 -04:00
freeClient ( c ) ;
aeStop ( config . el ) ;
return ;
}
if ( config . keepalive ) {
resetClient ( c ) ;
} else {
config . liveclients - - ;
createMissingClients ( c ) ;
config . liveclients + + ;
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 ;
2009-03-22 05:30:00 -04:00
REDIS_NOTUSED ( el ) ;
REDIS_NOTUSED ( fd ) ;
REDIS_NOTUSED ( mask ) ;
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 ) ;
}
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 ;
}
continue ;
2013-08-06 12:50:54 -04:00
}
2012-02-23 09:02:43 -05:00
if ( config . requests_finished < config . requests )
config . latency [ config . requests_finished + + ] = c - > latency ;
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 ;
REDIS_NOTUSED ( el ) ;
REDIS_NOTUSED ( fd ) ;
REDIS_NOTUSED ( mask ) ;
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. */
if ( config . requests_issued + + > = config . requests ) {
freeClient ( c ) ;
return ;
}
/* Really initialize: randomize keys and set start time. */
2010-12-23 05:22:40 -05:00
if ( config . randomkeys ) randomizeClientKey ( c ) ;
2010-11-04 09:47:15 -04:00
c - > start = ustime ( ) ;
c - > latency = - 1 ;
2009-03-22 05:30:00 -04:00
}
2010-12-23 05:22:40 -05:00
2009-03-22 05:30:00 -04:00
if ( sdslen ( c - > obuf ) > c - > written ) {
void * ptr = c - > obuf + c - > written ;
2010-11-04 08:37:05 -04:00
int 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 ) {
2010-11-04 08:37:05 -04:00
aeDeleteFileEvent ( config . el , c - > context - > fd , AE_WRITABLE ) ;
aeCreateFileEvent ( config . 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 . */
2013-08-08 10:42:08 -04:00
static client createClient ( char * cmd , size_t len , client from ) {
2012-02-23 09:02:43 -05:00
int j ;
2009-03-22 05:30:00 -04:00
client c = zmalloc ( sizeof ( struct _client ) ) ;
2012-02-23 09:02:43 -05:00
2010-11-04 08:37:05 -04:00
if ( config . hostsocket = = NULL ) {
c - > context = redisConnectNonBlock ( config . hostip , config . hostport ) ;
} 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 " ) ;
if ( config . hostsocket = = NULL )
fprintf ( stderr , " %s:%d: %s \n " , config . hostip , config . hostport , c - > context - > errstr ) ;
else
fprintf ( stderr , " %s: %s \n " , config . hostsocket , c - > context - > errstr ) ;
exit ( 1 ) ;
}
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 . */
2013-08-06 12:50:54 -04:00
if ( config . dbnum ! = 0 ) {
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 ;
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
}
}
2014-10-27 02:02:52 -04:00
if ( config . idlemode = = 0 )
aeCreateFileEvent ( config . el , c - > context - > fd , AE_WRITABLE , writeHandler , c ) ;
2009-03-22 05:30:00 -04:00
listAddNodeTail ( config . clients , c ) ;
2010-11-04 08:37:05 -04:00
config . liveclients + + ;
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 ) {
2013-08-08 10:42:08 -04:00
createClient ( NULL , 0 , c ) ;
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 ) ;
}
2010-08-30 05:25:02 -04:00
static void showLatencyReport ( void ) {
2010-11-04 09:47:15 -04:00
int i , curlat = 0 ;
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 ) ;
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 + + ) {
if ( config . latency [ i ] / 1000 ! = curlat | | i = = ( config . requests - 1 ) ) {
curlat = config . latency [ i ] / 1000 ;
perc = ( ( float ) ( i + 1 ) * 100 ) / config . requests ;
printf ( " %.2f%% <= %d milliseconds \n " , perc , curlat ) ;
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
}
}
2012-02-23 09:02:43 -05:00
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
2013-08-08 10:42:08 -04:00
c = createClient ( cmd , len , NULL ) ;
2010-12-22 12:31:33 -05:00
createMissingClients ( c ) ;
config . start = mstime ( ) ;
aeMain ( config . el ) ;
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 ( ) ;
}
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 ;
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 ;
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 ) ;
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 (
" Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>] \n \n "
" -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 "
2011-11-07 05:29:37 -05:00
" -d <size> Data size of SET/GET value in bytes (default 2) \n "
2013-08-06 12:50:54 -04:00
" -dbnum <db> SELECT the specified db number (default 0) \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 "
2011-11-07 05:29:37 -05:00
" -q Quiet. Just show query/sec values \n "
" --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 ) {
REDIS_NOTUSED ( eventLoop ) ;
REDIS_NOTUSED ( id ) ;
REDIS_NOTUSED ( clientData ) ;
2014-07-04 11:52:14 -04:00
if ( config . liveclients = = 0 ) {
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
}
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 ;
2011-08-17 11:06:19 -04:00
float rps = ( float ) config . 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 ;
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 ;
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
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 ) ;
2013-08-08 10:42:08 -04:00
c = createClient ( " " , 0 , NULL ) ; /* will never receive a reply */
2009-11-23 12:50:39 -05:00
createMissingClients ( c ) ;
aeMain ( config . el ) ;
/* 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
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 " ) ) {
2013-08-08 10:42:08 -04:00
len = redisFormatCommand ( & cmd , " SET key:__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 " ) ) {
2013-08-08 10:42:08 -04:00
len = redisFormatCommand ( & cmd , " GET key:__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 " ) ) {
2013-08-08 10:42:08 -04:00
len = redisFormatCommand ( & cmd , " INCR counter:__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 " ) ) {
len = redisFormatCommand ( & cmd , " LPUSH mylist %s " , data ) ;
benchmark ( " LPUSH " , cmd , len ) ;
free ( cmd ) ;
}
2009-03-22 05:30:00 -04:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " lpop " ) ) {
len = redisFormatCommand ( & cmd , " LPOP mylist " ) ;
benchmark ( " LPOP " , cmd , len ) ;
free ( cmd ) ;
}
2009-03-22 05:30:00 -04:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " sadd " ) ) {
len = redisFormatCommand ( & cmd ,
2013-08-08 10:42:08 -04:00
" SADD myset element:__rand_int__ " ) ;
2011-11-07 05:29:37 -05:00
benchmark ( " SADD " , cmd , len ) ;
free ( cmd ) ;
}
2009-03-22 05:30:00 -04:00
2011-11-07 05:29:37 -05:00
if ( test_is_selected ( " spop " ) ) {
len = redisFormatCommand ( & cmd , " SPOP myset " ) ;
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 " ) )
{
len = redisFormatCommand ( & cmd , " LPUSH mylist %s " , data ) ;
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 " ) ) {
len = redisFormatCommand ( & cmd , " LRANGE mylist 0 99 " ) ;
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 " ) ) {
len = redisFormatCommand ( & cmd , " LRANGE mylist 0 299 " ) ;
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 " ) ) {
len = redisFormatCommand ( & cmd , " LRANGE mylist 0 449 " ) ;
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 " ) ) {
len = redisFormatCommand ( & cmd , " LRANGE mylist 0 599 " ) ;
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 ) {
2013-08-08 10:42:08 -04:00
argv [ i ] = " key:__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 ) ;
return 0 ;
}