]>
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.   48  #define REDIS_NOTUSED(V) ((void) V)   50  static struct  config 
{   54      const char  * hostsocket
;   59      int  requests_finished
;   63      int  randomkeys_keyspacelen
;   78  typedef  struct  _client 
{   79      redisContext 
* context
;   81      char  * randptr
[ 32 ];  /* needed for MSET against 10 keys */   83      unsigned int  written
;  /* bytes of 'obuf' already written */   84      long long  start
;  /* start time of a request */   85      long long  latency
;  /* request latency */   86      int  pending
;     /* Number of pending requests (sent but no reply received) */   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
-> pending 
=  config
. pipeline
;  145  static void  randomizeClientKey ( client c
) {  149      for  ( i 
=  0 ;  i 
<  c
-> randlen
;  i
++) {  150          r 
=  random () %  config
. randomkeys_keyspacelen
;  151          snprintf ( buf
, sizeof ( buf
), " %0 12zu" , r
);  152          memcpy ( c
-> randptr
[ i
], buf
, 12 );  156  static void  clientDone ( client c
) {  157      if  ( config
. requests_finished 
==  config
. requests
) {  162      if  ( config
. keepalive
) {  165          config
. liveclients
--;  166          createMissingClients ( c
);  167          config
. liveclients
++;  172  static void  readHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
) {  179      /* Calculate latency only for the first read event. This means that the  180       * server already sent the reply and we need to parse it. Parsing overhead  181       * is not part of the latency, so calculate it only once, here. */  182      if  ( c
-> latency 
<  0 )  c
-> latency 
=  ustime ()-( c
-> start
);  184      if  ( redisBufferRead ( c
-> context
) !=  REDIS_OK
) {  185          fprintf ( stderr
, "Error:  %s \n " , c
-> context
-> errstr
);  189              if  ( redisGetReply ( c
-> context
,& reply
) !=  REDIS_OK
) {  190                  fprintf ( stderr
, "Error:  %s \n " , c
-> context
-> errstr
);  194                  if  ( reply 
== ( void *) REDIS_REPLY_ERROR
) {  195                      fprintf ( stderr
, "Unexpected error reply, exiting... \n " );  199                  if  ( config
. requests_finished 
<  config
. requests
)  200                      config
. latency
[ config
. requests_finished
++] =  c
-> latency
;  202                  if  ( c
-> pending 
==  0 )  clientDone ( c
);  210  static void  writeHandler ( aeEventLoop 
* el
,  int  fd
,  void  * privdata
,  int  mask
) {  216      /* Initialize request when nothing was written. */  217      if  ( c
-> written 
==  0 ) {  218          /* Enforce upper bound to number of requests. */  219          if  ( config
. requests_issued
++ >=  config
. requests
) {  224          /* Really initialize: randomize keys and set start time. */  225          if  ( config
. randomkeys
)  randomizeClientKey ( c
);  230      if  ( sdslen ( c
-> obuf
) >  c
-> written
) {  231          void  * ptr 
=  c
-> obuf
+ c
-> written
;  232          int  nwritten 
=  write ( c
-> context
-> fd
, ptr
, sdslen ( c
-> obuf
)- c
-> written
);  233          if  ( nwritten 
== - 1 ) {  235                  fprintf ( stderr
,  "Writing to socket:  %s \n " ,  strerror ( errno
));  239          c
-> written 
+=  nwritten
;  240          if  ( sdslen ( c
-> obuf
) ==  c
-> written
) {  241              aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);  242              aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
, readHandler
, c
);  247  static  client 
createClient ( char  * cmd
,  size_t  len
) {  249      client c 
=  zmalloc ( sizeof ( struct  _client
));  251      if  ( config
. hostsocket 
==  NULL
) {  252          c
-> context 
=  redisConnectNonBlock ( config
. hostip
, config
. hostport
);  254          c
-> context 
=  redisConnectUnixNonBlock ( config
. hostsocket
);  256      if  ( c
-> context
-> err
) {  257          fprintf ( stderr
, "Could not connect to Redis at " );  258          if  ( config
. hostsocket 
==  NULL
)  259              fprintf ( stderr
, " %s : %d :  %s \n " , config
. hostip
, config
. hostport
, c
-> context
-> errstr
);  261              fprintf ( stderr
, " %s :  %s \n " , config
. hostsocket
, c
-> context
-> errstr
);  264      /* Queue N requests accordingly to the pipeline size. */  265      c
-> obuf 
=  sdsempty ();  266      for  ( j 
=  0 ;  j 
<  config
. pipeline
;  j
++)  267          c
-> obuf 
=  sdscatlen ( c
-> obuf
, cmd
, len
);  270      c
-> pending 
=  config
. pipeline
;  272      /* Find substrings in the output buffer that need to be randomized. */  273      if  ( config
. randomkeys
) {  275          while  (( p 
=  strstr ( p
, ":rand:" )) !=  NULL
) {  276              assert ( c
-> randlen 
< ( signed )( sizeof ( c
-> randptr
)/ sizeof ( char *)));  277              c
-> randptr
[ c
-> randlen
++] =  p
+ 6 ;  282  /*    redisSetReplyObjectFunctions(c->context,NULL); */  283      aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
, writeHandler
, c
);  284      listAddNodeTail ( config
. clients
, c
);  285      config
. liveclients
++;  289  static void  createMissingClients ( client c
) {  292      while ( config
. liveclients 
<  config
. numclients
) {  293          createClient ( c
-> obuf
, sdslen ( c
-> obuf
)/ config
. pipeline
);  295          /* Listen backlog is quite limited on most systems */  303  static int  compareLatency ( const void  * a
,  const void  * b
) {  304      return  (*( long long *) a
)-(*( long long *) b
);  307  static void  showLatencyReport ( void ) {  309      float  perc
,  reqpersec
;  311      reqpersec 
= ( float ) config
. requests_finished
/(( float ) config
. totlatency
/ 1000 );  312      if  (! config
. quiet 
&& ! config
. csv
) {  313          printf ( "======  %s  ====== \n " ,  config
. title
);  314          printf ( "   %d  requests completed in %.2f seconds \n " ,  config
. requests_finished
,  315              ( float ) config
. totlatency
/ 1000 );  316          printf ( "   %d  parallel clients \n " ,  config
. numclients
);  317          printf ( "   %d  bytes payload \n " ,  config
. datasize
);  318          printf ( "  keep alive:  %d \n " ,  config
. keepalive
);  321          qsort ( config
. latency
, config
. requests
, sizeof ( long long ), compareLatency
);  322          for  ( i 
=  0 ;  i 
<  config
. requests
;  i
++) {  323              if  ( config
. latency
[ i
]/ 1000  !=  curlat 
||  i 
== ( config
. requests
- 1 )) {  324                  curlat 
=  config
. latency
[ i
]/ 1000 ;  325                  perc 
= (( float )( i
+ 1 )* 100 )/ config
. requests
;  326                  printf ( "%.2f%% <=  %d  milliseconds \n " ,  perc
,  curlat
);  329          printf ( "%.2f requests per second \n\n " ,  reqpersec
);  330      }  else if  ( config
. csv
) {  331          printf ( " \" %s \" , \" %.2f \"\n " ,  config
. title
,  reqpersec
);  333          printf ( " %s : %.2f requests per second \n " ,  config
. title
,  reqpersec
);  337  static void  benchmark ( char  * title
,  char  * cmd
,  int  len
) {  340      config
. title 
=  title
;  341      config
. requests_issued 
=  0 ;  342      config
. requests_finished 
=  0 ;  344      c 
=  createClient ( cmd
, len
);  345      createMissingClients ( c
);  347      config
. start 
=  mstime ();  349      config
. totlatency 
=  mstime ()- config
. start
;  355  /* Returns number of consumed options. */  356  int  parseOptions ( int  argc
,  const char  ** argv
) {  361      for  ( i 
=  1 ;  i 
<  argc
;  i
++) {  362          lastarg 
= ( i 
== ( argc
- 1 ));  364          if  (! strcmp ( argv
[ i
], "-c" )) {  365              if  ( lastarg
)  goto  invalid
;  366              config
. numclients 
=  atoi ( argv
[++ i
]);  367          }  else if  (! strcmp ( argv
[ i
], "-n" )) {  368              if  ( lastarg
)  goto  invalid
;  369              config
. requests 
=  atoi ( argv
[++ i
]);  370          }  else if  (! strcmp ( argv
[ i
], "-k" )) {  371              if  ( lastarg
)  goto  invalid
;  372              config
. keepalive 
=  atoi ( argv
[++ i
]);  373          }  else if  (! strcmp ( argv
[ i
], "-h" )) {  374              if  ( lastarg
)  goto  invalid
;  375              config
. hostip 
=  strdup ( argv
[++ i
]);  376          }  else if  (! strcmp ( argv
[ i
], "-p" )) {  377              if  ( lastarg
)  goto  invalid
;  378              config
. hostport 
=  atoi ( argv
[++ i
]);  379          }  else if  (! strcmp ( argv
[ i
], "-s" )) {  380              if  ( lastarg
)  goto  invalid
;  381              config
. hostsocket 
=  strdup ( argv
[++ i
]);  382          }  else if  (! strcmp ( argv
[ i
], "-d" )) {  383              if  ( lastarg
)  goto  invalid
;  384              config
. datasize 
=  atoi ( argv
[++ i
]);  385              if  ( config
. datasize 
<  1 )  config
. datasize
= 1 ;  386              if  ( config
. datasize 
>  1024 * 1024 * 1024 )  config
. datasize 
=  1024 * 1024 * 1024 ;  387          }  else if  (! strcmp ( argv
[ i
], "-P" )) {  388              if  ( lastarg
)  goto  invalid
;  389              config
. pipeline 
=  atoi ( argv
[++ i
]);  390              if  ( config
. pipeline 
<=  0 )  config
. pipeline
= 1 ;  391          }  else if  (! strcmp ( argv
[ i
], "-r" )) {  392              if  ( lastarg
)  goto  invalid
;  393              config
. randomkeys 
=  1 ;  394              config
. randomkeys_keyspacelen 
=  atoi ( argv
[++ i
]);  395              if  ( config
. randomkeys_keyspacelen 
<  0 )  396                  config
. randomkeys_keyspacelen 
=  0 ;  397          }  else if  (! strcmp ( argv
[ i
], "-q" )) {  399          }  else if  (! strcmp ( argv
[ i
], "--csv" )) {  401          }  else if  (! strcmp ( argv
[ i
], "-l" )) {  403          }  else if  (! strcmp ( argv
[ i
], "-I" )) {  405          }  else if  (! strcmp ( argv
[ i
], "-t" )) {  406              if  ( lastarg
)  goto  invalid
;  407              /* We get the list of tests to run as a string in the form  408               * get,set,lrange,...,test_N. Then we add a comma before and  409               * after the string in order to make sure that searching  410               * for ",testname," will always get a match if the test is  412              config
. tests 
=  sdsnew ( "," );  413              config
. tests 
=  sdscat ( config
. tests
,( char *) argv
[++ i
]);  414              config
. tests 
=  sdscat ( config
. tests
, "," );  415              sdstolower ( config
. tests
);  416          }  else if  (! strcmp ( argv
[ i
], "--help" )) {  420              /* Assume the user meant to provide an option when the arg starts  421               * with a dash. We're done otherwise and should use the remainder  422               * as the command and arguments for running the benchmark. */  423              if  ( argv
[ i
][ 0 ] ==  '-' )  goto  invalid
;  431      printf ( "Invalid option  \" %s \"  or option argument missing \n\n " , argv
[ i
]);  435  "Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>] \n\n "  436  " -h <hostname>      Server hostname (default 127.0.0.1) \n "  437  " -p <port>          Server port (default 6379) \n "  438  " -s <socket>        Server socket (overrides host and port) \n "  439  " -c <clients>       Number of parallel connections (default 50) \n "  440  " -n <requests>      Total number of requests (default 10000) \n "  441  " -d <size>          Data size of SET/GET value in bytes (default 2) \n "  442  " -k <boolean>       1=keep alive 0=reconnect (default 1) \n "  443  " -r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD \n "  444  "  Using this option the benchmark will get/set keys \n "  445  "  in the form mykey_rand:000000012456 instead of constant \n "  446  "  keys, the <keyspacelen> argument determines the max \n "  447  "  number of values for the random number. For instance \n "  448  "  if set to 10 only rand:000000000000 - rand:000000000009 \n "  449  "  range will be allowed. \n "  450  " -P <numreq>        Pipeline <numreq> requests. Default 1 (no pipeline). \n "  451  " -q                 Quiet. Just show query/sec values \n "  452  " --csv              Output in CSV format \n "  453  " -l                 Loop. Run the tests forever \n "  454  " -t <tests>         Only run the comma separated list of tests. The test \n "  455  "                    names are the same as the ones produced as output. \n "  456  " -I                 Idle mode. Just open N idle connections and wait. \n\n "  458  " Run the benchmark with the default configuration against 127.0.0.1:6379: \n "  459  "   $ redis-benchmark \n\n "  460  " Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1: \n "  461  "   $ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20 \n\n "  462  " Fill 127.0.0.1:6379 with about 1 million keys only using the SET test: \n "  463  "   $ redis-benchmark -t set -n 1000000 -r 100000000 \n\n "  464  " Benchmark 127.0.0.1:6379 for a few commands producing CSV output: \n "  465  "   $ redis-benchmark -t ping,set,get -n 100000 --csv \n\n "  466  " Fill a list with 10000 random elements: \n "  467  "   $ redis-benchmark -r 10000 -n 10000 lpush mylist ele:rand:000000000000 \n\n "  472  int  showThroughput ( struct  aeEventLoop 
* eventLoop
,  long long  id
,  void  * clientData
) {  473      REDIS_NOTUSED ( eventLoop
);  475      REDIS_NOTUSED ( clientData
);  477      if  ( config
. csv
)  return  250 ;  478      float  dt 
= ( float )( mstime ()- config
. start
)/ 1000.0 ;  479      float  rps 
= ( float ) config
. requests_finished
/ dt
;  480      printf ( " %s : %.2f \r " ,  config
. title
,  rps
);  482      return  250 ;  /* every 250ms */  485  /* Return true if the named test was selected using the -t command line  486   * switch, or if all the tests are selected (no -t passed by user). */  487  int  test_is_selected ( char  * name
) {  489      int  l 
=  strlen ( name
);  491      if  ( config
. tests 
==  NULL
)  return  1 ;  493      memcpy ( buf
+ 1 , name
, l
);  496      return  strstr ( config
. tests
, buf
) !=  NULL
;  499  int  main ( int  argc
,  const char  ** argv
) {  506      signal ( SIGHUP
,  SIG_IGN
);  507      signal ( SIGPIPE
,  SIG_IGN
);  509      config
. numclients 
=  50 ;  510      config
. requests 
=  10000 ;  511      config
. liveclients 
=  0 ;  512      config
. el 
=  aeCreateEventLoop ( 1024 * 10 );  513      aeCreateTimeEvent ( config
. el
, 1 , showThroughput
, NULL
, NULL
);  514      config
. keepalive 
=  1 ;  517      config
. randomkeys 
=  0 ;  518      config
. randomkeys_keyspacelen 
=  0 ;  523      config
. latency 
=  NULL
;  524      config
. clients 
=  listCreate ();  525      config
. hostip 
=  "127.0.0.1" ;  526      config
. hostport 
=  6379 ;  527      config
. hostsocket 
=  NULL
;  530      i 
=  parseOptions ( argc
, argv
);  534      config
. latency 
=  zmalloc ( sizeof ( long long )* config
. requests
);  536      if  ( config
. keepalive 
==  0 ) {  537          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 " );  540      if  ( config
. idlemode
) {  541          printf ( "Creating  %d  idle connections and waiting forever (Ctrl+C when done) \n " ,  config
. numclients
);  542          c 
=  createClient ( "" , 0 );  /* will never receive a reply */  543          createMissingClients ( c
);  545          /* and will wait for every */  548      /* Run benchmark with command in the remainder of the arguments. */  550          sds title 
=  sdsnew ( argv
[ 0 ]);  551          for  ( i 
=  1 ;  i 
<  argc
;  i
++) {  552              title 
=  sdscatlen ( title
,  " " ,  1 );  553              title 
=  sdscatlen ( title
, ( char *) argv
[ i
],  strlen ( argv
[ i
]));  557              len 
=  redisFormatCommandArgv (& cmd
, argc
, argv
, NULL
);  558              benchmark ( title
, cmd
, len
);  560          }  while ( config
. loop
);  565      /* Run default benchmark suite. */  567          data 
=  zmalloc ( config
. datasize
+ 1 );  568          memset ( data
, 'x' , config
. datasize
);  569          data
[ config
. datasize
] =  '\0' ;  571          if  ( test_is_selected ( "ping_inline" ) ||  test_is_selected ( "ping" ))  572              benchmark ( "PING_INLINE" , "PING \r\n " , 6 );  574          if  ( test_is_selected ( "ping_mbulk" ) ||  test_is_selected ( "ping" )) {  575              len 
=  redisFormatCommand (& cmd
, "PING" );  576              benchmark ( "PING_BULK" , cmd
, len
);  580          if  ( test_is_selected ( "set" )) {  581              len 
=  redisFormatCommand (& cmd
, "SET foo:rand:000000000000  %s " , data
);  582              benchmark ( "SET" , cmd
, len
);  586          if  ( test_is_selected ( "get" )) {  587              len 
=  redisFormatCommand (& cmd
, "GET foo:rand:000000000000" );  588              benchmark ( "GET" , cmd
, len
);  592          if  ( test_is_selected ( "incr" )) {  593              len 
=  redisFormatCommand (& cmd
, "INCR counter:rand:000000000000" );  594              benchmark ( "INCR" , cmd
, len
);  598          if  ( test_is_selected ( "lpush" )) {  599              len 
=  redisFormatCommand (& cmd
, "LPUSH mylist  %s " , data
);  600              benchmark ( "LPUSH" , cmd
, len
);  604          if  ( test_is_selected ( "lpop" )) {  605              len 
=  redisFormatCommand (& cmd
, "LPOP mylist" );  606              benchmark ( "LPOP" , cmd
, len
);  610          if  ( test_is_selected ( "sadd" )) {  611              len 
=  redisFormatCommand (& cmd
,  612                  "SADD myset counter:rand:000000000000" );  613              benchmark ( "SADD" , cmd
, len
);  617          if  ( test_is_selected ( "spop" )) {  618              len 
=  redisFormatCommand (& cmd
, "SPOP myset" );  619              benchmark ( "SPOP" , cmd
, len
);  623          if  ( test_is_selected ( "lrange" ) ||  624              test_is_selected ( "lrange_100" ) ||  625              test_is_selected ( "lrange_300" ) ||  626              test_is_selected ( "lrange_500" ) ||  627              test_is_selected ( "lrange_600" ))  629              len 
=  redisFormatCommand (& cmd
, "LPUSH mylist  %s " , data
);  630              benchmark ( "LPUSH (needed to benchmark LRANGE)" , cmd
, len
);  634          if  ( test_is_selected ( "lrange" ) ||  test_is_selected ( "lrange_100" )) {  635              len 
=  redisFormatCommand (& cmd
, "LRANGE mylist 0 99" );  636              benchmark ( "LRANGE_100 (first 100 elements)" , cmd
, len
);  640          if  ( test_is_selected ( "lrange" ) ||  test_is_selected ( "lrange_300" )) {  641              len 
=  redisFormatCommand (& cmd
, "LRANGE mylist 0 299" );  642              benchmark ( "LRANGE_300 (first 300 elements)" , cmd
, len
);  646          if  ( test_is_selected ( "lrange" ) ||  test_is_selected ( "lrange_500" )) {  647              len 
=  redisFormatCommand (& cmd
, "LRANGE mylist 0 449" );  648              benchmark ( "LRANGE_500 (first 450 elements)" , cmd
, len
);  652          if  ( test_is_selected ( "lrange" ) ||  test_is_selected ( "lrange_600" )) {  653              len 
=  redisFormatCommand (& cmd
, "LRANGE mylist 0 599" );  654              benchmark ( "LRANGE_600 (first 600 elements)" , cmd
, len
);  658          if  ( test_is_selected ( "mset" )) {  659              const char  * argv
[ 21 ];  661              for  ( i 
=  1 ;  i 
<  21 ;  i 
+=  2 ) {  662                  argv
[ i
] =  "foo:rand:000000000000" ;  665              len 
=  redisFormatCommandArgv (& cmd
, 21 , argv
, NULL
);  666              benchmark ( "MSET (10 keys)" , cmd
, len
);  670          if  (! config
. csv
)  printf ( " \n " );  671      }  while ( config
. loop
);