]>
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
;
86 typedef struct _client
{
87 redisContext
* context
;
90 unsigned int written
; /* bytes of 'obuf' already written */
92 long long start
; /* start time in milliseconds */
96 static void writeHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
);
97 static void createMissingClients ( client c
);
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
);
138 c
-> state
= CLIENT_SENDQUERY
;
142 static void randomizeClientKey ( client c
) {
147 p
= strstr ( c
-> obuf
, "_rand" );
150 r
= random () % config
. randomkeys_keyspacelen
;
151 sprintf ( buf
, " %l d" , r
);
152 memcpy ( p
, buf
, strlen ( buf
));
155 static void clientDone ( client c
) {
157 config
. donerequests
++;
158 latency
= mstime () - c
-> start
;
159 if ( latency
> MAX_LATENCY
) latency
= MAX_LATENCY
;
160 config
. latency
[ latency
]++;
162 if ( config
. donerequests
== config
. requests
) {
167 if ( config
. keepalive
) {
169 if ( config
. randomkeys
) randomizeClientKey ( c
);
171 config
. liveclients
--;
172 createMissingClients ( c
);
173 config
. liveclients
++;
178 static void readHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
) {
185 if ( redisBufferRead ( c
-> context
) != REDIS_OK
) {
186 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
);
198 static void writeHandler ( aeEventLoop
* el
, int fd
, void * privdata
, int mask
) {
204 if ( c
-> state
== CLIENT_CONNECTING
) {
205 c
-> state
= CLIENT_SENDQUERY
;
208 if ( sdslen ( c
-> obuf
) > c
-> written
) {
209 void * ptr
= c
-> obuf
+ c
-> written
;
210 int nwritten
= write ( c
-> context
-> fd
, ptr
, sdslen ( c
-> obuf
)- c
-> written
);
211 if ( nwritten
== - 1 ) {
213 fprintf ( stderr
, "Writing to socket: %s \n " , strerror ( errno
));
217 c
-> written
+= nwritten
;
218 if ( sdslen ( c
-> obuf
) == c
-> written
) {
219 aeDeleteFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
);
220 aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_READABLE
, readHandler
, c
);
221 c
-> state
= CLIENT_READREPLY
;
226 static client
createClient ( int replytype
) {
227 client c
= zmalloc ( sizeof ( struct _client
));
228 if ( config
. hostsocket
== NULL
) {
229 c
-> context
= redisConnectNonBlock ( config
. hostip
, config
. hostport
);
231 c
-> context
= redisConnectUnixNonBlock ( config
. hostsocket
);
233 if ( c
-> context
-> err
) {
234 fprintf ( stderr
, "Could not connect to Redis at " );
235 if ( config
. hostsocket
== NULL
)
236 fprintf ( stderr
, " %s : %d : %s \n " , config
. hostip
, config
. hostport
, c
-> context
-> errstr
);
238 fprintf ( stderr
, " %s : %s \n " , config
. hostsocket
, c
-> context
-> errstr
);
241 c
-> replytype
= replytype
;
242 c
-> state
= CLIENT_CONNECTING
;
243 c
-> obuf
= sdsempty ();
245 redisSetReplyObjectFunctions ( c
-> context
, NULL
);
246 aeCreateFileEvent ( config
. el
, c
-> context
-> fd
, AE_WRITABLE
, writeHandler
, c
);
247 listAddNodeTail ( config
. clients
, c
);
248 config
. liveclients
++;
252 static void createMissingClients ( client c
) {
253 while ( config
. liveclients
< config
. numclients
) {
254 client
new = createClient ( c
-> replytype
);
256 new -> obuf
= sdsdup ( c
-> obuf
);
257 if ( config
. randomkeys
) randomizeClientKey ( c
);
261 static void showLatencyReport ( void ) {
263 float perc
, reqpersec
;
265 reqpersec
= ( float ) config
. donerequests
/(( float ) config
. totlatency
/ 1000 );
267 printf ( "====== %s ====== \n " , config
. title
);
268 printf ( " %d requests completed in %.2f seconds \n " , config
. donerequests
,
269 ( float ) config
. totlatency
/ 1000 );
270 printf ( " %d parallel clients \n " , config
. numclients
);
271 printf ( " %d bytes payload \n " , config
. datasize
);
272 printf ( " keep alive: %d \n " , config
. keepalive
);
274 for ( j
= 0 ; j
<= MAX_LATENCY
; j
++) {
275 if ( config
. latency
[ j
]) {
276 seen
+= config
. latency
[ j
];
277 perc
= (( float ) seen
* 100 )/ config
. donerequests
;
278 printf ( "%.2f%% <= %d milliseconds \n " , perc
, j
);
281 printf ( "%.2f requests per second \n\n " , reqpersec
);
283 printf ( " %s : %.2f requests per second \n " , config
. title
, reqpersec
);
287 static void prepareForBenchmark ( char * title
) {
288 memset ( config
. latency
, 0 , sizeof ( int )*( MAX_LATENCY
+ 1 ));
289 config
. title
= title
;
290 config
. start
= mstime ();
291 config
. donerequests
= 0 ;
294 static void endBenchmark ( void ) {
295 config
. totlatency
= mstime ()- config
. start
;
300 void parseOptions ( int argc
, char ** argv
) {
303 for ( i
= 1 ; i
< argc
; i
++) {
304 int lastarg
= i
== argc
- 1 ;
306 if (! strcmp ( argv
[ i
], "-c" ) && ! lastarg
) {
307 config
. numclients
= atoi ( argv
[ i
+ 1 ]);
309 } else if (! strcmp ( argv
[ i
], "-n" ) && ! lastarg
) {
310 config
. requests
= atoi ( argv
[ i
+ 1 ]);
312 } else if (! strcmp ( argv
[ i
], "-k" ) && ! lastarg
) {
313 config
. keepalive
= atoi ( argv
[ i
+ 1 ]);
315 } else if (! strcmp ( argv
[ i
], "-h" ) && ! lastarg
) {
316 config
. hostip
= argv
[ i
+ 1 ];
318 } else if (! strcmp ( argv
[ i
], "-p" ) && ! lastarg
) {
319 config
. hostport
= atoi ( argv
[ i
+ 1 ]);
321 } else if (! strcmp ( argv
[ i
], "-s" ) && ! lastarg
) {
322 config
. hostsocket
= argv
[ i
+ 1 ];
324 } else if (! strcmp ( argv
[ i
], "-d" ) && ! lastarg
) {
325 config
. datasize
= atoi ( argv
[ i
+ 1 ]);
327 if ( config
. datasize
< 1 ) config
. datasize
= 1 ;
328 if ( config
. datasize
> 1024 * 1024 ) config
. datasize
= 1024 * 1024 ;
329 } else if (! strcmp ( argv
[ i
], "-r" ) && ! lastarg
) {
330 config
. randomkeys
= 1 ;
331 config
. randomkeys_keyspacelen
= atoi ( argv
[ i
+ 1 ]);
332 if ( config
. randomkeys_keyspacelen
< 0 )
333 config
. randomkeys_keyspacelen
= 0 ;
335 } else if (! strcmp ( argv
[ i
], "-q" )) {
337 } else if (! strcmp ( argv
[ i
], "-l" )) {
339 } else if (! strcmp ( argv
[ i
], "-D" )) {
341 } else if (! strcmp ( argv
[ i
], "-I" )) {
344 printf ( "Wrong option ' %s ' or option argument missing \n\n " , argv
[ i
]);
345 printf ( "Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>] \n\n " );
346 printf ( " -h <hostname> Server hostname (default 127.0.0.1) \n " );
347 printf ( " -p <port> Server port (default 6379) \n " );
348 printf ( " -s <socket> Server socket (overrides host and port) \n " );
349 printf ( " -c <clients> Number of parallel connections (default 50) \n " );
350 printf ( " -n <requests> Total number of requests (default 10000) \n " );
351 printf ( " -d <size> Data size of SET/GET value in bytes (default 2) \n " );
352 printf ( " -k <boolean> 1=keep alive 0=reconnect (default 1) \n " );
353 printf ( " -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD \n " );
354 printf ( " Using this option the benchmark will get/set keys \n " );
355 printf ( " in the form mykey_rand000000012456 instead of constant \n " );
356 printf ( " keys, the <keyspacelen> argument determines the max \n " );
357 printf ( " number of values for the random number. For instance \n " );
358 printf ( " if set to 10 only rand000000000000 - rand000000000009 \n " );
359 printf ( " range will be allowed. \n " );
360 printf ( " -q Quiet. Just show query/sec values \n " );
361 printf ( " -l Loop. Run the tests forever \n " );
362 printf ( " -I Idle mode. Just open N idle connections and wait. \n " );
363 printf ( " -D Debug mode. more verbose. \n " );
369 int showThroughput ( struct aeEventLoop
* eventLoop
, long long id
, void * clientData
) {
370 REDIS_NOTUSED ( eventLoop
);
372 REDIS_NOTUSED ( clientData
);
374 float dt
= ( float )( mstime ()- config
. start
)/ 1000.0 ;
375 float rps
= ( float ) config
. donerequests
/ dt
;
376 printf ( " %s : %.2f \r " , config
. title
, rps
);
378 return 250 ; /* every 250ms */
381 int main ( int argc
, char ** argv
) {
384 signal ( SIGHUP
, SIG_IGN
);
385 signal ( SIGPIPE
, SIG_IGN
);
388 config
. numclients
= 50 ;
389 config
. requests
= 10000 ;
390 config
. liveclients
= 0 ;
391 config
. el
= aeCreateEventLoop ();
392 aeCreateTimeEvent ( config
. el
, 1 , showThroughput
, NULL
, NULL
);
393 config
. keepalive
= 1 ;
394 config
. donerequests
= 0 ;
396 config
. randomkeys
= 0 ;
397 config
. randomkeys_keyspacelen
= 0 ;
401 config
. latency
= NULL
;
402 config
. clients
= listCreate ();
403 config
. latency
= zmalloc ( sizeof ( int )*( MAX_LATENCY
+ 1 ));
405 config
. hostip
= "127.0.0.1" ;
406 config
. hostport
= 6379 ;
407 config
. hostsocket
= NULL
;
409 parseOptions ( argc
, argv
);
411 if ( config
. keepalive
== 0 ) {
412 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 " );
415 if ( config
. idlemode
) {
416 printf ( "Creating %d idle connections and waiting forever (Ctrl+C when done) \n " , config
. numclients
);
417 prepareForBenchmark ( "IDLE" );
418 c
= createClient ( 0 ); /* will never receive a reply */
419 c
-> obuf
= sdsempty ();
420 createMissingClients ( c
);
422 /* and will wait for every */
426 prepareForBenchmark ( "PING" );
427 c
= createClient ( REDIS_REPLY_STATUS
);
428 c
-> obuf
= sdscat ( c
-> obuf
, "PING \r\n " );
429 createMissingClients ( c
);
433 prepareForBenchmark ( "PING (multi bulk)" );
434 c
= createClient ( REDIS_REPLY_STATUS
);
435 c
-> obuf
= sdscat ( c
-> obuf
, "*1 \r\n $4 \r\n PING \r\n " );
436 createMissingClients ( c
);
440 prepareForBenchmark ( "MSET (10 keys, multi bulk)" );
441 c
= createClient ( REDIS_REPLY_ARRAY
);
442 c
-> obuf
= sdscatprintf ( c
-> obuf
, "* %d \r\n $4 \r\n MSET \r\n " , 11 );
445 char * data
= zmalloc ( config
. datasize
+ 2 );
446 memset ( data
, 'x' , config
. datasize
);
447 for ( i
= 0 ; i
< 10 ; i
++) {
448 c
-> obuf
= sdscatprintf ( c
-> obuf
, "$ %d \r\n %s \r\n " , config
. datasize
, data
);
452 createMissingClients ( c
);
456 prepareForBenchmark ( "SET" );
457 c
= createClient ( REDIS_REPLY_STATUS
);
458 c
-> obuf
= sdscat ( c
-> obuf
, "SET foo_rand000000000000 " );
460 char * data
= zmalloc ( config
. datasize
+ 2 );
461 memset ( data
, 'x' , config
. datasize
);
462 data
[ config
. datasize
] = ' \r ' ;
463 data
[ config
. datasize
+ 1 ] = ' \n ' ;
464 c
-> obuf
= sdscatlen ( c
-> obuf
, data
, config
. datasize
+ 2 );
466 createMissingClients ( c
);
470 prepareForBenchmark ( "GET" );
471 c
= createClient ( REDIS_REPLY_STRING
);
472 c
-> obuf
= sdscat ( c
-> obuf
, "GET foo_rand000000000000 \r\n " );
473 createMissingClients ( c
);
477 prepareForBenchmark ( "INCR" );
478 c
= createClient ( REDIS_REPLY_INTEGER
);
479 c
-> obuf
= sdscat ( c
-> obuf
, "INCR counter_rand000000000000 \r\n " );
480 createMissingClients ( c
);
484 prepareForBenchmark ( "LPUSH" );
485 c
= createClient ( REDIS_REPLY_INTEGER
);
486 c
-> obuf
= sdscat ( c
-> obuf
, "LPUSH mylist bar \r\n " );
487 createMissingClients ( c
);
491 prepareForBenchmark ( "LPOP" );
492 c
= createClient ( REDIS_REPLY_STRING
);
493 c
-> obuf
= sdscat ( c
-> obuf
, "LPOP mylist \r\n " );
494 createMissingClients ( c
);
498 prepareForBenchmark ( "SADD" );
499 c
= createClient ( REDIS_REPLY_STATUS
);
500 c
-> obuf
= sdscat ( c
-> obuf
, "SADD myset counter_rand000000000000 \r\n " );
501 createMissingClients ( c
);
505 prepareForBenchmark ( "SPOP" );
506 c
= createClient ( REDIS_REPLY_STRING
);
507 c
-> obuf
= sdscat ( c
-> obuf
, "SPOP myset \r\n " );
508 createMissingClients ( c
);
512 prepareForBenchmark ( "LPUSH (again, in order to bench LRANGE)" );
513 c
= createClient ( REDIS_REPLY_STATUS
);
514 c
-> obuf
= sdscat ( c
-> obuf
, "LPUSH mylist bar \r\n " );
515 createMissingClients ( c
);
519 prepareForBenchmark ( "LRANGE (first 100 elements)" );
520 c
= createClient ( REDIS_REPLY_ARRAY
);
521 c
-> obuf
= sdscat ( c
-> obuf
, "LRANGE mylist 0 99 \r\n " );
522 createMissingClients ( c
);
526 prepareForBenchmark ( "LRANGE (first 300 elements)" );
527 c
= createClient ( REDIS_REPLY_ARRAY
);
528 c
-> obuf
= sdscat ( c
-> obuf
, "LRANGE mylist 0 299 \r\n " );
529 createMissingClients ( c
);
533 prepareForBenchmark ( "LRANGE (first 450 elements)" );
534 c
= createClient ( REDIS_REPLY_ARRAY
);
535 c
-> obuf
= sdscat ( c
-> obuf
, "LRANGE mylist 0 449 \r\n " );
536 createMissingClients ( c
);
540 prepareForBenchmark ( "LRANGE (first 600 elements)" );
541 c
= createClient ( REDIS_REPLY_ARRAY
);
542 c
-> obuf
= sdscat ( c
-> obuf
, "LRANGE mylist 0 599 \r\n " );
543 createMissingClients ( c
);
548 } while ( config
. loop
);