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
;
84 typedef struct _client
{
89 int mbulk
; /* Number of elements in an mbulk reply */
90 int readlen
; /* readlen == -1 means read a single line */
92 unsigned int written
; /* bytes of 'obuf' already written */
94 long long start
; /* start time in milliseconds */
98 static void writeHandler(aeEventLoop
*el
, int fd
, void *privdata
, int mask
);
99 static void createMissingClients(client c
);
102 static long long mstime(void) {
106 gettimeofday(&tv
, NULL
);
107 mst
= ((long)tv
.tv_sec
)*1000;
108 mst
+= tv
.tv_usec
/1000;
112 static void freeClient(client c
) {
115 aeDeleteFileEvent(config
.el
,c
->fd
,AE_WRITABLE
);
116 aeDeleteFileEvent(config
.el
,c
->fd
,AE_READABLE
);
121 config
.liveclients
--;
122 ln
= listSearchKey(config
.clients
,c
);
124 listDelNode(config
.clients
,ln
);
127 static void freeAllClients(void) {
128 listNode
*ln
= config
.clients
->head
, *next
;
132 freeClient(ln
->value
);
137 static void resetClient(client c
) {
138 aeDeleteFileEvent(config
.el
,c
->fd
,AE_WRITABLE
);
139 aeDeleteFileEvent(config
.el
,c
->fd
,AE_READABLE
);
140 aeCreateFileEvent(config
.el
,c
->fd
, AE_WRITABLE
,writeHandler
,c
);
142 c
->ibuf
= sdsempty();
143 c
->readlen
= (c
->replytype
== REPLY_BULK
||
144 c
->replytype
== REPLY_MBULK
) ? -1 : 0;
148 c
->state
= CLIENT_SENDQUERY
;
150 createMissingClients(c
);
153 static void randomizeClientKey(client c
) {
158 p
= strstr(c
->obuf
, "_rand");
161 r
= random() % config
.randomkeys_keyspacelen
;
162 sprintf(buf
,"%ld",r
);
163 memcpy(p
,buf
,strlen(buf
));
166 static void prepareClientForReply(client c
, int type
) {
167 if (type
== REPLY_BULK
) {
168 c
->replytype
= REPLY_BULK
;
170 } else if (type
== REPLY_MBULK
) {
171 c
->replytype
= REPLY_MBULK
;
180 static void clientDone(client c
) {
181 static int last_tot_received
= 1;
184 config
.donerequests
++;
185 latency
= mstime() - c
->start
;
186 if (latency
> MAX_LATENCY
) latency
= MAX_LATENCY
;
187 config
.latency
[latency
]++;
189 if (config
.debug
&& last_tot_received
!= c
->totreceived
) {
190 printf("Tot bytes received: %d\n", c
->totreceived
);
191 last_tot_received
= c
->totreceived
;
193 if (config
.donerequests
== config
.requests
) {
198 if (config
.keepalive
) {
200 if (config
.randomkeys
) randomizeClientKey(c
);
202 config
.liveclients
--;
203 createMissingClients(c
);
204 config
.liveclients
++;
209 static void readHandler(aeEventLoop
*el
, int fd
, void *privdata
, int mask
)
218 nread
= read(c
->fd
, buf
, 1024);
220 fprintf(stderr
, "Reading from socket: %s\n", strerror(errno
));
225 fprintf(stderr
, "EOF from client\n");
229 c
->totreceived
+= nread
;
230 c
->ibuf
= sdscatlen(c
->ibuf
,buf
,nread
);
233 /* Are we waiting for the first line of the command of for sdf
234 * count in bulk or multi bulk operations? */
235 if (c
->replytype
== REPLY_INT
||
236 c
->replytype
== REPLY_RETCODE
||
237 (c
->replytype
== REPLY_BULK
&& c
->readlen
== -1) ||
238 (c
->replytype
== REPLY_MBULK
&& c
->readlen
== -1) ||
239 (c
->replytype
== REPLY_MBULK
&& c
->mbulk
== -1)) {
242 /* Check if the first line is complete. This is only true if
243 * there is a newline inside the buffer. */
244 if ((p
= strchr(c
->ibuf
,'\n')) != NULL
) {
245 if (c
->replytype
== REPLY_BULK
||
246 (c
->replytype
== REPLY_MBULK
&& c
->mbulk
!= -1))
248 /* Read the count of a bulk reply (being it a single bulk or
249 * a multi bulk reply). "$<count>" for the protocol spec. */
252 c
->readlen
= atoi(c
->ibuf
+1)+2;
253 // printf("BULK ATOI: %s\n", c->ibuf+1);
254 /* Handle null bulk reply "$-1" */
255 if (c
->readlen
-2 == -1) {
259 /* Leave all the rest in the input buffer */
260 c
->ibuf
= sdsrange(c
->ibuf
,(p
-c
->ibuf
)+1,-1);
261 /* fall through to reach the point where the code will try
262 * to check if the bulk reply is complete. */
263 } else if (c
->replytype
== REPLY_MBULK
&& c
->mbulk
== -1) {
264 /* Read the count of a multi bulk reply. That is, how many
265 * bulk replies we have to read next. "*<count>" protocol. */
268 c
->mbulk
= atoi(c
->ibuf
+1);
269 /* Handle null bulk reply "*-1" */
270 if (c
->mbulk
== -1) {
274 // printf("%p) %d elements list\n", c, c->mbulk);
275 /* Leave all the rest in the input buffer */
276 c
->ibuf
= sdsrange(c
->ibuf
,(p
-c
->ibuf
)+1,-1);
279 c
->ibuf
= sdstrim(c
->ibuf
,"\r\n");
285 /* bulk read, did we read everything? */
286 if (((c
->replytype
== REPLY_MBULK
&& c
->mbulk
!= -1) ||
287 (c
->replytype
== REPLY_BULK
)) && c
->readlen
!= -1 &&
288 (unsigned)c
->readlen
<= sdslen(c
->ibuf
))
290 // printf("BULKSTATUS mbulk:%d readlen:%d sdslen:%d\n",
291 // c->mbulk,c->readlen,sdslen(c->ibuf));
292 if (c
->replytype
== REPLY_BULK
) {
294 } else if (c
->replytype
== REPLY_MBULK
) {
295 // printf("%p) %d (%d)) ",c, c->mbulk, c->readlen);
296 // fwrite(c->ibuf,c->readlen,1,stdout);
298 if (--c
->mbulk
== 0) {
301 c
->ibuf
= sdsrange(c
->ibuf
,c
->readlen
,-1);
309 static void writeHandler(aeEventLoop
*el
, int fd
, void *privdata
, int mask
)
316 if (c
->state
== CLIENT_CONNECTING
) {
317 c
->state
= CLIENT_SENDQUERY
;
320 if (sdslen(c
->obuf
) > c
->written
) {
321 void *ptr
= c
->obuf
+c
->written
;
322 int len
= sdslen(c
->obuf
) - c
->written
;
323 int nwritten
= write(c
->fd
, ptr
, len
);
324 if (nwritten
== -1) {
326 fprintf(stderr
, "Writing to socket: %s\n", strerror(errno
));
330 c
->written
+= nwritten
;
331 if (sdslen(c
->obuf
) == c
->written
) {
332 aeDeleteFileEvent(config
.el
,c
->fd
,AE_WRITABLE
);
333 aeCreateFileEvent(config
.el
,c
->fd
,AE_READABLE
,readHandler
,c
);
334 c
->state
= CLIENT_READREPLY
;
339 static client
createClient(void) {
340 client c
= zmalloc(sizeof(struct _client
));
341 char err
[ANET_ERR_LEN
];
343 c
->fd
= anetTcpNonBlockConnect(err
,config
.hostip
,config
.hostport
);
344 if (c
->fd
== ANET_ERR
) {
346 fprintf(stderr
,"Connect: %s\n",err
);
349 anetTcpNoDelay(NULL
,c
->fd
);
350 c
->obuf
= sdsempty();
351 c
->ibuf
= sdsempty();
356 c
->state
= CLIENT_CONNECTING
;
357 aeCreateFileEvent(config
.el
, c
->fd
, AE_WRITABLE
, writeHandler
, c
);
358 config
.liveclients
++;
359 listAddNodeTail(config
.clients
,c
);
363 static void createMissingClients(client c
) {
364 while(config
.liveclients
< config
.numclients
) {
365 client
new = createClient();
368 new->obuf
= sdsdup(c
->obuf
);
369 if (config
.randomkeys
) randomizeClientKey(c
);
370 prepareClientForReply(new,c
->replytype
);
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")) {
456 } else if (!strcmp(argv
[i
],"-I")) {
459 printf("Wrong option '%s' or option argument missing\n\n",argv
[i
]);
460 printf("Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n");
461 printf(" -h <hostname> Server hostname (default 127.0.0.1)\n");
462 printf(" -p <hostname> Server port (default 6379)\n");
463 printf(" -c <clients> Number of parallel connections (default 50)\n");
464 printf(" -n <requests> Total number of requests (default 10000)\n");
465 printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n");
466 printf(" -k <boolean> 1=keep alive 0=reconnect (default 1)\n");
467 printf(" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n");
468 printf(" Using this option the benchmark will get/set keys\n");
469 printf(" in the form mykey_rand000000012456 instead of constant\n");
470 printf(" keys, the <keyspacelen> argument determines the max\n");
471 printf(" number of values for the random number. For instance\n");
472 printf(" if set to 10 only rand000000000000 - rand000000000009\n");
473 printf(" range will be allowed.\n");
474 printf(" -q Quiet. Just show query/sec values\n");
475 printf(" -l Loop. Run the tests forever\n");
476 printf(" -I Idle mode. Just open N idle connections and wait.\n");
477 printf(" -D Debug mode. more verbose.\n");
483 int main(int argc
, char **argv
) {
486 signal(SIGHUP
, SIG_IGN
);
487 signal(SIGPIPE
, SIG_IGN
);
490 config
.numclients
= 50;
491 config
.requests
= 10000;
492 config
.liveclients
= 0;
493 config
.el
= aeCreateEventLoop();
494 config
.keepalive
= 1;
495 config
.donerequests
= 0;
497 config
.randomkeys
= 0;
498 config
.randomkeys_keyspacelen
= 0;
502 config
.latency
= NULL
;
503 config
.clients
= listCreate();
504 config
.latency
= zmalloc(sizeof(int)*(MAX_LATENCY
+1));
506 config
.hostip
= "127.0.0.1";
507 config
.hostport
= 6379;
509 parseOptions(argc
,argv
);
511 if (config
.keepalive
== 0) {
512 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");
515 if (config
.idlemode
) {
516 printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config
.numclients
);
517 prepareForBenchmark();
520 c
->obuf
= sdsempty();
521 prepareClientForReply(c
,REPLY_RETCODE
); /* will never receive it */
522 createMissingClients(c
);
524 /* and will wait for every */
528 prepareForBenchmark();
531 c
->obuf
= sdscat(c
->obuf
,"PING\r\n");
532 prepareClientForReply(c
,REPLY_RETCODE
);
533 createMissingClients(c
);
535 endBenchmark("PING");
537 prepareForBenchmark();
540 c
->obuf
= sdscat(c
->obuf
,"*1\r\n$4\r\nPING\r\n");
541 prepareClientForReply(c
,REPLY_RETCODE
);
542 createMissingClients(c
);
544 endBenchmark("PING (multi bulk)");
546 prepareForBenchmark();
549 c
->obuf
= sdscatprintf(c
->obuf
,"SET foo_rand000000000000 %d\r\n",config
.datasize
);
551 char *data
= zmalloc(config
.datasize
+2);
552 memset(data
,'x',config
.datasize
);
553 data
[config
.datasize
] = '\r';
554 data
[config
.datasize
+1] = '\n';
555 c
->obuf
= sdscatlen(c
->obuf
,data
,config
.datasize
+2);
557 prepareClientForReply(c
,REPLY_RETCODE
);
558 createMissingClients(c
);
562 prepareForBenchmark();
565 c
->obuf
= sdscat(c
->obuf
,"GET foo_rand000000000000\r\n");
566 prepareClientForReply(c
,REPLY_BULK
);
567 createMissingClients(c
);
571 prepareForBenchmark();
574 c
->obuf
= sdscat(c
->obuf
,"INCR counter_rand000000000000\r\n");
575 prepareClientForReply(c
,REPLY_INT
);
576 createMissingClients(c
);
578 endBenchmark("INCR");
580 prepareForBenchmark();
583 c
->obuf
= sdscat(c
->obuf
,"LPUSH mylist 3\r\nbar\r\n");
584 prepareClientForReply(c
,REPLY_INT
);
585 createMissingClients(c
);
587 endBenchmark("LPUSH");
589 prepareForBenchmark();
592 c
->obuf
= sdscat(c
->obuf
,"LPOP mylist\r\n");
593 prepareClientForReply(c
,REPLY_BULK
);
594 createMissingClients(c
);
596 endBenchmark("LPOP");
598 prepareForBenchmark();
601 c
->obuf
= sdscat(c
->obuf
,"SADD myset 24\r\ncounter_rand000000000000\r\n");
602 prepareClientForReply(c
,REPLY_RETCODE
);
603 createMissingClients(c
);
605 endBenchmark("SADD");
607 prepareForBenchmark();
610 c
->obuf
= sdscat(c
->obuf
,"SPOP myset\r\n");
611 prepareClientForReply(c
,REPLY_BULK
);
612 createMissingClients(c
);
614 endBenchmark("SPOP");
616 prepareForBenchmark();
619 c
->obuf
= sdscat(c
->obuf
,"LPUSH mylist 3\r\nbar\r\n");
620 prepareClientForReply(c
,REPLY_RETCODE
);
621 createMissingClients(c
);
623 endBenchmark("LPUSH (again, in order to bench LRANGE)");
625 prepareForBenchmark();
628 c
->obuf
= sdscat(c
->obuf
,"LRANGE mylist 0 99\r\n");
629 prepareClientForReply(c
,REPLY_MBULK
);
630 createMissingClients(c
);
632 endBenchmark("LRANGE (first 100 elements)");
634 prepareForBenchmark();
637 c
->obuf
= sdscat(c
->obuf
,"LRANGE mylist 0 299\r\n");
638 prepareClientForReply(c
,REPLY_MBULK
);
639 createMissingClients(c
);
641 endBenchmark("LRANGE (first 300 elements)");
643 prepareForBenchmark();
646 c
->obuf
= sdscat(c
->obuf
,"LRANGE mylist 0 449\r\n");
647 prepareClientForReply(c
,REPLY_MBULK
);
648 createMissingClients(c
);
650 endBenchmark("LRANGE (first 450 elements)");
652 prepareForBenchmark();
655 c
->obuf
= sdscat(c
->obuf
,"LRANGE mylist 0 599\r\n");
656 prepareClientForReply(c
,REPLY_MBULK
);
657 createMissingClients(c
);
659 endBenchmark("LRANGE (first 600 elements)");
662 } while(config
.loop
);