]>
git.saurik.com Git - redis.git/blob - benchmark.c
b550196c8cea211353bcbff9f5fd35570ff98c04
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.
47 #define REPLY_RETCODE 1
50 #define CLIENT_CONNECTING 0
51 #define CLIENT_SENDQUERY 1
52 #define CLIENT_READREPLY 2
54 #define MAX_LATENCY 5000
56 #define REDIS_NOTUSED(V) ((void) V)
58 static struct config
{
66 int randomkeys_keyspacelen
;
79 typedef struct _client
{
84 int readlen
; /* readlen == -1 means read a single line */
85 unsigned int written
; /* bytes of 'obuf' already written */
87 long long start
; /* start time in milliseconds */
91 static void writeHandler(aeEventLoop
*el
, int fd
, void *privdata
, int mask
);
92 static void createMissingClients(client c
);
95 static long long mstime(void) {
99 gettimeofday(&tv
, NULL
);
100 mst
= ((long)tv
.tv_sec
)*1000;
101 mst
+= tv
.tv_usec
/1000;
105 static void freeClient(client c
) {
108 aeDeleteFileEvent(config
.el
,c
->fd
,AE_WRITABLE
);
109 aeDeleteFileEvent(config
.el
,c
->fd
,AE_READABLE
);
114 config
.liveclients
--;
115 ln
= listSearchKey(config
.clients
,c
);
117 listDelNode(config
.clients
,ln
);
120 static void freeAllClients(void) {
121 listNode
*ln
= config
.clients
->head
, *next
;
125 freeClient(ln
->value
);
130 static void resetClient(client c
) {
131 aeDeleteFileEvent(config
.el
,c
->fd
,AE_WRITABLE
);
132 aeDeleteFileEvent(config
.el
,c
->fd
,AE_READABLE
);
133 aeCreateFileEvent(config
.el
,c
->fd
, AE_WRITABLE
,writeHandler
,c
,NULL
);
135 c
->ibuf
= sdsempty();
136 c
->readlen
= (c
->replytype
== REPLY_BULK
) ? -1 : 0;
138 c
->state
= CLIENT_SENDQUERY
;
140 createMissingClients(c
);
143 static void randomizeClientKey(client c
) {
148 p
= strstr(c
->obuf
, "_rand");
151 r
= random() % config
.randomkeys_keyspacelen
;
152 sprintf(buf
,"%ld",r
);
153 memcpy(p
,buf
,strlen(buf
));
156 static void clientDone(client c
) {
158 config
.donerequests
++;
159 latency
= mstime() - c
->start
;
160 if (latency
> MAX_LATENCY
) latency
= MAX_LATENCY
;
161 config
.latency
[latency
]++;
163 if (config
.donerequests
== config
.requests
) {
168 if (config
.keepalive
) {
170 if (config
.randomkeys
) randomizeClientKey(c
);
172 config
.liveclients
--;
173 createMissingClients(c
);
174 config
.liveclients
++;
179 static void readHandler(aeEventLoop
*el
, int fd
, void *privdata
, int mask
)
188 nread
= read(c
->fd
, buf
, 1024);
190 fprintf(stderr
, "Reading from socket: %s\n", strerror(errno
));
195 fprintf(stderr
, "EOF from client\n");
199 c
->ibuf
= sdscatlen(c
->ibuf
,buf
,nread
);
201 if (c
->replytype
== REPLY_INT
||
202 c
->replytype
== REPLY_RETCODE
||
203 (c
->replytype
== REPLY_BULK
&& c
->readlen
== -1)) {
206 if ((p
= strchr(c
->ibuf
,'\n')) != NULL
) {
207 if (c
->replytype
== REPLY_BULK
) {
210 c
->readlen
= atoi(c
->ibuf
+1)+2;
211 if (c
->readlen
-2 == -1) {
215 c
->ibuf
= sdsrange(c
->ibuf
,(p
-c
->ibuf
)+1,-1);
217 c
->ibuf
= sdstrim(c
->ibuf
,"\r\n");
224 if ((unsigned)c
->readlen
== sdslen(c
->ibuf
))
228 static void writeHandler(aeEventLoop
*el
, int fd
, void *privdata
, int mask
)
235 if (c
->state
== CLIENT_CONNECTING
) {
236 c
->state
= CLIENT_SENDQUERY
;
239 if (sdslen(c
->obuf
) > c
->written
) {
240 void *ptr
= c
->obuf
+c
->written
;
241 int len
= sdslen(c
->obuf
) - c
->written
;
242 int nwritten
= write(c
->fd
, ptr
, len
);
243 if (nwritten
== -1) {
244 fprintf(stderr
, "Writing to socket: %s\n", strerror(errno
));
248 c
->written
+= nwritten
;
249 if (sdslen(c
->obuf
) == c
->written
) {
250 aeDeleteFileEvent(config
.el
,c
->fd
,AE_WRITABLE
);
251 aeCreateFileEvent(config
.el
,c
->fd
,AE_READABLE
,readHandler
,c
,NULL
);
252 c
->state
= CLIENT_READREPLY
;
257 static client
createClient(void) {
258 client c
= zmalloc(sizeof(struct _client
));
259 char err
[ANET_ERR_LEN
];
261 c
->fd
= anetTcpNonBlockConnect(err
,config
.hostip
,config
.hostport
);
262 if (c
->fd
== ANET_ERR
) {
264 fprintf(stderr
,"Connect: %s\n",err
);
267 anetTcpNoDelay(NULL
,c
->fd
);
268 c
->obuf
= sdsempty();
269 c
->ibuf
= sdsempty();
272 c
->state
= CLIENT_CONNECTING
;
273 aeCreateFileEvent(config
.el
, c
->fd
, AE_WRITABLE
, writeHandler
, c
, NULL
);
274 config
.liveclients
++;
275 listAddNodeTail(config
.clients
,c
);
279 static void createMissingClients(client c
) {
280 while(config
.liveclients
< config
.numclients
) {
281 client
new = createClient();
284 new->obuf
= sdsdup(c
->obuf
);
285 if (config
.randomkeys
) randomizeClientKey(c
);
286 new->replytype
= c
->replytype
;
287 if (c
->replytype
== REPLY_BULK
)
292 static void showLatencyReport(char *title
) {
294 float perc
, reqpersec
;
296 reqpersec
= (float)config
.donerequests
/((float)config
.totlatency
/1000);
298 printf("====== %s ======\n", 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
);
305 for (j
= 0; j
<= MAX_LATENCY
; j
++) {
306 if (config
.latency
[j
]) {
307 seen
+= config
.latency
[j
];
308 perc
= ((float)seen
*100)/config
.donerequests
;
309 printf("%.2f%% <= %d milliseconds\n", perc
, j
);
312 printf("%.2f requests per second\n\n", reqpersec
);
314 printf("%s: %.2f requests per second\n", title
, reqpersec
);
318 static void prepareForBenchmark(void)
320 memset(config
.latency
,0,sizeof(int)*(MAX_LATENCY
+1));
321 config
.start
= mstime();
322 config
.donerequests
= 0;
325 static void endBenchmark(char *title
) {
326 config
.totlatency
= mstime()-config
.start
;
327 showLatencyReport(title
);
331 void parseOptions(int argc
, char **argv
) {
334 for (i
= 1; i
< argc
; i
++) {
335 int lastarg
= i
==argc
-1;
337 if (!strcmp(argv
[i
],"-c") && !lastarg
) {
338 config
.numclients
= atoi(argv
[i
+1]);
340 } else if (!strcmp(argv
[i
],"-n") && !lastarg
) {
341 config
.requests
= atoi(argv
[i
+1]);
343 } else if (!strcmp(argv
[i
],"-k") && !lastarg
) {
344 config
.keepalive
= atoi(argv
[i
+1]);
346 } else if (!strcmp(argv
[i
],"-h") && !lastarg
) {
347 char *ip
= zmalloc(32);
348 if (anetResolve(NULL
,argv
[i
+1],ip
) == ANET_ERR
) {
349 printf("Can't resolve %s\n", argv
[i
]);
354 } else if (!strcmp(argv
[i
],"-p") && !lastarg
) {
355 config
.hostport
= atoi(argv
[i
+1]);
357 } else if (!strcmp(argv
[i
],"-d") && !lastarg
) {
358 config
.datasize
= atoi(argv
[i
+1]);
360 if (config
.datasize
< 1) config
.datasize
=1;
361 if (config
.datasize
> 1024*1024) config
.datasize
= 1024*1024;
362 } else if (!strcmp(argv
[i
],"-r") && !lastarg
) {
363 config
.randomkeys
= 1;
364 config
.randomkeys_keyspacelen
= atoi(argv
[i
+1]);
365 if (config
.randomkeys_keyspacelen
< 0)
366 config
.randomkeys_keyspacelen
= 0;
368 } else if (!strcmp(argv
[i
],"-q")) {
370 } else if (!strcmp(argv
[i
],"-l")) {
373 printf("Wrong option '%s' or option argument missing\n\n",argv
[i
]);
374 printf("Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n");
375 printf(" -h <hostname> Server hostname (default 127.0.0.1)\n");
376 printf(" -p <hostname> Server port (default 6379)\n");
377 printf(" -c <clients> Number of parallel connections (default 50)\n");
378 printf(" -n <requests> Total number of requests (default 10000)\n");
379 printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n");
380 printf(" -k <boolean> 1=keep alive 0=reconnect (default 1)\n");
381 printf(" -r <keyspacelen> Use random keys for SET/GET/INCR\n");
382 printf(" Using this option the benchmark will get/set keys\n");
383 printf(" in the form mykey_rand000000012456 instead of constant\n");
384 printf(" keys, the <keyspacelen> argument determines the max\n");
385 printf(" number of values for the random number. For instance\n");
386 printf(" if set to 10 only rand000000000000 - rand000000000009\n");
387 printf(" range will be allowed.\n");
388 printf(" -q Quiet. Just show query/sec values\n");
389 printf(" -l Loop. Run the tests forever\n");
395 int main(int argc
, char **argv
) {
398 signal(SIGHUP
, SIG_IGN
);
399 signal(SIGPIPE
, SIG_IGN
);
401 config
.numclients
= 50;
402 config
.requests
= 10000;
403 config
.liveclients
= 0;
404 config
.el
= aeCreateEventLoop();
405 config
.keepalive
= 1;
406 config
.donerequests
= 0;
408 config
.randomkeys
= 0;
409 config
.randomkeys_keyspacelen
= 0;
412 config
.latency
= NULL
;
413 config
.clients
= listCreate();
414 config
.latency
= zmalloc(sizeof(int)*(MAX_LATENCY
+1));
416 config
.hostip
= "127.0.0.1";
417 config
.hostport
= 6379;
419 parseOptions(argc
,argv
);
421 if (config
.keepalive
== 0) {
422 printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' in order to use a lot of clients/requests\n");
426 prepareForBenchmark();
429 c
->obuf
= sdscat(c
->obuf
,"PING\r\n");
430 c
->replytype
= REPLY_RETCODE
;
431 createMissingClients(c
);
433 endBenchmark("PING");
435 prepareForBenchmark();
438 c
->obuf
= sdscatprintf(c
->obuf
,"SET foo_rand000000000000 %d\r\n",config
.datasize
);
440 char *data
= zmalloc(config
.datasize
+2);
441 memset(data
,'x',config
.datasize
);
442 data
[config
.datasize
] = '\r';
443 data
[config
.datasize
+1] = '\n';
444 c
->obuf
= sdscatlen(c
->obuf
,data
,config
.datasize
+2);
446 c
->replytype
= REPLY_RETCODE
;
447 createMissingClients(c
);
451 prepareForBenchmark();
454 c
->obuf
= sdscat(c
->obuf
,"GET foo_rand000000000000\r\n");
455 c
->replytype
= REPLY_BULK
;
457 createMissingClients(c
);
461 prepareForBenchmark();
464 c
->obuf
= sdscat(c
->obuf
,"INCR counter_rand000000000000\r\n");
465 c
->replytype
= REPLY_INT
;
466 createMissingClients(c
);
468 endBenchmark("INCR");
470 prepareForBenchmark();
473 c
->obuf
= sdscat(c
->obuf
,"LPUSH mylist 3\r\nbar\r\n");
474 c
->replytype
= REPLY_INT
;
475 createMissingClients(c
);
477 endBenchmark("LPUSH");
479 prepareForBenchmark();
482 c
->obuf
= sdscat(c
->obuf
,"LPOP mylist\r\n");
483 c
->replytype
= REPLY_BULK
;
485 createMissingClients(c
);
487 endBenchmark("LPOP");
490 } while(config
.loop
);