]>
git.saurik.com Git - redis.git/blob - src/redis-benchmark.c 
ab0d9230c89840a7f9c8e956b0b64cbc36d66186
   1  /* Redis benchmark utility.    3   * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>    6   * Redistribution and use in source and binary forms, with or without    7   * modification, are permitted provided that the following conditions are met:    9   *   * Redistributions of source code must retain the above copyright notice,   10   *     this list of conditions and the following disclaimer.   11   *   * Redistributions in binary form must reproduce the above copyright   12   *     notice, this list of conditions and the following disclaimer in the   13   *     documentation and/or other materials provided with the distribution.   14   *   * Neither the name of Redis nor the names of its contributors may be used   15   *     to endorse or promote products derived from this software without   16   *     specific prior written permission.   18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"   19   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE   20   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE   21   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE   22   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR   23   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF   24   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS   25   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   26   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   27   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE   28   * POSSIBILITY OF SUCH DAMAGE.   48  #define CLIENT_CONNECTING 0   49  #define CLIENT_SENDQUERY 1   50  #define CLIENT_READREPLY 2   52  #define REDIS_NOTUSED(V) ((void) V)   54  static struct  config 
{   63      int  randomkeys_keyspacelen
;   79  typedef  struct  _client 
{   80      redisContext 
* context
;   84      unsigned int  written
;  /* bytes of 'obuf' already written */   86      long long  start
;  /* start time of a request */   87      long long  latency
;  /* request latency */   91  static void  writeHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
);   92  static void  createMissingClients ( client c
);   95  static long long  ustime ( void ) {   99      gettimeofday (& tv
,  NULL
);  100      ust 
= (( long ) tv
. tv_sec
)* 1000000 ;  105  static long long  mstime ( void ) {  109      gettimeofday (& tv
,  NULL
);  110      mst 
= (( long ) tv
. tv_sec
)* 1000 ;  111      mst 
+=  tv
. tv_usec
/ 1000 ;  115  static void  freeClient ( client c
) {  117      aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);  118      aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
);  119      redisFree ( c
-> context
);  122      config
. liveclients
--;  123      ln 
=  listSearchKey ( config
. clients
, c
);  125      listDelNode ( config
. clients
, ln
);  128  static void  freeAllClients ( void ) {  129      listNode 
* ln 
=  config
. clients
-> head
, * next
;  133          freeClient ( ln
-> value
);  138  static void  resetClient ( client c
) {  139      aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);  140      aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
);  141      aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
, writeHandler
, c
);  143      c
-> state 
=  CLIENT_SENDQUERY
;  148  static void  randomizeClientKey ( client c
) {  153      if  ( c
-> randptr 
==  NULL
)  return ;  155      /* Check if we have to randomize (only once per connection) */  156      if  ( c
-> randptr 
== ( void *)- 1 ) {  157          p 
=  strstr ( c
-> obuf
, ":rand:" );  162              newline 
=  strstr ( p
, " \r\n " );  163              assert ( newline
-( p
+ 6 ) ==  12 );  /* 12 chars for randomness */  168      /* Set random number in output buffer */  169      r 
=  random () %  config
. randomkeys_keyspacelen
;  170      snprintf ( buf
, sizeof ( buf
), " %0 12ld" , r
);  171      memcpy ( c
-> randptr
, buf
, 12 );  174  static void  clientDone ( client c
) {  175      if  ( config
. donerequests 
==  config
. requests
) {  180      if  ( config
. keepalive
) {  182          if  ( config
. randomkeys
)  randomizeClientKey ( c
);  184          config
. liveclients
--;  185          createMissingClients ( c
);  186          config
. liveclients
++;  191  static void  readHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
) {  198      /* Calculate latency only for the first read event. This means that the  199       * server already sent the reply and we need to parse it. Parsing overhead  200       * is not part of the latency, so calculate it only once, here. */  201      if  ( c
-> latency 
<  0 )  c
-> latency 
=  ustime ()-( c
-> start
);  203      if  ( redisBufferRead ( c
-> context
) !=  REDIS_OK
) {  204          fprintf ( stderr
, "Error:  %s \n " , c
-> context
-> errstr
);  207          if  ( redisGetReply ( c
-> context
,& reply
) !=  REDIS_OK
) {  208              fprintf ( stderr
, "Error:  %s \n " , c
-> context
-> errstr
);  212              if  ( reply 
== ( void *) REDIS_REPLY_ERROR
) {  213                  fprintf ( stderr
, "Unexpected error reply, exiting... \n " );  217              if  ( config
. donerequests 
<  config
. requests
)  218                  config
. latency
[ config
. donerequests
++] =  c
-> latency
;  224  static void  writeHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
) {  230      if  ( c
-> state 
==  CLIENT_CONNECTING
) {  231          c
-> state 
=  CLIENT_SENDQUERY
;  235      if  ( sdslen ( c
-> obuf
) >  c
-> written
) {  236          void  * ptr 
=  c
-> obuf
+ c
-> written
;  237          int  nwritten 
=  write ( c
-> context
-> fd
, ptr
, sdslen ( c
-> obuf
)- c
-> written
);  238          if  ( nwritten 
== - 1 ) {  240                  fprintf ( stderr
,  "Writing to socket:  %s \n " ,  strerror ( errno
));  244          c
-> written 
+=  nwritten
;  245          if  ( sdslen ( c
-> obuf
) ==  c
-> written
) {  246              aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);  247              aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
, readHandler
, c
);  248              c
-> state 
=  CLIENT_READREPLY
;  253  static  client 
createClient ( int  replytype
) {  254      client c 
=  zmalloc ( sizeof ( struct  _client
));  255      if  ( config
. hostsocket 
==  NULL
) {  256          c
-> context 
=  redisConnectNonBlock ( config
. hostip
, config
. hostport
);  258          c
-> context 
=  redisConnectUnixNonBlock ( config
. hostsocket
);  260      if  ( c
-> context
-> err
) {  261          fprintf ( stderr
, "Could not connect to Redis at " );  262          if  ( config
. hostsocket 
==  NULL
)  263              fprintf ( stderr
, " %s : %d :  %s \n " , config
. hostip
, config
. hostport
, c
-> context
-> errstr
);  265              fprintf ( stderr
, " %s :  %s \n " , config
. hostsocket
, c
-> context
-> errstr
);  268      c
-> replytype 
=  replytype
;  269      c
-> state 
=  CLIENT_CONNECTING
;  271      c
-> randptr 
= ( void *)- 1 ;  273      redisSetReplyObjectFunctions ( c
-> context
, NULL
);  274      aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
, writeHandler
, c
);  275      listAddNodeTail ( config
. clients
, c
);  276      config
. liveclients
++;  280  static void  createMissingClients ( client c
) {  281      while ( config
. liveclients 
<  config
. numclients
) {  282          client 
new  =  createClient ( c
-> replytype
);  283          new -> obuf 
=  sdsdup ( c
-> obuf
);  284          if  ( config
. randomkeys
)  randomizeClientKey ( c
);  288  static int  compareLatency ( const void  * a
,  const void  * b
) {  289      return  (*( long long *) a
)-(*( long long *) b
);  292  static void  showLatencyReport ( void ) {  294      float  perc
,  reqpersec
;  296      reqpersec 
= ( float ) config
. donerequests
/(( float ) config
. totlatency
/ 1000 );  298          printf ( "======  %s  ====== \n " ,  config
. title
);  299          printf ( "   %d  requests completed in %.2f seconds \n " ,  config
. donerequests
,  300              ( float ) config
. totlatency
/ 1000 );  301          printf ( "   %d  parallel clients \n " ,  config
. numclients
);  302          printf ( "   %d  bytes payload \n " ,  config
. datasize
);  303          printf ( "  keep alive:  %d \n " ,  config
. keepalive
);  306          qsort ( config
. latency
, config
. requests
, sizeof ( long long ), compareLatency
);  307          for  ( i 
=  0 ;  i 
<  config
. requests
;  i
++) {  308              if  ( config
. latency
[ i
]/ 1000  !=  curlat 
||  i 
== ( config
. requests
- 1 )) {  309                  curlat 
=  config
. latency
[ i
]/ 1000 ;  310                  perc 
= (( float )( i
+ 1 )* 100 )/ config
. requests
;  311                  printf ( "%.2f%% <=  %d  milliseconds \n " ,  perc
,  curlat
);  314          printf ( "%.2f requests per second \n\n " ,  reqpersec
);  316          printf ( " %s : %.2f requests per second \n " ,  config
. title
,  reqpersec
);  320  static void  prepareForBenchmark ( char  * title
) {  321      config
. title 
=  title
;  322      config
. start 
=  mstime ();  323      config
. donerequests 
=  0 ;  326  static void  endBenchmark ( void ) {  327      config
. totlatency 
=  mstime ()- config
. start
;  332  void  parseOptions ( int  argc
,  char  ** argv
) {  335      for  ( i 
=  1 ;  i 
<  argc
;  i
++) {  336          int  lastarg 
=  i
== argc
- 1 ;  338          if  (! strcmp ( argv
[ i
], "-c" ) && ! lastarg
) {  339              config
. numclients 
=  atoi ( argv
[ i
+ 1 ]);  341          }  else if  (! strcmp ( argv
[ i
], "-n" ) && ! lastarg
) {  342              config
. requests 
=  atoi ( argv
[ i
+ 1 ]);  344          }  else if  (! strcmp ( argv
[ i
], "-k" ) && ! lastarg
) {  345              config
. keepalive 
=  atoi ( argv
[ i
+ 1 ]);  347          }  else if  (! strcmp ( argv
[ i
], "-h" ) && ! lastarg
) {  348              config
. hostip 
=  argv
[ i
+ 1 ];  350          }  else if  (! strcmp ( argv
[ i
], "-p" ) && ! lastarg
) {  351              config
. hostport 
=  atoi ( argv
[ i
+ 1 ]);  353          }  else if  (! strcmp ( argv
[ i
], "-s" ) && ! lastarg
) {  354              config
. hostsocket 
=  argv
[ i
+ 1 ];  356          }  else if  (! strcmp ( argv
[ i
], "-d" ) && ! lastarg
) {  357              config
. datasize 
=  atoi ( argv
[ i
+ 1 ]);  359              if  ( config
. datasize 
<  1 )  config
. datasize
= 1 ;  360              if  ( config
. datasize 
>  1024 * 1024 )  config
. datasize 
=  1024 * 1024 ;  361          }  else if  (! strcmp ( argv
[ i
], "-r" ) && ! lastarg
) {  362              config
. randomkeys 
=  1 ;  363              config
. randomkeys_keyspacelen 
=  atoi ( argv
[ i
+ 1 ]);  364              if  ( config
. randomkeys_keyspacelen 
<  0 )  365                  config
. randomkeys_keyspacelen 
=  0 ;  367          }  else if  (! strcmp ( argv
[ i
], "-q" )) {  369          }  else if  (! strcmp ( argv
[ i
], "-l" )) {  371          }  else if  (! strcmp ( argv
[ i
], "-D" )) {  373          }  else if  (! strcmp ( argv
[ i
], "-I" )) {  376              printf ( "Wrong option ' %s ' or option argument missing \n\n " , argv
[ i
]);  377              printf ( "Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>] \n\n " );  378              printf ( " -h <hostname>      Server hostname (default 127.0.0.1) \n " );  379              printf ( " -p <port>          Server port (default 6379) \n " );  380              printf ( " -s <socket>        Server socket (overrides host and port) \n " );  381              printf ( " -c <clients>       Number of parallel connections (default 50) \n " );  382              printf ( " -n <requests>      Total number of requests (default 10000) \n " );  383              printf ( " -d <size>          Data size of SET/GET value in bytes (default 2) \n " );  384              printf ( " -k <boolean>       1=keep alive 0=reconnect (default 1) \n " );  385              printf ( " -r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD \n " );  386              printf ( "  Using this option the benchmark will get/set keys \n " );  387              printf ( "  in the form mykey_rand000000012456 instead of constant \n " );  388              printf ( "  keys, the <keyspacelen> argument determines the max \n " );  389              printf ( "  number of values for the random number. For instance \n " );  390              printf ( "  if set to 10 only rand000000000000 - rand000000000009 \n " );  391              printf ( "  range will be allowed. \n " );  392              printf ( " -q                 Quiet. Just show query/sec values \n " );  393              printf ( " -l                 Loop. Run the tests forever \n " );  394              printf ( " -I                 Idle mode. Just open N idle connections and wait. \n " );  395              printf ( " -D                 Debug mode. more verbose. \n " );  401  int  showThroughput ( struct  aeEventLoop 
* eventLoop
,  long long  id
,  void  * clientData
) {  402      REDIS_NOTUSED ( eventLoop
);  404      REDIS_NOTUSED ( clientData
);  406      float  dt 
= ( float )( mstime ()- config
. start
)/ 1000.0 ;  407      float  rps 
= ( float ) config
. donerequests
/ dt
;  408      printf ( " %s : %.2f \r " ,  config
. title
,  rps
);  410      return  250 ;  /* every 250ms */  413  int  main ( int  argc
,  char  ** argv
) {  417      signal ( SIGHUP
,  SIG_IGN
);  418      signal ( SIGPIPE
,  SIG_IGN
);  421      config
. numclients 
=  50 ;  422      config
. requests 
=  10000 ;  423      config
. liveclients 
=  0 ;  424      config
. el 
=  aeCreateEventLoop ();  425      aeCreateTimeEvent ( config
. el
, 1 , showThroughput
, NULL
, NULL
);  426      config
. keepalive 
=  1 ;  427      config
. donerequests 
=  0 ;  429      config
. randomkeys 
=  0 ;  430      config
. randomkeys_keyspacelen 
=  0 ;  434      config
. latency 
=  NULL
;  435      config
. clients 
=  listCreate ();  436      config
. hostip 
=  "127.0.0.1" ;  437      config
. hostport 
=  6379 ;  438      config
. hostsocket 
=  NULL
;  440      parseOptions ( argc
, argv
);  441      config
. latency 
=  zmalloc ( sizeof ( long long )* config
. requests
);  443      if  ( config
. keepalive 
==  0 ) {  444          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 " );  447      if  ( config
. idlemode
) {  448          printf ( "Creating  %d  idle connections and waiting forever (Ctrl+C when done) \n " ,  config
. numclients
);  449          prepareForBenchmark ( "IDLE" );  450          c 
=  createClient ( 0 );  /* will never receive a reply */  451          c
-> obuf 
=  sdsempty ();  452          createMissingClients ( c
);  454          /* and will wait for every */  461          data 
=  zmalloc ( config
. datasize
+ 1 );  462          memset ( data
, 'x' , config
. datasize
);  463          data
[ config
. datasize
] =  '\0' ;  465          prepareForBenchmark ( "PING (inline)" );  466          c 
=  createClient ( REDIS_REPLY_STATUS
);  467          c
-> obuf 
=  sdscat ( sdsempty (), "PING \r\n " );  468          createMissingClients ( c
);  472          prepareForBenchmark ( "PING" );  473          c 
=  createClient ( REDIS_REPLY_STATUS
);  474          len 
=  redisFormatCommand (& cmd
, "PING" );  475          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  477          createMissingClients ( c
);  481          prepareForBenchmark ( "MSET (10 keys)" );  482          c 
=  createClient ( REDIS_REPLY_ARRAY
);  484              const char  * argv
[ 11 ];  486              for  ( i 
=  1 ;  i 
<  11 ;  i
++)  488              len 
=  redisFormatCommandArgv (& cmd
, 11 , argv
, NULL
);  489              c
-> obuf 
=  sdsnewlen ( cmd
, len
);  492          createMissingClients ( c
);  496          prepareForBenchmark ( "SET" );  497          c 
=  createClient ( REDIS_REPLY_STATUS
);  498          len 
=  redisFormatCommand (& cmd
, "SET foo:rand:000000000000  %s " , data
);  499          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  501          createMissingClients ( c
);  505          prepareForBenchmark ( "GET" );  506          c 
=  createClient ( REDIS_REPLY_STRING
);  507          len 
=  redisFormatCommand (& cmd
, "GET foo:rand:000000000000" );  508          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  510          createMissingClients ( c
);  514          prepareForBenchmark ( "INCR" );  515          c 
=  createClient ( REDIS_REPLY_INTEGER
);  516          len 
=  redisFormatCommand (& cmd
, "INCR counter:rand:000000000000" );  517          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  519          createMissingClients ( c
);  523          prepareForBenchmark ( "LPUSH" );  524          c 
=  createClient ( REDIS_REPLY_INTEGER
);  525          len 
=  redisFormatCommand (& cmd
, "LPUSH mylist  %s " , data
);  526          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  528          createMissingClients ( c
);  532          prepareForBenchmark ( "LPOP" );  533          c 
=  createClient ( REDIS_REPLY_STRING
);  534          len 
=  redisFormatCommand (& cmd
, "LPOP mylist" );  535          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  537          createMissingClients ( c
);  541          prepareForBenchmark ( "SADD" );  542          c 
=  createClient ( REDIS_REPLY_STATUS
);  543          len 
=  redisFormatCommand (& cmd
, "SADD myset counter:rand:000000000000" );  544          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  546          createMissingClients ( c
);  550          prepareForBenchmark ( "SPOP" );  551          c 
=  createClient ( REDIS_REPLY_STRING
);  552          len 
=  redisFormatCommand (& cmd
, "SPOP myset" );  553          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  555          createMissingClients ( c
);  559          prepareForBenchmark ( "LPUSH (again, in order to bench LRANGE)" );  560          c 
=  createClient ( REDIS_REPLY_STATUS
);  561          len 
=  redisFormatCommand (& cmd
, "LPUSH mylist  %s " , data
);  562          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  564          createMissingClients ( c
);  568          prepareForBenchmark ( "LRANGE (first 100 elements)" );  569          c 
=  createClient ( REDIS_REPLY_ARRAY
);  570          len 
=  redisFormatCommand (& cmd
, "LRANGE mylist 0 99" );  571          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  573          createMissingClients ( c
);  577          prepareForBenchmark ( "LRANGE (first 300 elements)" );  578          c 
=  createClient ( REDIS_REPLY_ARRAY
);  579          len 
=  redisFormatCommand (& cmd
, "LRANGE mylist 0 299" );  580          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  582          createMissingClients ( c
);  586          prepareForBenchmark ( "LRANGE (first 450 elements)" );  587          c 
=  createClient ( REDIS_REPLY_ARRAY
);  588          len 
=  redisFormatCommand (& cmd
, "LRANGE mylist 0 449" );  589          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  591          createMissingClients ( c
);  595          prepareForBenchmark ( "LRANGE (first 600 elements)" );  596          c 
=  createClient ( REDIS_REPLY_ARRAY
);  597          len 
=  redisFormatCommand (& cmd
, "LRANGE mylist 0 599" );  598          c
-> obuf 
=  sdsnewlen ( cmd
, len
);  600          createMissingClients ( c
);  605      }  while ( config
. loop
);