]> git.saurik.com Git - redis.git/blob - src/redis-benchmark.c
Use hiredis from redis-benchmark
[redis.git] / src / redis-benchmark.c
1 /* Redis benchmark utility.
2 *
3 * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
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.
17 *
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.
29 */
30
31 #include "fmacros.h"
32
33 #include <stdio.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <sys/time.h>
39 #include <signal.h>
40 #include <assert.h>
41
42 #include "ae.h"
43 #include "hiredis.h"
44 #include "sds.h"
45 #include "adlist.h"
46 #include "zmalloc.h"
47
48 #define REPLY_INT 0
49 #define REPLY_RETCODE 1
50 #define REPLY_BULK 2
51 #define REPLY_MBULK 3
52
53 #define CLIENT_CONNECTING 0
54 #define CLIENT_SENDQUERY 1
55 #define CLIENT_READREPLY 2
56
57 #define MAX_LATENCY 5000
58
59 #define REDIS_NOTUSED(V) ((void) V)
60
61 static struct config {
62 int debug;
63 int numclients;
64 int requests;
65 int liveclients;
66 int donerequests;
67 int keysize;
68 int datasize;
69 int randomkeys;
70 int randomkeys_keyspacelen;
71 aeEventLoop *el;
72 char *hostip;
73 int hostport;
74 char *hostsocket;
75 int keepalive;
76 long long start;
77 long long totlatency;
78 int *latency;
79 char *title;
80 list *clients;
81 int quiet;
82 int loop;
83 int idlemode;
84 } config;
85
86 typedef struct _client {
87 redisContext *context;
88 int state;
89 sds obuf;
90 unsigned int written; /* bytes of 'obuf' already written */
91 int replytype;
92 long long start; /* start time in milliseconds */
93 } *client;
94
95 /* Prototypes */
96 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
97 static void createMissingClients(client c);
98
99 /* Implementation */
100 static long long mstime(void) {
101 struct timeval tv;
102 long long mst;
103
104 gettimeofday(&tv, NULL);
105 mst = ((long)tv.tv_sec)*1000;
106 mst += tv.tv_usec/1000;
107 return mst;
108 }
109
110 static void freeClient(client c) {
111 listNode *ln;
112 aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
113 aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
114 redisFree(c->context);
115 sdsfree(c->obuf);
116 zfree(c);
117 config.liveclients--;
118 ln = listSearchKey(config.clients,c);
119 assert(ln != NULL);
120 listDelNode(config.clients,ln);
121 }
122
123 static void freeAllClients(void) {
124 listNode *ln = config.clients->head, *next;
125
126 while(ln) {
127 next = ln->next;
128 freeClient(ln->value);
129 ln = next;
130 }
131 }
132
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);
137 c->written = 0;
138 c->state = CLIENT_SENDQUERY;
139 c->start = mstime();
140 }
141
142 static void randomizeClientKey(client c) {
143 char *p;
144 char buf[32];
145 long r;
146
147 p = strstr(c->obuf, "_rand");
148 if (!p) return;
149 p += 5;
150 r = random() % config.randomkeys_keyspacelen;
151 sprintf(buf,"%ld",r);
152 memcpy(p,buf,strlen(buf));
153 }
154
155 static void clientDone(client c) {
156 long long latency;
157 config.donerequests ++;
158 latency = mstime() - c->start;
159 if (latency > MAX_LATENCY) latency = MAX_LATENCY;
160 config.latency[latency]++;
161
162 if (config.donerequests == config.requests) {
163 freeClient(c);
164 aeStop(config.el);
165 return;
166 }
167 if (config.keepalive) {
168 resetClient(c);
169 if (config.randomkeys) randomizeClientKey(c);
170 } else {
171 config.liveclients--;
172 createMissingClients(c);
173 config.liveclients++;
174 freeClient(c);
175 }
176 }
177
178 static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
179 client c = privdata;
180 void *reply = NULL;
181 REDIS_NOTUSED(el);
182 REDIS_NOTUSED(fd);
183 REDIS_NOTUSED(mask);
184
185 if (redisBufferRead(c->context) != REDIS_OK) {
186 fprintf(stderr,"Error: %s\n",c->context->errstr);
187 exit(1);
188 } else {
189 if (redisGetReply(c->context,&reply) != REDIS_OK) {
190 fprintf(stderr,"Error: %s\n",c->context->errstr);
191 exit(1);
192 }
193 if (reply != NULL)
194 clientDone(c);
195 }
196 }
197
198 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
199 client c = privdata;
200 REDIS_NOTUSED(el);
201 REDIS_NOTUSED(fd);
202 REDIS_NOTUSED(mask);
203
204 if (c->state == CLIENT_CONNECTING) {
205 c->state = CLIENT_SENDQUERY;
206 c->start = mstime();
207 }
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) {
212 if (errno != EPIPE)
213 fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
214 freeClient(c);
215 return;
216 }
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;
222 }
223 }
224 }
225
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);
230 } else {
231 c->context = redisConnectUnixNonBlock(config.hostsocket);
232 }
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);
237 else
238 fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr);
239 exit(1);
240 }
241 c->replytype = replytype;
242 c->state = CLIENT_CONNECTING;
243 c->obuf = sdsempty();
244 c->written = 0;
245 redisSetReplyObjectFunctions(c->context,NULL);
246 aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
247 listAddNodeTail(config.clients,c);
248 config.liveclients++;
249 return c;
250 }
251
252 static void createMissingClients(client c) {
253 while(config.liveclients < config.numclients) {
254 client new = createClient(c->replytype);
255 sdsfree(new->obuf);
256 new->obuf = sdsdup(c->obuf);
257 if (config.randomkeys) randomizeClientKey(c);
258 }
259 }
260
261 static void showLatencyReport(void) {
262 int j, seen = 0;
263 float perc, reqpersec;
264
265 reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
266 if (!config.quiet) {
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);
273 printf("\n");
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);
279 }
280 }
281 printf("%.2f requests per second\n\n", reqpersec);
282 } else {
283 printf("%s: %.2f requests per second\n", config.title, reqpersec);
284 }
285 }
286
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;
292 }
293
294 static void endBenchmark(void) {
295 config.totlatency = mstime()-config.start;
296 showLatencyReport();
297 freeAllClients();
298 }
299
300 void parseOptions(int argc, char **argv) {
301 int i;
302
303 for (i = 1; i < argc; i++) {
304 int lastarg = i==argc-1;
305
306 if (!strcmp(argv[i],"-c") && !lastarg) {
307 config.numclients = atoi(argv[i+1]);
308 i++;
309 } else if (!strcmp(argv[i],"-n") && !lastarg) {
310 config.requests = atoi(argv[i+1]);
311 i++;
312 } else if (!strcmp(argv[i],"-k") && !lastarg) {
313 config.keepalive = atoi(argv[i+1]);
314 i++;
315 } else if (!strcmp(argv[i],"-h") && !lastarg) {
316 config.hostip = argv[i+1];
317 i++;
318 } else if (!strcmp(argv[i],"-p") && !lastarg) {
319 config.hostport = atoi(argv[i+1]);
320 i++;
321 } else if (!strcmp(argv[i],"-s") && !lastarg) {
322 config.hostsocket = argv[i+1];
323 i++;
324 } else if (!strcmp(argv[i],"-d") && !lastarg) {
325 config.datasize = atoi(argv[i+1]);
326 i++;
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;
334 i++;
335 } else if (!strcmp(argv[i],"-q")) {
336 config.quiet = 1;
337 } else if (!strcmp(argv[i],"-l")) {
338 config.loop = 1;
339 } else if (!strcmp(argv[i],"-D")) {
340 config.debug = 1;
341 } else if (!strcmp(argv[i],"-I")) {
342 config.idlemode = 1;
343 } else {
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");
364 exit(1);
365 }
366 }
367 }
368
369 int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) {
370 REDIS_NOTUSED(eventLoop);
371 REDIS_NOTUSED(id);
372 REDIS_NOTUSED(clientData);
373
374 float dt = (float)(mstime()-config.start)/1000.0;
375 float rps = (float)config.donerequests/dt;
376 printf("%s: %.2f\r", config.title, rps);
377 fflush(stdout);
378 return 250; /* every 250ms */
379 }
380
381 int main(int argc, char **argv) {
382 client c;
383
384 signal(SIGHUP, SIG_IGN);
385 signal(SIGPIPE, SIG_IGN);
386
387 config.debug = 0;
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;
395 config.datasize = 3;
396 config.randomkeys = 0;
397 config.randomkeys_keyspacelen = 0;
398 config.quiet = 0;
399 config.loop = 0;
400 config.idlemode = 0;
401 config.latency = NULL;
402 config.clients = listCreate();
403 config.latency = zmalloc(sizeof(int)*(MAX_LATENCY+1));
404
405 config.hostip = "127.0.0.1";
406 config.hostport = 6379;
407 config.hostsocket = NULL;
408
409 parseOptions(argc,argv);
410
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");
413 }
414
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);
421 aeMain(config.el);
422 /* and will wait for every */
423 }
424
425 do {
426 prepareForBenchmark("PING");
427 c = createClient(REDIS_REPLY_STATUS);
428 c->obuf = sdscat(c->obuf,"PING\r\n");
429 createMissingClients(c);
430 aeMain(config.el);
431 endBenchmark();
432
433 prepareForBenchmark("PING (multi bulk)");
434 c = createClient(REDIS_REPLY_STATUS);
435 c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
436 createMissingClients(c);
437 aeMain(config.el);
438 endBenchmark();
439
440 prepareForBenchmark("MSET (10 keys, multi bulk)");
441 c = createClient(REDIS_REPLY_ARRAY);
442 c->obuf = sdscatprintf(c->obuf,"*%d\r\n$4\r\nMSET\r\n", 11);
443 {
444 int i;
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);
449 }
450 zfree(data);
451 }
452 createMissingClients(c);
453 aeMain(config.el);
454 endBenchmark();
455
456 prepareForBenchmark("SET");
457 c = createClient(REDIS_REPLY_STATUS);
458 c->obuf = sdscat(c->obuf,"SET foo_rand000000000000 ");
459 {
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);
465 }
466 createMissingClients(c);
467 aeMain(config.el);
468 endBenchmark();
469
470 prepareForBenchmark("GET");
471 c = createClient(REDIS_REPLY_STRING);
472 c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
473 createMissingClients(c);
474 aeMain(config.el);
475 endBenchmark();
476
477 prepareForBenchmark("INCR");
478 c = createClient(REDIS_REPLY_INTEGER);
479 c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
480 createMissingClients(c);
481 aeMain(config.el);
482 endBenchmark();
483
484 prepareForBenchmark("LPUSH");
485 c = createClient(REDIS_REPLY_INTEGER);
486 c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
487 createMissingClients(c);
488 aeMain(config.el);
489 endBenchmark();
490
491 prepareForBenchmark("LPOP");
492 c = createClient(REDIS_REPLY_STRING);
493 c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
494 createMissingClients(c);
495 aeMain(config.el);
496 endBenchmark();
497
498 prepareForBenchmark("SADD");
499 c = createClient(REDIS_REPLY_STATUS);
500 c->obuf = sdscat(c->obuf,"SADD myset counter_rand000000000000\r\n");
501 createMissingClients(c);
502 aeMain(config.el);
503 endBenchmark();
504
505 prepareForBenchmark("SPOP");
506 c = createClient(REDIS_REPLY_STRING);
507 c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
508 createMissingClients(c);
509 aeMain(config.el);
510 endBenchmark();
511
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);
516 aeMain(config.el);
517 endBenchmark();
518
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);
523 aeMain(config.el);
524 endBenchmark();
525
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);
530 aeMain(config.el);
531 endBenchmark();
532
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);
537 aeMain(config.el);
538 endBenchmark();
539
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);
544 aeMain(config.el);
545 endBenchmark();
546
547 printf("\n");
548 } while(config.loop);
549
550 return 0;
551 }