]> git.saurik.com Git - redis.git/blob - src/redis-benchmark.c
Show the current throughput while benchmarking
[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 "anet.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 int keepalive;
75 long long start;
76 long long totlatency;
77 int *latency;
78 char *title;
79 list *clients;
80 int quiet;
81 int loop;
82 int idlemode;
83 } config;
84
85 typedef struct _client {
86 int state;
87 int fd;
88 sds obuf;
89 sds ibuf;
90 int mbulk; /* Number of elements in an mbulk reply */
91 int readlen; /* readlen == -1 means read a single line */
92 int totreceived;
93 unsigned int written; /* bytes of 'obuf' already written */
94 int replytype;
95 long long start; /* start time in milliseconds */
96 } *client;
97
98 /* Prototypes */
99 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
100 static void createMissingClients(client c);
101
102 /* Implementation */
103 static long long mstime(void) {
104 struct timeval tv;
105 long long mst;
106
107 gettimeofday(&tv, NULL);
108 mst = ((long)tv.tv_sec)*1000;
109 mst += tv.tv_usec/1000;
110 return mst;
111 }
112
113 static void freeClient(client c) {
114 listNode *ln;
115
116 aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
117 aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
118 sdsfree(c->ibuf);
119 sdsfree(c->obuf);
120 close(c->fd);
121 zfree(c);
122 config.liveclients--;
123 ln = listSearchKey(config.clients,c);
124 assert(ln != NULL);
125 listDelNode(config.clients,ln);
126 }
127
128 static void freeAllClients(void) {
129 listNode *ln = config.clients->head, *next;
130
131 while(ln) {
132 next = ln->next;
133 freeClient(ln->value);
134 ln = next;
135 }
136 }
137
138 static void resetClient(client c) {
139 aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
140 aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
141 aeCreateFileEvent(config.el,c->fd, AE_WRITABLE,writeHandler,c);
142 sdsfree(c->ibuf);
143 c->ibuf = sdsempty();
144 c->readlen = (c->replytype == REPLY_BULK ||
145 c->replytype == REPLY_MBULK) ? -1 : 0;
146 c->mbulk = -1;
147 c->written = 0;
148 c->totreceived = 0;
149 c->state = CLIENT_SENDQUERY;
150 c->start = mstime();
151 createMissingClients(c);
152 }
153
154 static void randomizeClientKey(client c) {
155 char *p;
156 char buf[32];
157 long r;
158
159 p = strstr(c->obuf, "_rand");
160 if (!p) return;
161 p += 5;
162 r = random() % config.randomkeys_keyspacelen;
163 sprintf(buf,"%ld",r);
164 memcpy(p,buf,strlen(buf));
165 }
166
167 static void prepareClientForReply(client c, int type) {
168 if (type == REPLY_BULK) {
169 c->replytype = REPLY_BULK;
170 c->readlen = -1;
171 } else if (type == REPLY_MBULK) {
172 c->replytype = REPLY_MBULK;
173 c->readlen = -1;
174 c->mbulk = -1;
175 } else {
176 c->replytype = type;
177 c->readlen = 0;
178 }
179 }
180
181 static void clientDone(client c) {
182 static int last_tot_received = 1;
183
184 long long latency;
185 config.donerequests ++;
186 latency = mstime() - c->start;
187 if (latency > MAX_LATENCY) latency = MAX_LATENCY;
188 config.latency[latency]++;
189
190 if (config.debug && last_tot_received != c->totreceived) {
191 printf("Tot bytes received: %d\n", c->totreceived);
192 last_tot_received = c->totreceived;
193 }
194 if (config.donerequests == config.requests) {
195 freeClient(c);
196 aeStop(config.el);
197 return;
198 }
199 if (config.keepalive) {
200 resetClient(c);
201 if (config.randomkeys) randomizeClientKey(c);
202 } else {
203 config.liveclients--;
204 createMissingClients(c);
205 config.liveclients++;
206 freeClient(c);
207 }
208 }
209
210 /* Read a length from the buffer pointed to by *p, store the length in *len,
211 * and return the number of bytes that the cursor advanced. */
212 static int readLen(char *p, int *len) {
213 char *tail = strstr(p,"\r\n");
214 if (tail == NULL)
215 return 0;
216 *tail = '\0';
217 *len = atoi(p+1);
218 return tail+2-p;
219 }
220
221 static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
222 {
223 char buf[1024], *p;
224 int nread, pos=0, len=0;
225 client c = privdata;
226 REDIS_NOTUSED(el);
227 REDIS_NOTUSED(fd);
228 REDIS_NOTUSED(mask);
229
230 nread = read(c->fd,buf,sizeof(buf));
231 if (nread == -1) {
232 fprintf(stderr, "Reading from socket: %s\n", strerror(errno));
233 freeClient(c);
234 return;
235 }
236 if (nread == 0) {
237 fprintf(stderr, "EOF from client\n");
238 freeClient(c);
239 return;
240 }
241 c->totreceived += nread;
242 c->ibuf = sdscatlen(c->ibuf,buf,nread);
243 len = sdslen(c->ibuf);
244
245 if (c->replytype == REPLY_INT ||
246 c->replytype == REPLY_RETCODE)
247 {
248 /* Check if the first line is complete. This is everything we need
249 * when waiting for an integer or status code reply.*/
250 if ((p = strstr(c->ibuf,"\r\n")) != NULL)
251 goto done;
252 } else if (c->replytype == REPLY_BULK) {
253 int advance = 0;
254 if (c->readlen < 0) {
255 advance = readLen(c->ibuf+pos,&c->readlen);
256 if (advance) {
257 pos += advance;
258 if (c->readlen == -1) {
259 goto done;
260 } else {
261 /* include the trailing \r\n */
262 c->readlen += 2;
263 }
264 } else {
265 goto skip;
266 }
267 }
268
269 int canconsume;
270 if (c->readlen > 0) {
271 canconsume = c->readlen > (len-pos) ? (len-pos) : c->readlen;
272 c->readlen -= canconsume;
273 pos += canconsume;
274 }
275
276 if (c->readlen == 0)
277 goto done;
278 } else if (c->replytype == REPLY_MBULK) {
279 int advance = 0;
280 if (c->mbulk == -1) {
281 advance = readLen(c->ibuf+pos,&c->mbulk);
282 if (advance) {
283 pos += advance;
284 if (c->mbulk == -1)
285 goto done;
286 } else {
287 goto skip;
288 }
289 }
290
291 int canconsume;
292 while(c->mbulk > 0 && pos < len) {
293 if (c->readlen > 0) {
294 canconsume = c->readlen > (len-pos) ? (len-pos) : c->readlen;
295 c->readlen -= canconsume;
296 pos += canconsume;
297 if (c->readlen == 0)
298 c->mbulk--;
299 } else {
300 advance = readLen(c->ibuf+pos,&c->readlen);
301 if (advance) {
302 pos += advance;
303 if (c->readlen == -1) {
304 c->mbulk--;
305 continue;
306 } else {
307 /* include the trailing \r\n */
308 c->readlen += 2;
309 }
310 } else {
311 goto skip;
312 }
313 }
314 }
315
316 if (c->mbulk == 0)
317 goto done;
318 }
319
320 skip:
321 c->ibuf = sdsrange(c->ibuf,pos,-1);
322 return;
323 done:
324 clientDone(c);
325 return;
326 }
327
328 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
329 {
330 client c = privdata;
331 REDIS_NOTUSED(el);
332 REDIS_NOTUSED(fd);
333 REDIS_NOTUSED(mask);
334
335 if (c->state == CLIENT_CONNECTING) {
336 c->state = CLIENT_SENDQUERY;
337 c->start = mstime();
338 }
339 if (sdslen(c->obuf) > c->written) {
340 void *ptr = c->obuf+c->written;
341 int len = sdslen(c->obuf) - c->written;
342 int nwritten = write(c->fd, ptr, len);
343 if (nwritten == -1) {
344 if (errno != EPIPE)
345 fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
346 freeClient(c);
347 return;
348 }
349 c->written += nwritten;
350 if (sdslen(c->obuf) == c->written) {
351 aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
352 aeCreateFileEvent(config.el,c->fd,AE_READABLE,readHandler,c);
353 c->state = CLIENT_READREPLY;
354 }
355 }
356 }
357
358 static client createClient(void) {
359 client c = zmalloc(sizeof(struct _client));
360 char err[ANET_ERR_LEN];
361
362 c->fd = anetTcpNonBlockConnect(err,config.hostip,config.hostport);
363 if (c->fd == ANET_ERR) {
364 zfree(c);
365 fprintf(stderr,"Connect: %s\n",err);
366 return NULL;
367 }
368 anetTcpNoDelay(NULL,c->fd);
369 c->obuf = sdsempty();
370 c->ibuf = sdsempty();
371 c->mbulk = -1;
372 c->readlen = 0;
373 c->written = 0;
374 c->totreceived = 0;
375 c->state = CLIENT_CONNECTING;
376 aeCreateFileEvent(config.el, c->fd, AE_WRITABLE, writeHandler, c);
377 config.liveclients++;
378 listAddNodeTail(config.clients,c);
379 return c;
380 }
381
382 static void createMissingClients(client c) {
383 while(config.liveclients < config.numclients) {
384 client new = createClient();
385 if (!new) continue;
386 sdsfree(new->obuf);
387 new->obuf = sdsdup(c->obuf);
388 if (config.randomkeys) randomizeClientKey(c);
389 prepareClientForReply(new,c->replytype);
390 }
391 }
392
393 static void showLatencyReport(void) {
394 int j, seen = 0;
395 float perc, reqpersec;
396
397 reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
398 if (!config.quiet) {
399 printf("====== %s ======\n", config.title);
400 printf(" %d requests completed in %.2f seconds\n", config.donerequests,
401 (float)config.totlatency/1000);
402 printf(" %d parallel clients\n", config.numclients);
403 printf(" %d bytes payload\n", config.datasize);
404 printf(" keep alive: %d\n", config.keepalive);
405 printf("\n");
406 for (j = 0; j <= MAX_LATENCY; j++) {
407 if (config.latency[j]) {
408 seen += config.latency[j];
409 perc = ((float)seen*100)/config.donerequests;
410 printf("%.2f%% <= %d milliseconds\n", perc, j);
411 }
412 }
413 printf("%.2f requests per second\n\n", reqpersec);
414 } else {
415 printf("%s: %.2f requests per second\n", config.title, reqpersec);
416 }
417 }
418
419 static void prepareForBenchmark(char *title) {
420 memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1));
421 config.title = title;
422 config.start = mstime();
423 config.donerequests = 0;
424 }
425
426 static void endBenchmark(void) {
427 config.totlatency = mstime()-config.start;
428 showLatencyReport();
429 freeAllClients();
430 }
431
432 void parseOptions(int argc, char **argv) {
433 int i;
434
435 for (i = 1; i < argc; i++) {
436 int lastarg = i==argc-1;
437
438 if (!strcmp(argv[i],"-c") && !lastarg) {
439 config.numclients = atoi(argv[i+1]);
440 i++;
441 } else if (!strcmp(argv[i],"-n") && !lastarg) {
442 config.requests = atoi(argv[i+1]);
443 i++;
444 } else if (!strcmp(argv[i],"-k") && !lastarg) {
445 config.keepalive = atoi(argv[i+1]);
446 i++;
447 } else if (!strcmp(argv[i],"-h") && !lastarg) {
448 char *ip = zmalloc(32);
449 if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
450 printf("Can't resolve %s\n", argv[i]);
451 exit(1);
452 }
453 config.hostip = ip;
454 i++;
455 } else if (!strcmp(argv[i],"-p") && !lastarg) {
456 config.hostport = atoi(argv[i+1]);
457 i++;
458 } else if (!strcmp(argv[i],"-d") && !lastarg) {
459 config.datasize = atoi(argv[i+1]);
460 i++;
461 if (config.datasize < 1) config.datasize=1;
462 if (config.datasize > 1024*1024) config.datasize = 1024*1024;
463 } else if (!strcmp(argv[i],"-r") && !lastarg) {
464 config.randomkeys = 1;
465 config.randomkeys_keyspacelen = atoi(argv[i+1]);
466 if (config.randomkeys_keyspacelen < 0)
467 config.randomkeys_keyspacelen = 0;
468 i++;
469 } else if (!strcmp(argv[i],"-q")) {
470 config.quiet = 1;
471 } else if (!strcmp(argv[i],"-l")) {
472 config.loop = 1;
473 } else if (!strcmp(argv[i],"-D")) {
474 config.debug = 1;
475 } else if (!strcmp(argv[i],"-I")) {
476 config.idlemode = 1;
477 } else {
478 printf("Wrong option '%s' or option argument missing\n\n",argv[i]);
479 printf("Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n");
480 printf(" -h <hostname> Server hostname (default 127.0.0.1)\n");
481 printf(" -p <hostname> Server port (default 6379)\n");
482 printf(" -c <clients> Number of parallel connections (default 50)\n");
483 printf(" -n <requests> Total number of requests (default 10000)\n");
484 printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n");
485 printf(" -k <boolean> 1=keep alive 0=reconnect (default 1)\n");
486 printf(" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n");
487 printf(" Using this option the benchmark will get/set keys\n");
488 printf(" in the form mykey_rand000000012456 instead of constant\n");
489 printf(" keys, the <keyspacelen> argument determines the max\n");
490 printf(" number of values for the random number. For instance\n");
491 printf(" if set to 10 only rand000000000000 - rand000000000009\n");
492 printf(" range will be allowed.\n");
493 printf(" -q Quiet. Just show query/sec values\n");
494 printf(" -l Loop. Run the tests forever\n");
495 printf(" -I Idle mode. Just open N idle connections and wait.\n");
496 printf(" -D Debug mode. more verbose.\n");
497 exit(1);
498 }
499 }
500 }
501
502 int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) {
503 REDIS_NOTUSED(eventLoop);
504 REDIS_NOTUSED(id);
505 REDIS_NOTUSED(clientData);
506
507 float dt = (float)(mstime()-config.start)/1000.0;
508 float rps = (float)config.donerequests/dt;
509 printf("%s: %.2f\r", config.title, rps);
510 fflush(stdout);
511 return 250; /* every 250ms */
512 }
513
514 int main(int argc, char **argv) {
515 client c;
516
517 signal(SIGHUP, SIG_IGN);
518 signal(SIGPIPE, SIG_IGN);
519
520 config.debug = 0;
521 config.numclients = 50;
522 config.requests = 10000;
523 config.liveclients = 0;
524 config.el = aeCreateEventLoop();
525 aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
526 config.keepalive = 1;
527 config.donerequests = 0;
528 config.datasize = 3;
529 config.randomkeys = 0;
530 config.randomkeys_keyspacelen = 0;
531 config.quiet = 0;
532 config.loop = 0;
533 config.idlemode = 0;
534 config.latency = NULL;
535 config.clients = listCreate();
536 config.latency = zmalloc(sizeof(int)*(MAX_LATENCY+1));
537
538 config.hostip = "127.0.0.1";
539 config.hostport = 6379;
540
541 parseOptions(argc,argv);
542
543 if (config.keepalive == 0) {
544 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");
545 }
546
547 if (config.idlemode) {
548 printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
549 prepareForBenchmark("IDLE");
550 c = createClient();
551 if (!c) exit(1);
552 c->obuf = sdsempty();
553 prepareClientForReply(c,REPLY_RETCODE); /* will never receive it */
554 createMissingClients(c);
555 aeMain(config.el);
556 /* and will wait for every */
557 }
558
559 do {
560 prepareForBenchmark("PING");
561 c = createClient();
562 if (!c) exit(1);
563 c->obuf = sdscat(c->obuf,"PING\r\n");
564 prepareClientForReply(c,REPLY_RETCODE);
565 createMissingClients(c);
566 aeMain(config.el);
567 endBenchmark();
568
569 prepareForBenchmark("PING (multi bulk)");
570 c = createClient();
571 if (!c) exit(1);
572 c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
573 prepareClientForReply(c,REPLY_RETCODE);
574 createMissingClients(c);
575 aeMain(config.el);
576 endBenchmark();
577
578 prepareForBenchmark("SET");
579 c = createClient();
580 if (!c) exit(1);
581 c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize);
582 {
583 char *data = zmalloc(config.datasize+2);
584 memset(data,'x',config.datasize);
585 data[config.datasize] = '\r';
586 data[config.datasize+1] = '\n';
587 c->obuf = sdscatlen(c->obuf,data,config.datasize+2);
588 }
589 prepareClientForReply(c,REPLY_RETCODE);
590 createMissingClients(c);
591 aeMain(config.el);
592 endBenchmark();
593
594 prepareForBenchmark("GET");
595 c = createClient();
596 if (!c) exit(1);
597 c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
598 prepareClientForReply(c,REPLY_BULK);
599 createMissingClients(c);
600 aeMain(config.el);
601 endBenchmark();
602
603 prepareForBenchmark("INCR");
604 c = createClient();
605 if (!c) exit(1);
606 c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
607 prepareClientForReply(c,REPLY_INT);
608 createMissingClients(c);
609 aeMain(config.el);
610 endBenchmark();
611
612 prepareForBenchmark("LPUSH");
613 c = createClient();
614 if (!c) exit(1);
615 c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
616 prepareClientForReply(c,REPLY_INT);
617 createMissingClients(c);
618 aeMain(config.el);
619 endBenchmark();
620
621 prepareForBenchmark("LPOP");
622 c = createClient();
623 if (!c) exit(1);
624 c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
625 prepareClientForReply(c,REPLY_BULK);
626 createMissingClients(c);
627 aeMain(config.el);
628 endBenchmark();
629
630 prepareForBenchmark("SADD");
631 c = createClient();
632 if (!c) exit(1);
633 c->obuf = sdscat(c->obuf,"SADD myset 24\r\ncounter_rand000000000000\r\n");
634 prepareClientForReply(c,REPLY_RETCODE);
635 createMissingClients(c);
636 aeMain(config.el);
637 endBenchmark();
638
639 prepareForBenchmark("SPOP");
640 c = createClient();
641 if (!c) exit(1);
642 c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
643 prepareClientForReply(c,REPLY_BULK);
644 createMissingClients(c);
645 aeMain(config.el);
646 endBenchmark();
647
648 prepareForBenchmark("LPUSH (again, in order to bench LRANGE)");
649 c = createClient();
650 if (!c) exit(1);
651 c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
652 prepareClientForReply(c,REPLY_RETCODE);
653 createMissingClients(c);
654 aeMain(config.el);
655 endBenchmark();
656
657 prepareForBenchmark("LRANGE (first 100 elements)");
658 c = createClient();
659 if (!c) exit(1);
660 c->obuf = sdscat(c->obuf,"LRANGE mylist 0 99\r\n");
661 prepareClientForReply(c,REPLY_MBULK);
662 createMissingClients(c);
663 aeMain(config.el);
664 endBenchmark();
665
666 prepareForBenchmark("LRANGE (first 300 elements)");
667 c = createClient();
668 if (!c) exit(1);
669 c->obuf = sdscat(c->obuf,"LRANGE mylist 0 299\r\n");
670 prepareClientForReply(c,REPLY_MBULK);
671 createMissingClients(c);
672 aeMain(config.el);
673 endBenchmark();
674
675 prepareForBenchmark("LRANGE (first 450 elements)");
676 c = createClient();
677 if (!c) exit(1);
678 c->obuf = sdscat(c->obuf,"LRANGE mylist 0 449\r\n");
679 prepareClientForReply(c,REPLY_MBULK);
680 createMissingClients(c);
681 aeMain(config.el);
682 endBenchmark();
683
684 prepareForBenchmark("LRANGE (first 600 elements)");
685 c = createClient();
686 if (!c) exit(1);
687 c->obuf = sdscat(c->obuf,"LRANGE mylist 0 599\r\n");
688 prepareClientForReply(c,REPLY_MBULK);
689 createMissingClients(c);
690 aeMain(config.el);
691 endBenchmark();
692
693 printf("\n");
694 } while(config.loop);
695
696 return 0;
697 }