]>
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 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
;
84 unsigned int written
; /* bytes of 'obuf' already written */
86 long long start
; /* start time of a request */
87 long long latency
; /* request latency */
91 static void writeHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
);
92 static void createMissingClients ( client c
);
95 static long long ustime ( void ) {
99 gettimeofday (& tv
, NULL
);
100 ust
= (( long ) tv
. tv_sec
)* 1000000 ;
105 static long long mstime ( void ) {
109 gettimeofday (& tv
, NULL
);
110 mst
= (( long ) tv
. tv_sec
)* 1000 ;
111 mst
+= tv
. tv_usec
/ 1000 ;
115 static void freeClient ( client c
) {
117 aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);
118 aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
);
119 redisFree ( c
-> context
);
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
-> context
-> fd
, AE_WRITABLE
);
140 aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
);
141 aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
, writeHandler
, c
);
143 c
-> state
= CLIENT_SENDQUERY
;
148 static void randomizeClientKey ( client c
) {
153 if ( c
-> randptr
== NULL
) return ;
155 /* Check if we have to randomize (only once per connection) */
156 if ( c
-> randptr
== ( void *)- 1 ) {
157 p
= strstr ( c
-> obuf
, ":rand:" );
162 newline
= strstr ( p
, " \r\n " );
163 assert ( newline
-( p
+ 6 ) == 12 ); /* 12 chars for randomness */
168 /* Set random number in output buffer */
169 r
= random () % config
. randomkeys_keyspacelen
;
170 snprintf ( buf
, sizeof ( buf
), " %0 12ld" , r
);
171 memcpy ( c
-> randptr
, buf
, 12 );
174 static void clientDone ( client c
) {
175 if ( config
. donerequests
== config
. requests
) {
180 if ( config
. keepalive
) {
182 if ( config
. randomkeys
) randomizeClientKey ( c
);
184 config
. liveclients
--;
185 createMissingClients ( c
);
186 config
. liveclients
++;
191 static void readHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
) {
198 /* Calculate latency only for the first read event. This means that the
199 * server already sent the reply and we need to parse it. Parsing overhead
200 * is not part of the latency, so calculate it only once, here. */
201 if ( c
-> latency
< 0 ) c
-> latency
= ustime ()-( c
-> start
);
203 if ( redisBufferRead ( c
-> context
) != REDIS_OK
) {
204 fprintf ( stderr
, "Error: %s \n " , c
-> context
-> errstr
);
207 if ( redisGetReply ( c
-> context
,& reply
) != REDIS_OK
) {
208 fprintf ( stderr
, "Error: %s \n " , c
-> context
-> errstr
);
212 if ( reply
== ( void *) REDIS_REPLY_ERROR
) {
213 fprintf ( stderr
, "Unexpected error reply, exiting... \n " );
217 if ( config
. donerequests
< config
. requests
)
218 config
. latency
[ config
. donerequests
++] = c
-> latency
;
224 static void writeHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
) {
230 if ( c
-> state
== CLIENT_CONNECTING
) {
231 c
-> state
= CLIENT_SENDQUERY
;
235 if ( sdslen ( c
-> obuf
) > c
-> written
) {
236 void * ptr
= c
-> obuf
+ c
-> written
;
237 int nwritten
= write ( c
-> context
-> fd
, ptr
, sdslen ( c
-> obuf
)- c
-> written
);
238 if ( nwritten
== - 1 ) {
240 fprintf ( stderr
, "Writing to socket: %s \n " , strerror ( errno
));
244 c
-> written
+= nwritten
;
245 if ( sdslen ( c
-> obuf
) == c
-> written
) {
246 aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);
247 aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
, readHandler
, c
);
248 c
-> state
= CLIENT_READREPLY
;
253 static client
createClient ( int replytype
) {
254 client c
= zmalloc ( sizeof ( struct _client
));
255 if ( config
. hostsocket
== NULL
) {
256 c
-> context
= redisConnectNonBlock ( config
. hostip
, config
. hostport
);
258 c
-> context
= redisConnectUnixNonBlock ( config
. hostsocket
);
260 if ( c
-> context
-> err
) {
261 fprintf ( stderr
, "Could not connect to Redis at " );
262 if ( config
. hostsocket
== NULL
)
263 fprintf ( stderr
, " %s : %d : %s \n " , config
. hostip
, config
. hostport
, c
-> context
-> errstr
);
265 fprintf ( stderr
, " %s : %s \n " , config
. hostsocket
, c
-> context
-> errstr
);
268 c
-> replytype
= replytype
;
269 c
-> state
= CLIENT_CONNECTING
;
271 c
-> randptr
= ( void *)- 1 ;
273 redisSetReplyObjectFunctions ( c
-> context
, NULL
);
274 aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
, writeHandler
, c
);
275 listAddNodeTail ( config
. clients
, c
);
276 config
. liveclients
++;
280 static void createMissingClients ( client c
) {
281 while ( config
. liveclients
< config
. numclients
) {
282 client
new = createClient ( c
-> replytype
);
283 new -> obuf
= sdsdup ( c
-> obuf
);
284 if ( config
. randomkeys
) randomizeClientKey ( c
);
288 static int compareLatency ( const void * a
, const void * b
) {
289 return (*( long long *) a
)-(*( long long *) b
);
292 static void showLatencyReport ( void ) {
294 float perc
, reqpersec
;
296 reqpersec
= ( float ) config
. donerequests
/(( float ) config
. totlatency
/ 1000 );
298 printf ( "====== %s ====== \n " , config
. title
);
299 printf ( " %d requests completed in %.2f seconds \n " , config
. donerequests
,
300 ( float ) config
. totlatency
/ 1000 );
301 printf ( " %d parallel clients \n " , config
. numclients
);
302 printf ( " %d bytes payload \n " , config
. datasize
);
303 printf ( " keep alive: %d \n " , config
. keepalive
);
306 qsort ( config
. latency
, config
. requests
, sizeof ( long long ), compareLatency
);
307 for ( i
= 0 ; i
< config
. requests
; i
++) {
308 if ( config
. latency
[ i
]/ 1000 != curlat
|| i
== ( config
. requests
- 1 )) {
309 curlat
= config
. latency
[ i
]/ 1000 ;
310 perc
= (( float )( i
+ 1 )* 100 )/ config
. requests
;
311 printf ( "%.2f%% <= %d milliseconds \n " , perc
, curlat
);
314 printf ( "%.2f requests per second \n\n " , reqpersec
);
316 printf ( " %s : %.2f requests per second \n " , config
. title
, reqpersec
);
320 static void prepareForBenchmark ( char * title
) {
321 config
. title
= title
;
322 config
. start
= mstime ();
323 config
. donerequests
= 0 ;
326 static void endBenchmark ( void ) {
327 config
. totlatency
= mstime ()- config
. start
;
332 void parseOptions ( int argc
, char ** argv
) {
335 for ( i
= 1 ; i
< argc
; i
++) {
336 int lastarg
= i
== argc
- 1 ;
338 if (! strcmp ( argv
[ i
], "-c" ) && ! lastarg
) {
339 config
. numclients
= atoi ( argv
[ i
+ 1 ]);
341 } else if (! strcmp ( argv
[ i
], "-n" ) && ! lastarg
) {
342 config
. requests
= atoi ( argv
[ i
+ 1 ]);
344 } else if (! strcmp ( argv
[ i
], "-k" ) && ! lastarg
) {
345 config
. keepalive
= atoi ( argv
[ i
+ 1 ]);
347 } else if (! strcmp ( argv
[ i
], "-h" ) && ! lastarg
) {
348 config
. hostip
= argv
[ i
+ 1 ];
350 } else if (! strcmp ( argv
[ i
], "-p" ) && ! lastarg
) {
351 config
. hostport
= atoi ( argv
[ i
+ 1 ]);
353 } else if (! strcmp ( argv
[ i
], "-s" ) && ! lastarg
) {
354 config
. hostsocket
= argv
[ i
+ 1 ];
356 } else if (! strcmp ( argv
[ i
], "-d" ) && ! lastarg
) {
357 config
. datasize
= atoi ( argv
[ i
+ 1 ]);
359 if ( config
. datasize
< 1 ) config
. datasize
= 1 ;
360 if ( config
. datasize
> 1024 * 1024 ) config
. datasize
= 1024 * 1024 ;
361 } else if (! strcmp ( argv
[ i
], "-r" ) && ! lastarg
) {
362 config
. randomkeys
= 1 ;
363 config
. randomkeys_keyspacelen
= atoi ( argv
[ i
+ 1 ]);
364 if ( config
. randomkeys_keyspacelen
< 0 )
365 config
. randomkeys_keyspacelen
= 0 ;
367 } else if (! strcmp ( argv
[ i
], "-q" )) {
369 } else if (! strcmp ( argv
[ i
], "-l" )) {
371 } else if (! strcmp ( argv
[ i
], "-D" )) {
373 } else if (! strcmp ( argv
[ i
], "-I" )) {
376 printf ( "Wrong option ' %s ' or option argument missing \n\n " , argv
[ i
]);
377 printf ( "Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>] \n\n " );
378 printf ( " -h <hostname> Server hostname (default 127.0.0.1) \n " );
379 printf ( " -p <port> Server port (default 6379) \n " );
380 printf ( " -s <socket> Server socket (overrides host and port) \n " );
381 printf ( " -c <clients> Number of parallel connections (default 50) \n " );
382 printf ( " -n <requests> Total number of requests (default 10000) \n " );
383 printf ( " -d <size> Data size of SET/GET value in bytes (default 2) \n " );
384 printf ( " -k <boolean> 1=keep alive 0=reconnect (default 1) \n " );
385 printf ( " -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD \n " );
386 printf ( " Using this option the benchmark will get/set keys \n " );
387 printf ( " in the form mykey_rand000000012456 instead of constant \n " );
388 printf ( " keys, the <keyspacelen> argument determines the max \n " );
389 printf ( " number of values for the random number. For instance \n " );
390 printf ( " if set to 10 only rand000000000000 - rand000000000009 \n " );
391 printf ( " range will be allowed. \n " );
392 printf ( " -q Quiet. Just show query/sec values \n " );
393 printf ( " -l Loop. Run the tests forever \n " );
394 printf ( " -I Idle mode. Just open N idle connections and wait. \n " );
395 printf ( " -D Debug mode. more verbose. \n " );
401 int showThroughput ( struct aeEventLoop
* eventLoop
, long long id
, void * clientData
) {
402 REDIS_NOTUSED ( eventLoop
);
404 REDIS_NOTUSED ( clientData
);
406 float dt
= ( float )( mstime ()- config
. start
)/ 1000.0 ;
407 float rps
= ( float ) config
. donerequests
/ dt
;
408 printf ( " %s : %.2f \r " , config
. title
, rps
);
410 return 250 ; /* every 250ms */
413 int main ( int argc
, char ** argv
) {
417 signal ( SIGHUP
, SIG_IGN
);
418 signal ( SIGPIPE
, SIG_IGN
);
421 config
. numclients
= 50 ;
422 config
. requests
= 10000 ;
423 config
. liveclients
= 0 ;
424 config
. el
= aeCreateEventLoop ();
425 aeCreateTimeEvent ( config
. el
, 1 , showThroughput
, NULL
, NULL
);
426 config
. keepalive
= 1 ;
427 config
. donerequests
= 0 ;
429 config
. randomkeys
= 0 ;
430 config
. randomkeys_keyspacelen
= 0 ;
434 config
. latency
= NULL
;
435 config
. clients
= listCreate ();
436 config
. hostip
= "127.0.0.1" ;
437 config
. hostport
= 6379 ;
438 config
. hostsocket
= NULL
;
440 parseOptions ( argc
, argv
);
441 config
. latency
= zmalloc ( sizeof ( long long )* config
. requests
);
443 if ( config
. keepalive
== 0 ) {
444 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 " );
447 if ( config
. idlemode
) {
448 printf ( "Creating %d idle connections and waiting forever (Ctrl+C when done) \n " , config
. numclients
);
449 prepareForBenchmark ( "IDLE" );
450 c
= createClient ( 0 ); /* will never receive a reply */
451 c
-> obuf
= sdsempty ();
452 createMissingClients ( c
);
454 /* and will wait for every */
461 data
= zmalloc ( config
. datasize
+ 1 );
462 memset ( data
, 'x' , config
. datasize
);
463 data
[ config
. datasize
] = '\0' ;
465 prepareForBenchmark ( "PING (inline)" );
466 c
= createClient ( REDIS_REPLY_STATUS
);
467 c
-> obuf
= sdscat ( sdsempty (), "PING \r\n " );
468 createMissingClients ( c
);
472 prepareForBenchmark ( "PING" );
473 c
= createClient ( REDIS_REPLY_STATUS
);
474 len
= redisFormatCommand (& cmd
, "PING" );
475 c
-> obuf
= sdsnewlen ( cmd
, len
);
477 createMissingClients ( c
);
481 prepareForBenchmark ( "MSET (10 keys)" );
482 c
= createClient ( REDIS_REPLY_ARRAY
);
484 const char * argv
[ 11 ];
486 for ( i
= 1 ; i
< 11 ; i
++)
488 len
= redisFormatCommandArgv (& cmd
, 11 , argv
, NULL
);
489 c
-> obuf
= sdsnewlen ( cmd
, len
);
492 createMissingClients ( c
);
496 prepareForBenchmark ( "SET" );
497 c
= createClient ( REDIS_REPLY_STATUS
);
498 len
= redisFormatCommand (& cmd
, "SET foo:rand:000000000000 %s " , data
);
499 c
-> obuf
= sdsnewlen ( cmd
, len
);
501 createMissingClients ( c
);
505 prepareForBenchmark ( "GET" );
506 c
= createClient ( REDIS_REPLY_STRING
);
507 len
= redisFormatCommand (& cmd
, "GET foo:rand:000000000000" );
508 c
-> obuf
= sdsnewlen ( cmd
, len
);
510 createMissingClients ( c
);
514 prepareForBenchmark ( "INCR" );
515 c
= createClient ( REDIS_REPLY_INTEGER
);
516 len
= redisFormatCommand (& cmd
, "INCR counter:rand:000000000000" );
517 c
-> obuf
= sdsnewlen ( cmd
, len
);
519 createMissingClients ( c
);
523 prepareForBenchmark ( "LPUSH" );
524 c
= createClient ( REDIS_REPLY_INTEGER
);
525 len
= redisFormatCommand (& cmd
, "LPUSH mylist %s " , data
);
526 c
-> obuf
= sdsnewlen ( cmd
, len
);
528 createMissingClients ( c
);
532 prepareForBenchmark ( "LPOP" );
533 c
= createClient ( REDIS_REPLY_STRING
);
534 len
= redisFormatCommand (& cmd
, "LPOP mylist" );
535 c
-> obuf
= sdsnewlen ( cmd
, len
);
537 createMissingClients ( c
);
541 prepareForBenchmark ( "SADD" );
542 c
= createClient ( REDIS_REPLY_STATUS
);
543 len
= redisFormatCommand (& cmd
, "SADD myset counter:rand:000000000000" );
544 c
-> obuf
= sdsnewlen ( cmd
, len
);
546 createMissingClients ( c
);
550 prepareForBenchmark ( "SPOP" );
551 c
= createClient ( REDIS_REPLY_STRING
);
552 len
= redisFormatCommand (& cmd
, "SPOP myset" );
553 c
-> obuf
= sdsnewlen ( cmd
, len
);
555 createMissingClients ( c
);
559 prepareForBenchmark ( "LPUSH (again, in order to bench LRANGE)" );
560 c
= createClient ( REDIS_REPLY_STATUS
);
561 len
= redisFormatCommand (& cmd
, "LPUSH mylist %s " , data
);
562 c
-> obuf
= sdsnewlen ( cmd
, len
);
564 createMissingClients ( c
);
568 prepareForBenchmark ( "LRANGE (first 100 elements)" );
569 c
= createClient ( REDIS_REPLY_ARRAY
);
570 len
= redisFormatCommand (& cmd
, "LRANGE mylist 0 99" );
571 c
-> obuf
= sdsnewlen ( cmd
, len
);
573 createMissingClients ( c
);
577 prepareForBenchmark ( "LRANGE (first 300 elements)" );
578 c
= createClient ( REDIS_REPLY_ARRAY
);
579 len
= redisFormatCommand (& cmd
, "LRANGE mylist 0 299" );
580 c
-> obuf
= sdsnewlen ( cmd
, len
);
582 createMissingClients ( c
);
586 prepareForBenchmark ( "LRANGE (first 450 elements)" );
587 c
= createClient ( REDIS_REPLY_ARRAY
);
588 len
= redisFormatCommand (& cmd
, "LRANGE mylist 0 449" );
589 c
-> obuf
= sdsnewlen ( cmd
, len
);
591 createMissingClients ( c
);
595 prepareForBenchmark ( "LRANGE (first 600 elements)" );
596 c
= createClient ( REDIS_REPLY_ARRAY
);
597 len
= redisFormatCommand (& cmd
, "LRANGE mylist 0 599" );
598 c
-> obuf
= sdsnewlen ( cmd
, len
);
600 createMissingClients ( c
);
605 } while ( config
. loop
);