]>
git.saurik.com Git - redis.git/blob - src/redis-benchmark.c 
5e40c97b50a4f108fee81dba786ed506dd7245d7
   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
;   83      unsigned int  written
;  /* bytes of 'obuf' already written */   85      long long  start
;  /* start time of a request */   86      long long  latency
;  /* request latency */   90  static void  writeHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
);   91  static void  createMissingClients ( client c
);   94  static long long  ustime ( void ) {   98      gettimeofday (& tv
,  NULL
);   99      ust 
= (( long ) tv
. tv_sec
)* 1000000 ;  104  static long long  mstime ( void ) {  108      gettimeofday (& tv
,  NULL
);  109      mst 
= (( long ) tv
. tv_sec
)* 1000 ;  110      mst 
+=  tv
. tv_usec
/ 1000 ;  114  static void  freeClient ( client c
) {  116      aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);  117      aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
);  118      redisFree ( c
-> context
);  121      config
. liveclients
--;  122      ln 
=  listSearchKey ( config
. clients
, c
);  124      listDelNode ( config
. clients
, ln
);  127  static void  freeAllClients ( void ) {  128      listNode 
* ln 
=  config
. clients
-> head
, * next
;  132          freeClient ( ln
-> value
);  137  static void  resetClient ( client c
) {  138      aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);  139      aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
);  140      aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
, writeHandler
, c
);  142      c
-> state 
=  CLIENT_SENDQUERY
;  147  static void  randomizeClientKey ( client c
) {  152      p 
=  strstr ( c
-> obuf
,  "_rand" );  155      r 
=  random () %  config
. randomkeys_keyspacelen
;  156      sprintf ( buf
, " %l d" , r
);  157      memcpy ( p
, buf
, strlen ( buf
));  160  static void  clientDone ( client c
) {  161      if  ( config
. donerequests 
==  config
. requests
) {  166      if  ( config
. keepalive
) {  168          if  ( config
. randomkeys
)  randomizeClientKey ( c
);  170          config
. liveclients
--;  171          createMissingClients ( c
);  172          config
. liveclients
++;  177  static void  readHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
) {  184      /* Calculate latency only for the first read event. This means that the  185       * server already sent the reply and we need to parse it. Parsing overhead  186       * is not part of the latency, so calculate it only once, here. */  187      if  ( c
-> latency 
<  0 )  c
-> latency 
=  ustime ()-( c
-> start
);  189      if  ( redisBufferRead ( c
-> context
) !=  REDIS_OK
) {  190          fprintf ( stderr
, "Error:  %s \n " , c
-> context
-> errstr
);  193          if  ( redisGetReply ( c
-> context
,& reply
) !=  REDIS_OK
) {  194              fprintf ( stderr
, "Error:  %s \n " , c
-> context
-> errstr
);  198              if  ( reply 
== ( void *) REDIS_REPLY_ERROR
) {  199                  fprintf ( stderr
, "Unexpected error reply, exiting... \n " );  203              if  ( config
. donerequests 
<  config
. requests
)  204                  config
. latency
[ config
. donerequests
++] =  c
-> latency
;  210  static void  writeHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
) {  216      if  ( c
-> state 
==  CLIENT_CONNECTING
) {  217          c
-> state 
=  CLIENT_SENDQUERY
;  221      if  ( sdslen ( c
-> obuf
) >  c
-> written
) {  222          void  * ptr 
=  c
-> obuf
+ c
-> written
;  223          int  nwritten 
=  write ( c
-> context
-> fd
, ptr
, sdslen ( c
-> obuf
)- c
-> written
);  224          if  ( nwritten 
== - 1 ) {  226                  fprintf ( stderr
,  "Writing to socket:  %s \n " ,  strerror ( errno
));  230          c
-> written 
+=  nwritten
;  231          if  ( sdslen ( c
-> obuf
) ==  c
-> written
) {  232              aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);  233              aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
, readHandler
, c
);  234              c
-> state 
=  CLIENT_READREPLY
;  239  static  client 
createClient ( int  replytype
) {  240      client c 
=  zmalloc ( sizeof ( struct  _client
));  241      if  ( config
. hostsocket 
==  NULL
) {  242          c
-> context 
=  redisConnectNonBlock ( config
. hostip
, config
. hostport
);  244          c
-> context 
=  redisConnectUnixNonBlock ( config
. hostsocket
);  246      if  ( c
-> context
-> err
) {  247          fprintf ( stderr
, "Could not connect to Redis at " );  248          if  ( config
. hostsocket 
==  NULL
)  249              fprintf ( stderr
, " %s : %d :  %s \n " , config
. hostip
, config
. hostport
, c
-> context
-> errstr
);  251              fprintf ( stderr
, " %s :  %s \n " , config
. hostsocket
, c
-> context
-> errstr
);  254      c
-> replytype 
=  replytype
;  255      c
-> state 
=  CLIENT_CONNECTING
;  256      c
-> obuf 
=  sdsempty ();  258      redisSetReplyObjectFunctions ( c
-> context
, NULL
);  259      aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
, writeHandler
, c
);  260      listAddNodeTail ( config
. clients
, c
);  261      config
. liveclients
++;  265  static void  createMissingClients ( client c
) {  266      while ( config
. liveclients 
<  config
. numclients
) {  267          client 
new  =  createClient ( c
-> replytype
);  269          new -> obuf 
=  sdsdup ( c
-> obuf
);  270          if  ( config
. randomkeys
)  randomizeClientKey ( c
);  274  static int  compareLatency ( const void  * a
,  const void  * b
) {  275      return  (*( long long *) a
)-(*( long long *) b
);  278  static void  showLatencyReport ( void ) {  280      float  perc
,  reqpersec
;  282      reqpersec 
= ( float ) config
. donerequests
/(( float ) config
. totlatency
/ 1000 );  284          printf ( "======  %s  ====== \n " ,  config
. title
);  285          printf ( "   %d  requests completed in %.2f seconds \n " ,  config
. donerequests
,  286              ( float ) config
. totlatency
/ 1000 );  287          printf ( "   %d  parallel clients \n " ,  config
. numclients
);  288          printf ( "   %d  bytes payload \n " ,  config
. datasize
);  289          printf ( "  keep alive:  %d \n " ,  config
. keepalive
);  292          qsort ( config
. latency
, config
. requests
, sizeof ( long long ), compareLatency
);  293          for  ( i 
=  0 ;  i 
<  config
. requests
;  i
++) {  294              if  ( config
. latency
[ i
]/ 1000  !=  curlat 
||  i 
== ( config
. requests
- 1 )) {  295                  curlat 
=  config
. latency
[ i
]/ 1000 ;  296                  perc 
= (( float )( i
+ 1 )* 100 )/ config
. requests
;  297                  printf ( "%.2f%% <=  %d  milliseconds \n " ,  perc
,  curlat
);  300          printf ( "%.2f requests per second \n\n " ,  reqpersec
);  302          printf ( " %s : %.2f requests per second \n " ,  config
. title
,  reqpersec
);  306  static void  prepareForBenchmark ( char  * title
) {  307      config
. title 
=  title
;  308      config
. start 
=  mstime ();  309      config
. donerequests 
=  0 ;  312  static void  endBenchmark ( void ) {  313      config
. totlatency 
=  mstime ()- config
. start
;  318  void  parseOptions ( int  argc
,  char  ** argv
) {  321      for  ( i 
=  1 ;  i 
<  argc
;  i
++) {  322          int  lastarg 
=  i
== argc
- 1 ;  324          if  (! strcmp ( argv
[ i
], "-c" ) && ! lastarg
) {  325              config
. numclients 
=  atoi ( argv
[ i
+ 1 ]);  327          }  else if  (! strcmp ( argv
[ i
], "-n" ) && ! lastarg
) {  328              config
. requests 
=  atoi ( argv
[ i
+ 1 ]);  330          }  else if  (! strcmp ( argv
[ i
], "-k" ) && ! lastarg
) {  331              config
. keepalive 
=  atoi ( argv
[ i
+ 1 ]);  333          }  else if  (! strcmp ( argv
[ i
], "-h" ) && ! lastarg
) {  334              config
. hostip 
=  argv
[ i
+ 1 ];  336          }  else if  (! strcmp ( argv
[ i
], "-p" ) && ! lastarg
) {  337              config
. hostport 
=  atoi ( argv
[ i
+ 1 ]);  339          }  else if  (! strcmp ( argv
[ i
], "-s" ) && ! lastarg
) {  340              config
. hostsocket 
=  argv
[ i
+ 1 ];  342          }  else if  (! strcmp ( argv
[ i
], "-d" ) && ! lastarg
) {  343              config
. datasize 
=  atoi ( argv
[ i
+ 1 ]);  345              if  ( config
. datasize 
<  1 )  config
. datasize
= 1 ;  346              if  ( config
. datasize 
>  1024 * 1024 )  config
. datasize 
=  1024 * 1024 ;  347          }  else if  (! strcmp ( argv
[ i
], "-r" ) && ! lastarg
) {  348              config
. randomkeys 
=  1 ;  349              config
. randomkeys_keyspacelen 
=  atoi ( argv
[ i
+ 1 ]);  350              if  ( config
. randomkeys_keyspacelen 
<  0 )  351                  config
. randomkeys_keyspacelen 
=  0 ;  353          }  else if  (! strcmp ( argv
[ i
], "-q" )) {  355          }  else if  (! strcmp ( argv
[ i
], "-l" )) {  357          }  else if  (! strcmp ( argv
[ i
], "-D" )) {  359          }  else if  (! strcmp ( argv
[ i
], "-I" )) {  362              printf ( "Wrong option ' %s ' or option argument missing \n\n " , argv
[ i
]);  363              printf ( "Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>] \n\n " );  364              printf ( " -h <hostname>      Server hostname (default 127.0.0.1) \n " );  365              printf ( " -p <port>          Server port (default 6379) \n " );  366              printf ( " -s <socket>        Server socket (overrides host and port) \n " );  367              printf ( " -c <clients>       Number of parallel connections (default 50) \n " );  368              printf ( " -n <requests>      Total number of requests (default 10000) \n " );  369              printf ( " -d <size>          Data size of SET/GET value in bytes (default 2) \n " );  370              printf ( " -k <boolean>       1=keep alive 0=reconnect (default 1) \n " );  371              printf ( " -r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD \n " );  372              printf ( "  Using this option the benchmark will get/set keys \n " );  373              printf ( "  in the form mykey_rand000000012456 instead of constant \n " );  374              printf ( "  keys, the <keyspacelen> argument determines the max \n " );  375              printf ( "  number of values for the random number. For instance \n " );  376              printf ( "  if set to 10 only rand000000000000 - rand000000000009 \n " );  377              printf ( "  range will be allowed. \n " );  378              printf ( " -q                 Quiet. Just show query/sec values \n " );  379              printf ( " -l                 Loop. Run the tests forever \n " );  380              printf ( " -I                 Idle mode. Just open N idle connections and wait. \n " );  381              printf ( " -D                 Debug mode. more verbose. \n " );  387  int  showThroughput ( struct  aeEventLoop 
* eventLoop
,  long long  id
,  void  * clientData
) {  388      REDIS_NOTUSED ( eventLoop
);  390      REDIS_NOTUSED ( clientData
);  392      float  dt 
= ( float )( mstime ()- config
. start
)/ 1000.0 ;  393      float  rps 
= ( float ) config
. donerequests
/ dt
;  394      printf ( " %s : %.2f \r " ,  config
. title
,  rps
);  396      return  250 ;  /* every 250ms */  399  int  main ( int  argc
,  char  ** argv
) {  402      signal ( SIGHUP
,  SIG_IGN
);  403      signal ( SIGPIPE
,  SIG_IGN
);  406      config
. numclients 
=  50 ;  407      config
. requests 
=  10000 ;  408      config
. liveclients 
=  0 ;  409      config
. el 
=  aeCreateEventLoop ();  410      aeCreateTimeEvent ( config
. el
, 1 , showThroughput
, NULL
, NULL
);  411      config
. keepalive 
=  1 ;  412      config
. donerequests 
=  0 ;  414      config
. randomkeys 
=  0 ;  415      config
. randomkeys_keyspacelen 
=  0 ;  419      config
. latency 
=  NULL
;  420      config
. clients 
=  listCreate ();  421      config
. hostip 
=  "127.0.0.1" ;  422      config
. hostport 
=  6379 ;  423      config
. hostsocket 
=  NULL
;  425      parseOptions ( argc
, argv
);  426      config
. latency 
=  zmalloc ( sizeof ( long long )* config
. requests
);  428      if  ( config
. keepalive 
==  0 ) {  429          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 " );  432      if  ( config
. idlemode
) {  433          printf ( "Creating  %d  idle connections and waiting forever (Ctrl+C when done) \n " ,  config
. numclients
);  434          prepareForBenchmark ( "IDLE" );  435          c 
=  createClient ( 0 );  /* will never receive a reply */  436          c
-> obuf 
=  sdsempty ();  437          createMissingClients ( c
);  439          /* and will wait for every */  443          prepareForBenchmark ( "PING" );  444          c 
=  createClient ( REDIS_REPLY_STATUS
);  445          c
-> obuf 
=  sdscat ( c
-> obuf
, "PING \r\n " );  446          createMissingClients ( c
);  450          prepareForBenchmark ( "PING (multi bulk)" );  451          c 
=  createClient ( REDIS_REPLY_STATUS
);  452          c
-> obuf 
=  sdscat ( c
-> obuf
, "*1 \r\n $4 \r\n PING \r\n " );  453          createMissingClients ( c
);  457          prepareForBenchmark ( "MSET (10 keys, multi bulk)" );  458          c 
=  createClient ( REDIS_REPLY_ARRAY
);  459          c
-> obuf 
=  sdscatprintf ( c
-> obuf
, "* %d \r\n $4 \r\n MSET \r\n " ,  11 );  462              char  * data 
=  zmalloc ( config
. datasize
+ 1 );  463              memset ( data
, 'x' , config
. datasize
);  464              data
[ config
. datasize
] =  '\0' ;  465              for  ( i 
=  0 ;  i 
<  10 ;  i
++) {  466                  c
-> obuf 
=  sdscatprintf ( c
-> obuf
, "$ %d \r\n %s \r\n " , config
. datasize
, data
);  470          createMissingClients ( c
);  474          prepareForBenchmark ( "SET" );  475          c 
=  createClient ( REDIS_REPLY_STATUS
);  476          c
-> obuf 
=  sdscat ( c
-> obuf
, "*3 \r\n $3 \r\n SET \r\n $20 \r\n foo_rand000000000000 \r\n " );  478              char  * data 
=  zmalloc ( config
. datasize
+ 2 );  479              memset ( data
, 'x' , config
. datasize
);  480              data
[ config
. datasize
] =  ' \r ' ;  481              data
[ config
. datasize
+ 1 ] =  ' \n ' ;  482              c
-> obuf 
=  sdscatprintf ( c
-> obuf
, "$ %d \r\n " , config
. datasize
);  483              c
-> obuf 
=  sdscatlen ( c
-> obuf
, data
, config
. datasize
+ 2 );  485          createMissingClients ( c
);  489          prepareForBenchmark ( "GET" );  490          c 
=  createClient ( REDIS_REPLY_STRING
);  491          c
-> obuf 
=  sdscat ( c
-> obuf
, "GET foo_rand000000000000 \r\n " );  492          createMissingClients ( c
);  496          prepareForBenchmark ( "INCR" );  497          c 
=  createClient ( REDIS_REPLY_INTEGER
);  498          c
-> obuf 
=  sdscat ( c
-> obuf
, "INCR counter_rand000000000000 \r\n " );  499          createMissingClients ( c
);  503          prepareForBenchmark ( "LPUSH" );  504          c 
=  createClient ( REDIS_REPLY_INTEGER
);  505          c
-> obuf 
=  sdscat ( c
-> obuf
, "LPUSH mylist bar \r\n " );  506          createMissingClients ( c
);  510          prepareForBenchmark ( "LPOP" );  511          c 
=  createClient ( REDIS_REPLY_STRING
);  512          c
-> obuf 
=  sdscat ( c
-> obuf
, "LPOP mylist \r\n " );  513          createMissingClients ( c
);  517          prepareForBenchmark ( "SADD" );  518          c 
=  createClient ( REDIS_REPLY_STATUS
);  519          c
-> obuf 
=  sdscat ( c
-> obuf
, "SADD myset counter_rand000000000000 \r\n " );  520          createMissingClients ( c
);  524          prepareForBenchmark ( "SPOP" );  525          c 
=  createClient ( REDIS_REPLY_STRING
);  526          c
-> obuf 
=  sdscat ( c
-> obuf
, "SPOP myset \r\n " );  527          createMissingClients ( c
);  531          prepareForBenchmark ( "LPUSH (again, in order to bench LRANGE)" );  532          c 
=  createClient ( REDIS_REPLY_STATUS
);  533          c
-> obuf 
=  sdscat ( c
-> obuf
, "LPUSH mylist bar \r\n " );  534          createMissingClients ( c
);  538          prepareForBenchmark ( "LRANGE (first 100 elements)" );  539          c 
=  createClient ( REDIS_REPLY_ARRAY
);  540          c
-> obuf 
=  sdscat ( c
-> obuf
, "LRANGE mylist 0 99 \r\n " );  541          createMissingClients ( c
);  545          prepareForBenchmark ( "LRANGE (first 300 elements)" );  546          c 
=  createClient ( REDIS_REPLY_ARRAY
);  547          c
-> obuf 
=  sdscat ( c
-> obuf
, "LRANGE mylist 0 299 \r\n " );  548          createMissingClients ( c
);  552          prepareForBenchmark ( "LRANGE (first 450 elements)" );  553          c 
=  createClient ( REDIS_REPLY_ARRAY
);  554          c
-> obuf 
=  sdscat ( c
-> obuf
, "LRANGE mylist 0 449 \r\n " );  555          createMissingClients ( c
);  559          prepareForBenchmark ( "LRANGE (first 600 elements)" );  560          c 
=  createClient ( REDIS_REPLY_ARRAY
);  561          c
-> obuf 
=  sdscat ( c
-> obuf
, "LRANGE mylist 0 599 \r\n " );  562          createMissingClients ( c
);  567      }  while ( config
. loop
);