]>
git.saurik.com Git - redis.git/blob - src/redis-benchmark.c 
   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.   49  #define REPLY_RETCODE 1   53  #define CLIENT_CONNECTING 0   54  #define CLIENT_SENDQUERY 1   55  #define CLIENT_READREPLY 2   57  #define MAX_LATENCY 5000   59  #define REDIS_NOTUSED(V) ((void) V)   61  static struct  config 
{   70      int  randomkeys_keyspacelen
;   85  typedef  struct  _client 
{   90      int  mbulk
;           /* Number of elements in an mbulk reply */   91      int  readlen
;         /* readlen == -1 means read a single line */   93      unsigned int  written
;         /* bytes of 'obuf' already written */   95      long long  start
;     /* start time in milliseconds */   99  static void  writeHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
);  100  static void  createMissingClients ( client c
);  103  static long long  mstime ( void ) {  107      gettimeofday (& tv
,  NULL
);  108      mst 
= (( long ) tv
. tv_sec
)* 1000 ;  109      mst 
+=  tv
. tv_usec
/ 1000 ;  113  static void  freeClient ( client c
) {  116      aeDeleteFileEvent ( config
. el
, c
-> fd
, AE_WRITABLE
);  117      aeDeleteFileEvent ( config
. el
, c
-> fd
, AE_READABLE
);  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
-> fd
, AE_WRITABLE
);  140      aeDeleteFileEvent ( config
. el
, c
-> fd
, AE_READABLE
);  141      aeCreateFileEvent ( config
. el
, c
-> fd
,  AE_WRITABLE
, writeHandler
, c
);  143      c
-> ibuf 
=  sdsempty ();  144      c
-> readlen 
= ( c
-> replytype 
==  REPLY_BULK 
||  145                    c
-> replytype 
==  REPLY_MBULK
) ? - 1  :  0 ;  149      c
-> state 
=  CLIENT_SENDQUERY
;  151      createMissingClients ( c
);  154  static void  randomizeClientKey ( client c
) {  159      p 
=  strstr ( c
-> obuf
,  "_rand" );  162      r 
=  random () %  config
. randomkeys_keyspacelen
;  163      sprintf ( buf
, " %l d" , r
);  164      memcpy ( p
, buf
, strlen ( buf
));  167  static void  prepareClientForReply ( client c
,  int  type
) {  168      if  ( type 
==  REPLY_BULK
) {  169          c
-> replytype 
=  REPLY_BULK
;  171      }  else if  ( type 
==  REPLY_MBULK
) {  172          c
-> replytype 
=  REPLY_MBULK
;  181  static void  clientDone ( client c
) {  182      static int  last_tot_received 
=  1 ;  185      config
. donerequests 
++;  186      latency 
=  mstime () -  c
-> start
;  187      if  ( latency 
>  MAX_LATENCY
)  latency 
=  MAX_LATENCY
;  188      config
. latency
[ latency
]++;  190      if  ( config
. debug 
&&  last_tot_received 
!=  c
-> totreceived
) {  191          printf ( "Tot bytes received:  %d \n " ,  c
-> totreceived
);  192          last_tot_received 
=  c
-> totreceived
;  194      if  ( config
. donerequests 
==  config
. requests
) {  199      if  ( config
. keepalive
) {  201          if  ( config
. randomkeys
)  randomizeClientKey ( c
);  203          config
. liveclients
--;  204          createMissingClients ( c
);  205          config
. liveclients
++;  210  /* Read a length from the buffer pointed to by *p, store the length in *len,  211   * and return the number of bytes that the cursor advanced. */  212  static int  readLen ( char  * p
,  int  * len
) {  213      char  * tail 
=  strstr ( p
, " \r\n " );  221  static void  readHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
)  224      int  nread
,  pos
= 0 ,  len
= 0 ;  230      nread 
=  read ( c
-> fd
, buf
, sizeof ( buf
));  232          fprintf ( stderr
,  "Reading from socket:  %s \n " ,  strerror ( errno
));  237          fprintf ( stderr
,  "EOF from client \n " );  241      c
-> totreceived 
+=  nread
;  242      c
-> ibuf 
=  sdscatlen ( c
-> ibuf
, buf
, nread
);  243      len 
=  sdslen ( c
-> ibuf
);  245      if  ( c
-> replytype 
==  REPLY_INT 
||  246          c
-> replytype 
==  REPLY_RETCODE
)  248          /* Check if the first line is complete. This is everything we need  249           * when waiting for an integer or status code reply.*/  250          if  (( p 
=  strstr ( c
-> ibuf
, " \r\n " )) !=  NULL
)  252      }  else if  ( c
-> replytype 
==  REPLY_BULK
) {  254          if  ( c
-> readlen 
<  0 ) {  255              advance 
=  readLen ( c
-> ibuf
+ pos
,& c
-> readlen
);  258                  if  ( c
-> readlen 
== - 1 ) {  261                      /* include the trailing \r\n */  270          if  ( c
-> readlen 
>  0 ) {  271              canconsume 
=  c
-> readlen 
> ( len
- pos
) ? ( len
- pos
) :  c
-> readlen
;  272              c
-> readlen 
-=  canconsume
;  278      }  else if  ( c
-> replytype 
==  REPLY_MBULK
) {  280          if  ( c
-> mbulk 
== - 1 ) {  281              advance 
=  readLen ( c
-> ibuf
+ pos
,& c
-> mbulk
);  292          while ( c
-> mbulk 
>  0  &&  pos 
<  len
) {  293              if  ( c
-> readlen 
>  0 ) {  294                  canconsume 
=  c
-> readlen 
> ( len
- pos
) ? ( len
- pos
) :  c
-> readlen
;  295                  c
-> readlen 
-=  canconsume
;  300                  advance 
=  readLen ( c
-> ibuf
+ pos
,& c
-> readlen
);  303                      if  ( c
-> readlen 
== - 1 ) {  307                          /* include the trailing \r\n */  321      c
-> ibuf 
=  sdsrange ( c
-> ibuf
, pos
,- 1 );  328  static void  writeHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
)  335      if  ( c
-> state 
==  CLIENT_CONNECTING
) {  336          c
-> state 
=  CLIENT_SENDQUERY
;  339      if  ( sdslen ( c
-> obuf
) >  c
-> written
) {  340          void  * ptr 
=  c
-> obuf
+ c
-> written
;  341          int  len 
=  sdslen ( c
-> obuf
) -  c
-> written
;  342          int  nwritten 
=  write ( c
-> fd
,  ptr
,  len
);  343          if  ( nwritten 
== - 1 ) {  345                  fprintf ( stderr
,  "Writing to socket:  %s \n " ,  strerror ( errno
));  349          c
-> written 
+=  nwritten
;  350          if  ( sdslen ( c
-> obuf
) ==  c
-> written
) {  351              aeDeleteFileEvent ( config
. el
, c
-> fd
, AE_WRITABLE
);  352              aeCreateFileEvent ( config
. el
, c
-> fd
, AE_READABLE
, readHandler
, c
);  353              c
-> state 
=  CLIENT_READREPLY
;  358  static  client 
createClient ( void ) {  359      client c 
=  zmalloc ( sizeof ( struct  _client
));  360      char  err
[ ANET_ERR_LEN
];  362      c
-> fd 
=  anetTcpNonBlockConnect ( err
, config
. hostip
, config
. hostport
);  363      if  ( c
-> fd 
==  ANET_ERR
) {  365          fprintf ( stderr
, "Connect:  %s \n " , err
);  368      anetTcpNoDelay ( NULL
, c
-> fd
);  369      c
-> obuf 
=  sdsempty ();  370      c
-> ibuf 
=  sdsempty ();  375      c
-> state 
=  CLIENT_CONNECTING
;  376      aeCreateFileEvent ( config
. el
,  c
-> fd
,  AE_WRITABLE
,  writeHandler
,  c
);  377      config
. liveclients
++;  378      listAddNodeTail ( config
. clients
, c
);  382  static void  createMissingClients ( client c
) {  383      while ( config
. liveclients 
<  config
. numclients
) {  384          client 
new  =  createClient ();  387          new -> obuf 
=  sdsdup ( c
-> obuf
);  388          if  ( config
. randomkeys
)  randomizeClientKey ( c
);  389          prepareClientForReply ( new , c
-> replytype
);  393  static void  showLatencyReport ( void ) {  395      float  perc
,  reqpersec
;  397      reqpersec 
= ( float ) config
. donerequests
/(( float ) config
. totlatency
/ 1000 );  399          printf ( "======  %s  ====== \n " ,  config
. title
);  400          printf ( "   %d  requests completed in %.2f seconds \n " ,  config
. donerequests
,  401              ( float ) config
. totlatency
/ 1000 );  402          printf ( "   %d  parallel clients \n " ,  config
. numclients
);  403          printf ( "   %d  bytes payload \n " ,  config
. datasize
);  404          printf ( "  keep alive:  %d \n " ,  config
. keepalive
);  406          for  ( j 
=  0 ;  j 
<=  MAX_LATENCY
;  j
++) {  407              if  ( config
. latency
[ j
]) {  408                  seen 
+=  config
. latency
[ j
];  409                  perc 
= (( float ) seen
* 100 )/ config
. donerequests
;  410                  printf ( "%.2f%% <=  %d  milliseconds \n " ,  perc
,  j
);  413          printf ( "%.2f requests per second \n\n " ,  reqpersec
);  415          printf ( " %s : %.2f requests per second \n " ,  config
. title
,  reqpersec
);  419  static void  prepareForBenchmark ( char  * title
) {  420      memset ( config
. latency
, 0 , sizeof ( int )*( MAX_LATENCY
+ 1 ));  421      config
. title 
=  title
;  422      config
. start 
=  mstime ();  423      config
. donerequests 
=  0 ;  426  static void  endBenchmark ( void ) {  427      config
. totlatency 
=  mstime ()- config
. start
;  432  void  parseOptions ( int  argc
,  char  ** argv
) {  435      for  ( i 
=  1 ;  i 
<  argc
;  i
++) {  436          int  lastarg 
=  i
== argc
- 1 ;  438          if  (! strcmp ( argv
[ i
], "-c" ) && ! lastarg
) {  439              config
. numclients 
=  atoi ( argv
[ i
+ 1 ]);  441          }  else if  (! strcmp ( argv
[ i
], "-n" ) && ! lastarg
) {  442              config
. requests 
=  atoi ( argv
[ i
+ 1 ]);  444          }  else if  (! strcmp ( argv
[ i
], "-k" ) && ! lastarg
) {  445              config
. keepalive 
=  atoi ( argv
[ i
+ 1 ]);  447          }  else if  (! strcmp ( argv
[ i
], "-h" ) && ! lastarg
) {  448              char  * ip 
=  zmalloc ( 32 );  449              if  ( anetResolve ( NULL
, argv
[ i
+ 1 ], ip
) ==  ANET_ERR
) {  450                  printf ( "Can't resolve  %s \n " ,  argv
[ i
]);  455          }  else if  (! strcmp ( argv
[ i
], "-p" ) && ! lastarg
) {  456              config
. hostport 
=  atoi ( argv
[ i
+ 1 ]);  458          }  else if  (! strcmp ( argv
[ i
], "-d" ) && ! lastarg
) {  459              config
. datasize 
=  atoi ( argv
[ i
+ 1 ]);  461              if  ( config
. datasize 
<  1 )  config
. datasize
= 1 ;  462              if  ( config
. datasize 
>  1024 * 1024 )  config
. datasize 
=  1024 * 1024 ;  463          }  else if  (! strcmp ( argv
[ i
], "-r" ) && ! lastarg
) {  464              config
. randomkeys 
=  1 ;  465              config
. randomkeys_keyspacelen 
=  atoi ( argv
[ i
+ 1 ]);  466              if  ( config
. randomkeys_keyspacelen 
<  0 )  467                  config
. randomkeys_keyspacelen 
=  0 ;  469          }  else if  (! strcmp ( argv
[ i
], "-q" )) {  471          }  else if  (! strcmp ( argv
[ i
], "-l" )) {  473          }  else if  (! strcmp ( argv
[ i
], "-D" )) {  475          }  else if  (! strcmp ( argv
[ i
], "-I" )) {  478              printf ( "Wrong option ' %s ' or option argument missing \n\n " , argv
[ i
]);  479              printf ( "Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>] \n\n " );  480              printf ( " -h <hostname>      Server hostname (default 127.0.0.1) \n " );  481              printf ( " -p <hostname>      Server port (default 6379) \n " );  482              printf ( " -c <clients>       Number of parallel connections (default 50) \n " );  483              printf ( " -n <requests>      Total number of requests (default 10000) \n " );  484              printf ( " -d <size>          Data size of SET/GET value in bytes (default 2) \n " );  485              printf ( " -k <boolean>       1=keep alive 0=reconnect (default 1) \n " );  486              printf ( " -r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD \n " );  487              printf ( "  Using this option the benchmark will get/set keys \n " );  488              printf ( "  in the form mykey_rand000000012456 instead of constant \n " );  489              printf ( "  keys, the <keyspacelen> argument determines the max \n " );  490              printf ( "  number of values for the random number. For instance \n " );  491              printf ( "  if set to 10 only rand000000000000 - rand000000000009 \n " );  492              printf ( "  range will be allowed. \n " );  493              printf ( " -q                 Quiet. Just show query/sec values \n " );  494              printf ( " -l                 Loop. Run the tests forever \n " );  495              printf ( " -I                 Idle mode. Just open N idle connections and wait. \n " );  496              printf ( " -D                 Debug mode. more verbose. \n " );  502  int  showThroughput ( struct  aeEventLoop 
* eventLoop
,  long long  id
,  void  * clientData
) {  503      REDIS_NOTUSED ( eventLoop
);  505      REDIS_NOTUSED ( clientData
);  507      float  dt 
= ( float )( mstime ()- config
. start
)/ 1000.0 ;  508      float  rps 
= ( float ) config
. donerequests
/ dt
;  509      printf ( " %s : %.2f \r " ,  config
. title
,  rps
);  511      return  250 ;  /* every 250ms */  514  int  main ( int  argc
,  char  ** argv
) {  517      signal ( SIGHUP
,  SIG_IGN
);  518      signal ( SIGPIPE
,  SIG_IGN
);  521      config
. numclients 
=  50 ;  522      config
. requests 
=  10000 ;  523      config
. liveclients 
=  0 ;  524      config
. el 
=  aeCreateEventLoop ();  525      aeCreateTimeEvent ( config
. el
, 1 , showThroughput
, NULL
, NULL
);  526      config
. keepalive 
=  1 ;  527      config
. donerequests 
=  0 ;  529      config
. randomkeys 
=  0 ;  530      config
. randomkeys_keyspacelen 
=  0 ;  534      config
. latency 
=  NULL
;  535      config
. clients 
=  listCreate ();  536      config
. latency 
=  zmalloc ( sizeof ( int )*( MAX_LATENCY
+ 1 ));  538      config
. hostip 
=  "127.0.0.1" ;  539      config
. hostport 
=  6379 ;  541      parseOptions ( argc
, argv
);  543      if  ( config
. keepalive 
==  0 ) {  544          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 " );  547      if  ( config
. idlemode
) {  548          printf ( "Creating  %d  idle connections and waiting forever (Ctrl+C when done) \n " ,  config
. numclients
);  549          prepareForBenchmark ( "IDLE" );  552          c
-> obuf 
=  sdsempty ();  553          prepareClientForReply ( c
, REPLY_RETCODE
);  /* will never receive it */  554          createMissingClients ( c
);  556          /* and will wait for every */  560          prepareForBenchmark ( "PING" );  563          c
-> obuf 
=  sdscat ( c
-> obuf
, "PING \r\n " );  564          prepareClientForReply ( c
, REPLY_RETCODE
);  565          createMissingClients ( c
);  569          prepareForBenchmark ( "PING (multi bulk)" );  572          c
-> obuf 
=  sdscat ( c
-> obuf
, "*1 \r\n $4 \r\n PING \r\n " );  573          prepareClientForReply ( c
, REPLY_RETCODE
);  574          createMissingClients ( c
);  578          prepareForBenchmark ( "SET" );  581          c
-> obuf 
=  sdscatprintf ( c
-> obuf
, "SET foo_rand000000000000  %d \r\n " , config
. datasize
);  583              char  * data 
=  zmalloc ( config
. datasize
+ 2 );  584              memset ( data
, 'x' , config
. datasize
);  585              data
[ config
. datasize
] =  ' \r ' ;  586              data
[ config
. datasize
+ 1 ] =  ' \n ' ;  587              c
-> obuf 
=  sdscatlen ( c
-> obuf
, data
, config
. datasize
+ 2 );  589          prepareClientForReply ( c
, REPLY_RETCODE
);  590          createMissingClients ( c
);  594          prepareForBenchmark ( "GET" );  597          c
-> obuf 
=  sdscat ( c
-> obuf
, "GET foo_rand000000000000 \r\n " );  598          prepareClientForReply ( c
, REPLY_BULK
);  599          createMissingClients ( c
);  603          prepareForBenchmark ( "INCR" );  606          c
-> obuf 
=  sdscat ( c
-> obuf
, "INCR counter_rand000000000000 \r\n " );  607          prepareClientForReply ( c
, REPLY_INT
);  608          createMissingClients ( c
);  612          prepareForBenchmark ( "LPUSH" );  615          c
-> obuf 
=  sdscat ( c
-> obuf
, "LPUSH mylist 3 \r\n bar \r\n " );  616          prepareClientForReply ( c
, REPLY_INT
);  617          createMissingClients ( c
);  621          prepareForBenchmark ( "LPOP" );  624          c
-> obuf 
=  sdscat ( c
-> obuf
, "LPOP mylist \r\n " );  625          prepareClientForReply ( c
, REPLY_BULK
);  626          createMissingClients ( c
);  630          prepareForBenchmark ( "SADD" );  633          c
-> obuf 
=  sdscat ( c
-> obuf
, "SADD myset 24 \r\n counter_rand000000000000 \r\n " );  634          prepareClientForReply ( c
, REPLY_RETCODE
);  635          createMissingClients ( c
);  639          prepareForBenchmark ( "SPOP" );  642          c
-> obuf 
=  sdscat ( c
-> obuf
, "SPOP myset \r\n " );  643          prepareClientForReply ( c
, REPLY_BULK
);  644          createMissingClients ( c
);  648          prepareForBenchmark ( "LPUSH (again, in order to bench LRANGE)" );  651          c
-> obuf 
=  sdscat ( c
-> obuf
, "LPUSH mylist 3 \r\n bar \r\n " );  652          prepareClientForReply ( c
, REPLY_RETCODE
);  653          createMissingClients ( c
);  657          prepareForBenchmark ( "LRANGE (first 100 elements)" );  660          c
-> obuf 
=  sdscat ( c
-> obuf
, "LRANGE mylist 0 99 \r\n " );  661          prepareClientForReply ( c
, REPLY_MBULK
);  662          createMissingClients ( c
);  666          prepareForBenchmark ( "LRANGE (first 300 elements)" );  669          c
-> obuf 
=  sdscat ( c
-> obuf
, "LRANGE mylist 0 299 \r\n " );  670          prepareClientForReply ( c
, REPLY_MBULK
);  671          createMissingClients ( c
);  675          prepareForBenchmark ( "LRANGE (first 450 elements)" );  678          c
-> obuf 
=  sdscat ( c
-> obuf
, "LRANGE mylist 0 449 \r\n " );  679          prepareClientForReply ( c
, REPLY_MBULK
);  680          createMissingClients ( c
);  684          prepareForBenchmark ( "LRANGE (first 600 elements)" );  687          c
-> obuf 
=  sdscat ( c
-> obuf
, "LRANGE mylist 0 599 \r\n " );  688          prepareClientForReply ( c
, REPLY_MBULK
);  689          createMissingClients ( c
);  694      }  while ( config
. loop
);