]>
git.saurik.com Git - redis.git/blob - benchmark.c
bd1e861106dded7f67151d925ac0a2db40b0018c
1 /* Redis benchmark utility.
3 * Copyright (c) 2006-2009, 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
;
83 typedef struct _client
{
88 int mbulk
; /* Number of elements in an mbulk reply */
89 int readlen
; /* readlen == -1 means read a single line */
91 unsigned int written
; /* bytes of 'obuf' already written */
93 long long start
; /* start time in milliseconds */
97 static void writeHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
);
98 static void createMissingClients ( client c
);
101 static long long mstime ( void ) {
105 gettimeofday (& tv
, NULL
);
106 mst
= (( long ) tv
. tv_sec
)* 1000 ;
107 mst
+= tv
. tv_usec
/ 1000 ;
111 static void freeClient ( client c
) {
114 aeDeleteFileEvent ( config
. el
, c
-> fd
, AE_WRITABLE
);
115 aeDeleteFileEvent ( config
. el
, c
-> fd
, AE_READABLE
);
120 config
. liveclients
--;
121 ln
= listSearchKey ( config
. clients
, c
);
123 listDelNode ( config
. clients
, ln
);
126 static void freeAllClients ( void ) {
127 listNode
* ln
= config
. clients
-> head
, * next
;
131 freeClient ( ln
-> value
);
136 static void resetClient ( client c
) {
137 aeDeleteFileEvent ( config
. el
, c
-> fd
, AE_WRITABLE
);
138 aeDeleteFileEvent ( config
. el
, c
-> fd
, AE_READABLE
);
139 aeCreateFileEvent ( config
. el
, c
-> fd
, AE_WRITABLE
, writeHandler
, c
, NULL
);
141 c
-> ibuf
= sdsempty ();
142 c
-> readlen
= ( c
-> replytype
== REPLY_BULK
||
143 c
-> replytype
== REPLY_MBULK
) ? - 1 : 0 ;
147 c
-> state
= CLIENT_SENDQUERY
;
149 createMissingClients ( c
);
152 static void randomizeClientKey ( client c
) {
157 p
= strstr ( c
-> obuf
, "_rand" );
160 r
= random () % config
. randomkeys_keyspacelen
;
161 sprintf ( buf
, " %l d" , r
);
162 memcpy ( p
, buf
, strlen ( buf
));
165 static void prepareClientForReply ( client c
, int type
) {
166 if ( type
== REPLY_BULK
) {
167 c
-> replytype
= REPLY_BULK
;
169 } else if ( type
== REPLY_MBULK
) {
170 c
-> replytype
= REPLY_MBULK
;
179 static void clientDone ( client c
) {
180 static int last_tot_received
= 1 ;
183 config
. donerequests
++;
184 latency
= mstime () - c
-> start
;
185 if ( latency
> MAX_LATENCY
) latency
= MAX_LATENCY
;
186 config
. latency
[ latency
]++;
188 if ( config
. debug
&& last_tot_received
!= c
-> totreceived
) {
189 printf ( "Tot bytes received: %d \n " , c
-> totreceived
);
190 last_tot_received
= c
-> totreceived
;
192 if ( config
. donerequests
== config
. requests
) {
197 if ( config
. keepalive
) {
199 if ( config
. randomkeys
) randomizeClientKey ( c
);
201 config
. liveclients
--;
202 createMissingClients ( c
);
203 config
. liveclients
++;
208 static void readHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
)
217 nread
= read ( c
-> fd
, buf
, 1024 );
219 fprintf ( stderr
, "Reading from socket: %s \n " , strerror ( errno
));
224 fprintf ( stderr
, "EOF from client \n " );
228 c
-> totreceived
+= nread
;
229 c
-> ibuf
= sdscatlen ( c
-> ibuf
, buf
, nread
);
232 /* Are we waiting for the first line of the command of for sdf
233 * count in bulk or multi bulk operations? */
234 if ( c
-> replytype
== REPLY_INT
||
235 c
-> replytype
== REPLY_RETCODE
||
236 ( c
-> replytype
== REPLY_BULK
&& c
-> readlen
== - 1 ) ||
237 ( c
-> replytype
== REPLY_MBULK
&& c
-> readlen
== - 1 ) ||
238 ( c
-> replytype
== REPLY_MBULK
&& c
-> mbulk
== - 1 )) {
241 /* Check if the first line is complete. This is only true if
242 * there is a newline inside the buffer. */
243 if (( p
= strchr ( c
-> ibuf
, ' \n ' )) != NULL
) {
244 if ( c
-> replytype
== REPLY_BULK
||
245 ( c
-> replytype
== REPLY_MBULK
&& c
-> mbulk
!= - 1 ))
247 /* Read the count of a bulk reply (being it a single bulk or
248 * a multi bulk reply). "$<count>" for the protocol spec. */
251 c
-> readlen
= atoi ( c
-> ibuf
+ 1 )+ 2 ;
252 // printf("BULK ATOI: %s\n", c->ibuf+1);
253 /* Handle null bulk reply "$-1" */
254 if ( c
-> readlen
- 2 == - 1 ) {
258 /* Leave all the rest in the input buffer */
259 c
-> ibuf
= sdsrange ( c
-> ibuf
,( p
- c
-> ibuf
)+ 1 ,- 1 );
260 /* fall through to reach the point where the code will try
261 * to check if the bulk reply is complete. */
262 } else if ( c
-> replytype
== REPLY_MBULK
&& c
-> mbulk
== - 1 ) {
263 /* Read the count of a multi bulk reply. That is, how many
264 * bulk replies we have to read next. "*<count>" protocol. */
267 c
-> mbulk
= atoi ( c
-> ibuf
+ 1 );
268 /* Handle null bulk reply "*-1" */
269 if ( c
-> mbulk
== - 1 ) {
273 // printf("%p) %d elements list\n", c, c->mbulk);
274 /* Leave all the rest in the input buffer */
275 c
-> ibuf
= sdsrange ( c
-> ibuf
,( p
- c
-> ibuf
)+ 1 ,- 1 );
278 c
-> ibuf
= sdstrim ( c
-> ibuf
, " \r\n " );
284 /* bulk read, did we read everything? */
285 if ((( c
-> replytype
== REPLY_MBULK
&& c
-> mbulk
!= - 1 ) ||
286 ( c
-> replytype
== REPLY_BULK
)) && c
-> readlen
!= - 1 &&
287 ( unsigned ) c
-> readlen
<= sdslen ( c
-> ibuf
))
289 // printf("BULKSTATUS mbulk:%d readlen:%d sdslen:%d\n",
290 // c->mbulk,c->readlen,sdslen(c->ibuf));
291 if ( c
-> replytype
== REPLY_BULK
) {
293 } else if ( c
-> replytype
== REPLY_MBULK
) {
294 // printf("%p) %d (%d)) ",c, c->mbulk, c->readlen);
295 // fwrite(c->ibuf,c->readlen,1,stdout);
297 if (-- c
-> mbulk
== 0 ) {
300 c
-> ibuf
= sdsrange ( c
-> ibuf
, c
-> readlen
,- 1 );
308 static void writeHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
)
315 if ( c
-> state
== CLIENT_CONNECTING
) {
316 c
-> state
= CLIENT_SENDQUERY
;
319 if ( sdslen ( c
-> obuf
) > c
-> written
) {
320 void * ptr
= c
-> obuf
+ c
-> written
;
321 int len
= sdslen ( c
-> obuf
) - c
-> written
;
322 int nwritten
= write ( c
-> fd
, ptr
, len
);
323 if ( nwritten
== - 1 ) {
324 fprintf ( stderr
, "Writing to socket: %s \n " , strerror ( errno
));
328 c
-> written
+= nwritten
;
329 if ( sdslen ( c
-> obuf
) == c
-> written
) {
330 aeDeleteFileEvent ( config
. el
, c
-> fd
, AE_WRITABLE
);
331 aeCreateFileEvent ( config
. el
, c
-> fd
, AE_READABLE
, readHandler
, c
, NULL
);
332 c
-> state
= CLIENT_READREPLY
;
337 static client
createClient ( void ) {
338 client c
= zmalloc ( sizeof ( struct _client
));
339 char err
[ ANET_ERR_LEN
];
341 c
-> fd
= anetTcpNonBlockConnect ( err
, config
. hostip
, config
. hostport
);
342 if ( c
-> fd
== ANET_ERR
) {
344 fprintf ( stderr
, "Connect: %s \n " , err
);
347 anetTcpNoDelay ( NULL
, c
-> fd
);
348 c
-> obuf
= sdsempty ();
349 c
-> ibuf
= sdsempty ();
354 c
-> state
= CLIENT_CONNECTING
;
355 aeCreateFileEvent ( config
. el
, c
-> fd
, AE_WRITABLE
, writeHandler
, c
, NULL
);
356 config
. liveclients
++;
357 listAddNodeTail ( config
. clients
, c
);
361 static void createMissingClients ( client c
) {
362 while ( config
. liveclients
< config
. numclients
) {
363 client
new = createClient ();
366 new -> obuf
= sdsdup ( c
-> obuf
);
367 if ( config
. randomkeys
) randomizeClientKey ( c
);
368 new -> replytype
= c
-> replytype
;
369 if ( c
-> replytype
== REPLY_BULK
)
374 static void showLatencyReport ( char * title
) {
376 float perc
, reqpersec
;
378 reqpersec
= ( float ) config
. donerequests
/(( float ) config
. totlatency
/ 1000 );
380 printf ( "====== %s ====== \n " , title
);
381 printf ( " %d requests completed in %.2f seconds \n " , config
. donerequests
,
382 ( float ) config
. totlatency
/ 1000 );
383 printf ( " %d parallel clients \n " , config
. numclients
);
384 printf ( " %d bytes payload \n " , config
. datasize
);
385 printf ( " keep alive: %d \n " , config
. keepalive
);
387 for ( j
= 0 ; j
<= MAX_LATENCY
; j
++) {
388 if ( config
. latency
[ j
]) {
389 seen
+= config
. latency
[ j
];
390 perc
= (( float ) seen
* 100 )/ config
. donerequests
;
391 printf ( "%.2f%% <= %d milliseconds \n " , perc
, j
);
394 printf ( "%.2f requests per second \n\n " , reqpersec
);
396 printf ( " %s : %.2f requests per second \n " , title
, reqpersec
);
400 static void prepareForBenchmark ( void )
402 memset ( config
. latency
, 0 , sizeof ( int )*( MAX_LATENCY
+ 1 ));
403 config
. start
= mstime ();
404 config
. donerequests
= 0 ;
407 static void endBenchmark ( char * title
) {
408 config
. totlatency
= mstime ()- config
. start
;
409 showLatencyReport ( title
);
413 void parseOptions ( int argc
, char ** argv
) {
416 for ( i
= 1 ; i
< argc
; i
++) {
417 int lastarg
= i
== argc
- 1 ;
419 if (! strcmp ( argv
[ i
], "-c" ) && ! lastarg
) {
420 config
. numclients
= atoi ( argv
[ i
+ 1 ]);
422 } else if (! strcmp ( argv
[ i
], "-n" ) && ! lastarg
) {
423 config
. requests
= atoi ( argv
[ i
+ 1 ]);
425 } else if (! strcmp ( argv
[ i
], "-k" ) && ! lastarg
) {
426 config
. keepalive
= atoi ( argv
[ i
+ 1 ]);
428 } else if (! strcmp ( argv
[ i
], "-h" ) && ! lastarg
) {
429 char * ip
= zmalloc ( 32 );
430 if ( anetResolve ( NULL
, argv
[ i
+ 1 ], ip
) == ANET_ERR
) {
431 printf ( "Can't resolve %s \n " , argv
[ i
]);
436 } else if (! strcmp ( argv
[ i
], "-p" ) && ! lastarg
) {
437 config
. hostport
= atoi ( argv
[ i
+ 1 ]);
439 } else if (! strcmp ( argv
[ i
], "-d" ) && ! lastarg
) {
440 config
. datasize
= atoi ( argv
[ i
+ 1 ]);
442 if ( config
. datasize
< 1 ) config
. datasize
= 1 ;
443 if ( config
. datasize
> 1024 * 1024 ) config
. datasize
= 1024 * 1024 ;
444 } else if (! strcmp ( argv
[ i
], "-r" ) && ! lastarg
) {
445 config
. randomkeys
= 1 ;
446 config
. randomkeys_keyspacelen
= atoi ( argv
[ i
+ 1 ]);
447 if ( config
. randomkeys_keyspacelen
< 0 )
448 config
. randomkeys_keyspacelen
= 0 ;
450 } else if (! strcmp ( argv
[ i
], "-q" )) {
452 } else if (! strcmp ( argv
[ i
], "-l" )) {
454 } else if (! strcmp ( argv
[ i
], "-D" )) {
457 printf ( "Wrong option ' %s ' or option argument missing \n\n " , argv
[ i
]);
458 printf ( "Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>] \n\n " );
459 printf ( " -h <hostname> Server hostname (default 127.0.0.1) \n " );
460 printf ( " -p <hostname> Server port (default 6379) \n " );
461 printf ( " -c <clients> Number of parallel connections (default 50) \n " );
462 printf ( " -n <requests> Total number of requests (default 10000) \n " );
463 printf ( " -d <size> Data size of SET/GET value in bytes (default 2) \n " );
464 printf ( " -k <boolean> 1=keep alive 0=reconnect (default 1) \n " );
465 printf ( " -r <keyspacelen> Use random keys for SET/GET/INCR \n " );
466 printf ( " Using this option the benchmark will get/set keys \n " );
467 printf ( " in the form mykey_rand000000012456 instead of constant \n " );
468 printf ( " keys, the <keyspacelen> argument determines the max \n " );
469 printf ( " number of values for the random number. For instance \n " );
470 printf ( " if set to 10 only rand000000000000 - rand000000000009 \n " );
471 printf ( " range will be allowed. \n " );
472 printf ( " -q Quiet. Just show query/sec values \n " );
473 printf ( " -l Loop. Run the tests forever \n " );
474 printf ( " -D Debug mode. more verbose. \n " );
480 int main ( int argc
, char ** argv
) {
483 signal ( SIGHUP
, SIG_IGN
);
484 signal ( SIGPIPE
, SIG_IGN
);
487 config
. numclients
= 50 ;
488 config
. requests
= 10000 ;
489 config
. liveclients
= 0 ;
490 config
. el
= aeCreateEventLoop ();
491 config
. keepalive
= 1 ;
492 config
. donerequests
= 0 ;
494 config
. randomkeys
= 0 ;
495 config
. randomkeys_keyspacelen
= 0 ;
498 config
. latency
= NULL
;
499 config
. clients
= listCreate ();
500 config
. latency
= zmalloc ( sizeof ( int )*( MAX_LATENCY
+ 1 ));
502 config
. hostip
= "127.0.0.1" ;
503 config
. hostport
= 6379 ;
505 parseOptions ( argc
, argv
);
507 if ( config
. keepalive
== 0 ) {
508 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 " );
512 prepareForBenchmark ();
515 c
-> obuf
= sdscatprintf ( c
-> obuf
, "SET foo_rand000000000000 %d \r\n " , config
. datasize
);
517 char * data
= zmalloc ( config
. datasize
+ 2 );
518 memset ( data
, 'x' , config
. datasize
);
519 data
[ config
. datasize
] = ' \r ' ;
520 data
[ config
. datasize
+ 1 ] = ' \n ' ;
521 c
-> obuf
= sdscatlen ( c
-> obuf
, data
, config
. datasize
+ 2 );
523 prepareClientForReply ( c
, REPLY_RETCODE
);
524 createMissingClients ( c
);
528 prepareForBenchmark ();
531 c
-> obuf
= sdscat ( c
-> obuf
, "GET foo_rand000000000000 \r\n " );
532 prepareClientForReply ( c
, REPLY_BULK
);
533 createMissingClients ( c
);
537 prepareForBenchmark ();
540 c
-> obuf
= sdscat ( c
-> obuf
, "INCR counter_rand000000000000 \r\n " );
541 prepareClientForReply ( c
, REPLY_INT
);
542 createMissingClients ( c
);
544 endBenchmark ( "INCR" );
546 prepareForBenchmark ();
549 c
-> obuf
= sdscat ( c
-> obuf
, "LPUSH mylist 3 \r\n bar \r\n " );
550 prepareClientForReply ( c
, REPLY_INT
);
551 createMissingClients ( c
);
553 endBenchmark ( "LPUSH" );
555 prepareForBenchmark ();
558 c
-> obuf
= sdscat ( c
-> obuf
, "LPOP mylist \r\n " );
559 prepareClientForReply ( c
, REPLY_BULK
);
560 createMissingClients ( c
);
562 endBenchmark ( "LPOP" );
564 prepareForBenchmark ();
567 c
-> obuf
= sdscat ( c
-> obuf
, "PING \r\n " );
568 prepareClientForReply ( c
, REPLY_RETCODE
);
569 createMissingClients ( c
);
571 endBenchmark ( "PING" );
573 prepareForBenchmark ();
576 c
-> obuf
= sdscat ( c
-> obuf
, "LPUSH mylist 3 \r\n bar \r\n " );
577 prepareClientForReply ( c
, REPLY_RETCODE
);
578 createMissingClients ( c
);
580 endBenchmark ( "LPUSH (again, in order to bench LRANGE)" );
582 prepareForBenchmark ();
585 c
-> obuf
= sdscat ( c
-> obuf
, "LRANGE mylist 0 99 \r\n " );
586 prepareClientForReply ( c
, REPLY_MBULK
);
587 createMissingClients ( c
);
589 endBenchmark ( "LRANGE (first 100 elements)" );
591 prepareForBenchmark ();
594 c
-> obuf
= sdscat ( c
-> obuf
, "LRANGE mylist 0 299 \r\n " );
595 prepareClientForReply ( c
, REPLY_MBULK
);
596 createMissingClients ( c
);
598 endBenchmark ( "LRANGE (first 300 elements)" );
600 prepareForBenchmark ();
603 c
-> obuf
= sdscat ( c
-> obuf
, "LRANGE mylist 0 449 \r\n " );
604 prepareClientForReply ( c
, REPLY_MBULK
);
605 createMissingClients ( c
);
607 endBenchmark ( "LRANGE (first 450 elements)" );
609 prepareForBenchmark ();
612 c
-> obuf
= sdscat ( c
-> obuf
, "LRANGE mylist 0 599 \r\n " );
613 prepareClientForReply ( c
, REPLY_MBULK
);
614 createMissingClients ( c
);
616 endBenchmark ( "LRANGE (first 600 elements)" );
619 } while ( config
. loop
);