]>
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
);