]>
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
;
75 typedef struct _client
{
76 redisContext
* context
;
78 char * randptr
[ 10 ]; /* needed for MSET against 10 keys */
80 unsigned int written
; /* bytes of 'obuf' already written */
81 long long start
; /* start time of a request */
82 long long latency
; /* request latency */
86 static void writeHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
);
87 static void createMissingClients ( client c
);
90 static long long ustime ( void ) {
94 gettimeofday (& tv
, NULL
);
95 ust
= (( long ) tv
. tv_sec
)* 1000000 ;
100 static long long mstime ( void ) {
104 gettimeofday (& tv
, NULL
);
105 mst
= (( long ) tv
. tv_sec
)* 1000 ;
106 mst
+= tv
. tv_usec
/ 1000 ;
110 static void freeClient ( client c
) {
112 aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);
113 aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
);
114 redisFree ( c
-> context
);
117 config
. liveclients
--;
118 ln
= listSearchKey ( config
. clients
, c
);
120 listDelNode ( config
. clients
, ln
);
123 static void freeAllClients ( void ) {
124 listNode
* ln
= config
. clients
-> head
, * next
;
128 freeClient ( ln
-> value
);
133 static void resetClient ( client c
) {
134 aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);
135 aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
);
136 aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
, writeHandler
, c
);
140 static void randomizeClientKey ( client c
) {
144 for ( i
= 0 ; i
< c
-> randlen
; i
++) {
145 r
= random () % config
. randomkeys_keyspacelen
;
146 snprintf ( buf
, sizeof ( buf
), " %0 12zu" , r
);
147 memcpy ( c
-> randptr
[ i
], buf
, 12 );
151 static void clientDone ( client c
) {
152 if ( config
. requests_finished
== config
. requests
) {
157 if ( config
. keepalive
) {
160 config
. liveclients
--;
161 createMissingClients ( c
);
162 config
. liveclients
++;
167 static void readHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
) {
174 /* Calculate latency only for the first read event. This means that the
175 * server already sent the reply and we need to parse it. Parsing overhead
176 * is not part of the latency, so calculate it only once, here. */
177 if ( c
-> latency
< 0 ) c
-> latency
= ustime ()-( c
-> start
);
179 if ( redisBufferRead ( c
-> context
) != REDIS_OK
) {
180 fprintf ( stderr
, "Error: %s \n " , c
-> context
-> errstr
);
183 if ( redisGetReply ( c
-> context
,& reply
) != REDIS_OK
) {
184 fprintf ( stderr
, "Error: %s \n " , c
-> context
-> errstr
);
188 if ( reply
== ( void *) REDIS_REPLY_ERROR
) {
189 fprintf ( stderr
, "Unexpected error reply, exiting... \n " );
193 if ( config
. requests_finished
< config
. requests
)
194 config
. latency
[ config
. requests_finished
++] = c
-> latency
;
200 static void writeHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
) {
206 /* Initialize request when nothing was written. */
207 if ( c
-> written
== 0 ) {
208 /* Enforce upper bound to number of requests. */
209 if ( config
. requests_issued
++ >= config
. requests
) {
214 /* Really initialize: randomize keys and set start time. */
215 if ( config
. randomkeys
) randomizeClientKey ( c
);
220 if ( sdslen ( c
-> obuf
) > c
-> written
) {
221 void * ptr
= c
-> obuf
+ c
-> written
;
222 int nwritten
= write ( c
-> context
-> fd
, ptr
, sdslen ( c
-> obuf
)- c
-> written
);
223 if ( nwritten
== - 1 ) {
225 fprintf ( stderr
, "Writing to socket: %s \n " , strerror ( errno
));
229 c
-> written
+= nwritten
;
230 if ( sdslen ( c
-> obuf
) == c
-> written
) {
231 aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);
232 aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
, readHandler
, c
);
237 static client
createClient ( const char * cmd
, size_t len
) {
238 client c
= zmalloc ( sizeof ( struct _client
));
239 if ( config
. hostsocket
== NULL
) {
240 c
-> context
= redisConnectNonBlock ( config
. hostip
, config
. hostport
);
242 c
-> context
= redisConnectUnixNonBlock ( config
. hostsocket
);
244 if ( c
-> context
-> err
) {
245 fprintf ( stderr
, "Could not connect to Redis at " );
246 if ( config
. hostsocket
== NULL
)
247 fprintf ( stderr
, " %s : %d : %s \n " , config
. hostip
, config
. hostport
, c
-> context
-> errstr
);
249 fprintf ( stderr
, " %s : %s \n " , config
. hostsocket
, c
-> context
-> errstr
);
252 c
-> obuf
= sdsnewlen ( cmd
, len
);
256 /* Find substrings in the output buffer that need to be randomized. */
257 if ( config
. randomkeys
) {
258 char * p
= c
-> obuf
, * newline
;
259 while (( p
= strstr ( p
, ":rand:" )) != NULL
) {
260 newline
= strstr ( p
, " \r\n " );
261 assert ( newline
-( p
+ 6 ) == 12 ); /* 12 chars for randomness */
262 assert ( c
-> randlen
< ( signed )( sizeof ( c
-> randptr
)/ sizeof ( char *)));
263 c
-> randptr
[ c
-> randlen
++] = p
+ 6 ;
268 redisSetReplyObjectFunctions ( c
-> context
, NULL
);
269 aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
, writeHandler
, c
);
270 listAddNodeTail ( config
. clients
, c
);
271 config
. liveclients
++;
275 static void createMissingClients ( client c
) {
278 while ( config
. liveclients
< config
. numclients
) {
279 createClient ( c
-> obuf
, sdslen ( c
-> obuf
));
281 /* Listen backlog is quite limited on most systems */
289 static int compareLatency ( const void * a
, const void * b
) {
290 return (*( long long *) a
)-(*( long long *) b
);
293 static void showLatencyReport ( void ) {
295 float perc
, reqpersec
;
297 reqpersec
= ( float ) config
. requests_finished
/(( float ) config
. totlatency
/ 1000 );
299 printf ( "====== %s ====== \n " , config
. title
);
300 printf ( " %d requests completed in %.2f seconds \n " , config
. requests_finished
,
301 ( float ) config
. totlatency
/ 1000 );
302 printf ( " %d parallel clients \n " , config
. numclients
);
303 printf ( " %d bytes payload \n " , config
. datasize
);
304 printf ( " keep alive: %d \n " , config
. keepalive
);
307 qsort ( config
. latency
, config
. requests
, sizeof ( long long ), compareLatency
);
308 for ( i
= 0 ; i
< config
. requests
; i
++) {
309 if ( config
. latency
[ i
]/ 1000 != curlat
|| i
== ( config
. requests
- 1 )) {
310 curlat
= config
. latency
[ i
]/ 1000 ;
311 perc
= (( float )( i
+ 1 )* 100 )/ config
. requests
;
312 printf ( "%.2f%% <= %d milliseconds \n " , perc
, curlat
);
315 printf ( "%.2f requests per second \n\n " , reqpersec
);
317 printf ( " %s : %.2f requests per second \n " , config
. title
, reqpersec
);
321 static void benchmark ( const char * title
, const char * cmd
, int len
) {
324 config
. title
= title
;
325 config
. requests_issued
= 0 ;
326 config
. requests_finished
= 0 ;
328 c
= createClient ( cmd
, len
);
329 createMissingClients ( c
);
331 config
. start
= mstime ();
333 config
. totlatency
= mstime ()- config
. start
;
339 /* Returns number of consumed options. */
340 int parseOptions ( int argc
, const char ** argv
) {
345 for ( i
= 1 ; i
< argc
; i
++) {
346 lastarg
= ( i
== ( argc
- 1 ));
348 if (! strcmp ( argv
[ i
], "-c" )) {
349 if ( lastarg
) goto invalid
;
350 config
. numclients
= atoi ( argv
[++ i
]);
351 } else if (! strcmp ( argv
[ i
], "-n" )) {
352 if ( lastarg
) goto invalid
;
353 config
. requests
= atoi ( argv
[++ i
]);
354 } else if (! strcmp ( argv
[ i
], "-k" )) {
355 if ( lastarg
) goto invalid
;
356 config
. keepalive
= atoi ( argv
[++ i
]);
357 } else if (! strcmp ( argv
[ i
], "-h" )) {
358 if ( lastarg
) goto invalid
;
359 config
. hostip
= strdup ( argv
[++ i
]);
360 } else if (! strcmp ( argv
[ i
], "-p" )) {
361 if ( lastarg
) goto invalid
;
362 config
. hostport
= atoi ( argv
[++ i
]);
363 } else if (! strcmp ( argv
[ i
], "-s" )) {
364 if ( lastarg
) goto invalid
;
365 config
. hostsocket
= strdup ( argv
[++ i
]);
366 } else if (! strcmp ( argv
[ i
], "-d" )) {
367 if ( lastarg
) goto invalid
;
368 config
. datasize
= atoi ( argv
[++ i
]);
369 if ( config
. datasize
< 1 ) config
. datasize
= 1 ;
370 if ( config
. datasize
> 1024 * 1024 ) config
. datasize
= 1024 * 1024 ;
371 } else if (! strcmp ( argv
[ i
], "-r" )) {
372 if ( lastarg
) goto invalid
;
373 config
. randomkeys
= 1 ;
374 config
. randomkeys_keyspacelen
= atoi ( argv
[++ i
]);
375 if ( config
. randomkeys_keyspacelen
< 0 )
376 config
. randomkeys_keyspacelen
= 0 ;
377 } else if (! strcmp ( argv
[ i
], "-q" )) {
379 } else if (! strcmp ( argv
[ i
], "-l" )) {
381 } else if (! strcmp ( argv
[ i
], "-I" )) {
383 } else if (! strcmp ( argv
[ i
], "--help" )) {
387 /* Assume the user meant to provide an option when the arg starts
388 * with a dash. We're done otherwise and should use the remainder
389 * as the command and arguments for running the benchmark. */
390 if ( argv
[ i
][ 0 ] == '-' ) goto invalid
;
398 printf ( "Invalid option \" %s \" or option argument missing \n\n " , argv
[ i
]);
401 printf ( "Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>] \n\n " );
402 printf ( " -h <hostname> Server hostname (default 127.0.0.1) \n " );
403 printf ( " -p <port> Server port (default 6379) \n " );
404 printf ( " -s <socket> Server socket (overrides host and port) \n " );
405 printf ( " -c <clients> Number of parallel connections (default 50) \n " );
406 printf ( " -n <requests> Total number of requests (default 10000) \n " );
407 printf ( " -d <size> Data size of SET/GET value in bytes (default 2) \n " );
408 printf ( " -k <boolean> 1=keep alive 0=reconnect (default 1) \n " );
409 printf ( " -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD \n " );
410 printf ( " Using this option the benchmark will get/set keys \n " );
411 printf ( " in the form mykey_rand000000012456 instead of constant \n " );
412 printf ( " keys, the <keyspacelen> argument determines the max \n " );
413 printf ( " number of values for the random number. For instance \n " );
414 printf ( " if set to 10 only rand000000000000 - rand000000000009 \n " );
415 printf ( " range will be allowed. \n " );
416 printf ( " -q Quiet. Just show query/sec values \n " );
417 printf ( " -l Loop. Run the tests forever \n " );
418 printf ( " -I Idle mode. Just open N idle connections and wait. \n " );
422 int showThroughput ( struct aeEventLoop
* eventLoop
, long long id
, void * clientData
) {
423 REDIS_NOTUSED ( eventLoop
);
425 REDIS_NOTUSED ( clientData
);
427 float dt
= ( float )( mstime ()- config
. start
)/ 1000.0 ;
428 float rps
= ( float ) config
. requests_finished
/ dt
;
429 printf ( " %s : %.2f \r " , config
. title
, rps
);
431 return 250 ; /* every 250ms */
434 int main ( int argc
, const char ** argv
) {
441 signal ( SIGHUP
, SIG_IGN
);
442 signal ( SIGPIPE
, SIG_IGN
);
444 config
. numclients
= 50 ;
445 config
. requests
= 10000 ;
446 config
. liveclients
= 0 ;
447 config
. el
= aeCreateEventLoop ();
448 aeCreateTimeEvent ( config
. el
, 1 , showThroughput
, NULL
, NULL
);
449 config
. keepalive
= 1 ;
451 config
. randomkeys
= 0 ;
452 config
. randomkeys_keyspacelen
= 0 ;
456 config
. latency
= NULL
;
457 config
. clients
= listCreate ();
458 config
. hostip
= "127.0.0.1" ;
459 config
. hostport
= 6379 ;
460 config
. hostsocket
= NULL
;
462 i
= parseOptions ( argc
, argv
);
466 config
. latency
= zmalloc ( sizeof ( long long )* config
. requests
);
468 if ( config
. keepalive
== 0 ) {
469 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 " );
472 if ( config
. idlemode
) {
473 printf ( "Creating %d idle connections and waiting forever (Ctrl+C when done) \n " , config
. numclients
);
474 c
= createClient ( "" , 0 ); /* will never receive a reply */
475 createMissingClients ( c
);
477 /* and will wait for every */
480 /* Run benchmark with command in the remainder of the arguments. */
482 sds title
= sdsnew ( argv
[ 0 ]);
483 for ( i
= 1 ; i
< argc
; i
++) {
484 title
= sdscatlen ( title
, " " , 1 );
485 title
= sdscatlen ( title
, ( char *) argv
[ i
], strlen ( argv
[ i
]));
489 len
= redisFormatCommandArgv (& cmd
, argc
, argv
, NULL
);
490 benchmark ( title
, cmd
, len
);
492 } while ( config
. loop
);
497 /* Run default benchmark suite. */
499 data
= zmalloc ( config
. datasize
+ 1 );
500 memset ( data
, 'x' , config
. datasize
);
501 data
[ config
. datasize
] = '\0' ;
503 benchmark ( "PING (inline)" , "PING \r\n " , 6 );
505 len
= redisFormatCommand (& cmd
, "PING" );
506 benchmark ( "PING" , cmd
, len
);
509 const char * argv
[ 21 ];
511 for ( i
= 1 ; i
< 21 ; i
+= 2 ) {
512 argv
[ i
] = "foo:rand:000000000000" ;
515 len
= redisFormatCommandArgv (& cmd
, 21 , argv
, NULL
);
516 benchmark ( "MSET (10 keys)" , cmd
, len
);
519 len
= redisFormatCommand (& cmd
, "SET foo:rand:000000000000 %s " , data
);
520 benchmark ( "SET" , cmd
, len
);
523 len
= redisFormatCommand (& cmd
, "GET foo:rand:000000000000" );
524 benchmark ( "GET" , cmd
, len
);
527 len
= redisFormatCommand (& cmd
, "INCR counter:rand:000000000000" );
528 benchmark ( "INCR" , cmd
, len
);
531 len
= redisFormatCommand (& cmd
, "LPUSH mylist %s " , data
);
532 benchmark ( "LPUSH" , cmd
, len
);
535 len
= redisFormatCommand (& cmd
, "LPOP mylist" );
536 benchmark ( "LPOP" , cmd
, len
);
539 len
= redisFormatCommand (& cmd
, "SADD myset counter:rand:000000000000" );
540 benchmark ( "SADD" , cmd
, len
);
543 len
= redisFormatCommand (& cmd
, "SPOP myset" );
544 benchmark ( "SPOP" , cmd
, len
);
547 len
= redisFormatCommand (& cmd
, "LPUSH mylist %s " , data
);
548 benchmark ( "LPUSH (again, in order to bench LRANGE)" , cmd
, len
);
551 len
= redisFormatCommand (& cmd
, "LRANGE mylist 0 99" );
552 benchmark ( "LRANGE (first 100 elements)" , cmd
, len
);
555 len
= redisFormatCommand (& cmd
, "LRANGE mylist 0 299" );
556 benchmark ( "LRANGE (first 300 elements)" , cmd
, len
);
559 len
= redisFormatCommand (& cmd
, "LRANGE mylist 0 449" );
560 benchmark ( "LRANGE (first 450 elements)" , cmd
, len
);
563 len
= redisFormatCommand (& cmd
, "LRANGE mylist 0 599" );
564 benchmark ( "LRANGE (first 600 elements)" , cmd
, len
);
568 } while ( config
. loop
);