xref: /redis-3.2.3/src/redis-benchmark.c (revision 40f822da)
1 /* Redis benchmark utility.
2  *
3  * Copyright (c) 2009-2012, 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 <time.h>
39 #include <sys/time.h>
40 #include <signal.h>
41 #include <assert.h>
42 
43 #include "ae.h"
44 #include "hiredis.h"
45 #include "sds.h"
46 #include "adlist.h"
47 #include "zmalloc.h"
48 
49 #define REDIS_NOTUSED(V) ((void) V)
50 
51 static struct config {
52     aeEventLoop *el;
53     const char *hostip;
54     int hostport;
55     const char *hostsocket;
56     int numclients;
57     int liveclients;
58     int requests;
59     int requests_issued;
60     int requests_finished;
61     int keysize;
62     int datasize;
63     int randomkeys;
64     int randomkeys_keyspacelen;
65     int keepalive;
66     int pipeline;
67     long long start;
68     long long totlatency;
69     long long *latency;
70     const char *title;
71     list *clients;
72     int quiet;
73     int csv;
74     int loop;
75     int idlemode;
76     char *tests;
77 } config;
78 
79 typedef struct _client {
80     redisContext *context;
81     sds obuf;
82     char *randptr[32]; /* needed for MSET against 10 keys */
83     size_t randlen;
84     unsigned int written; /* bytes of 'obuf' already written */
85     long long start; /* start time of a request */
86     long long latency; /* request latency */
87     int pending;    /* Number of pending requests (sent but no reply received) */
88 } *client;
89 
90 /* Prototypes */
91 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
92 static void createMissingClients(client c);
93 
94 /* Implementation */
95 static long long ustime(void) {
96     struct timeval tv;
97     long long ust;
98 
99     gettimeofday(&tv, NULL);
100     ust = ((long)tv.tv_sec)*1000000;
101     ust += tv.tv_usec;
102     return ust;
103 }
104 
105 static long long mstime(void) {
106     struct timeval tv;
107     long long mst;
108 
109     gettimeofday(&tv, NULL);
110     mst = ((long long)tv.tv_sec)*1000;
111     mst += tv.tv_usec/1000;
112     return mst;
113 }
114 
115 static void freeClient(client c) {
116     listNode *ln;
117     aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
118     aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
119     redisFree(c->context);
120     sdsfree(c->obuf);
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->context->fd,AE_WRITABLE);
140     aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
141     aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
142     c->written = 0;
143     c->pending = config.pipeline;
144 }
145 
146 static void randomizeClientKey(client c) {
147     char buf[32];
148     size_t i, r;
149 
150     for (i = 0; i < c->randlen; i++) {
151         r = random() % config.randomkeys_keyspacelen;
152         snprintf(buf,sizeof(buf),"%012zu",r);
153         memcpy(c->randptr[i],buf,12);
154     }
155 }
156 
157 static void clientDone(client c) {
158     if (config.requests_finished == config.requests) {
159         freeClient(c);
160         aeStop(config.el);
161         return;
162     }
163     if (config.keepalive) {
164         resetClient(c);
165     } else {
166         config.liveclients--;
167         createMissingClients(c);
168         config.liveclients++;
169         freeClient(c);
170     }
171 }
172 
173 static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
174     client c = privdata;
175     void *reply = NULL;
176     REDIS_NOTUSED(el);
177     REDIS_NOTUSED(fd);
178     REDIS_NOTUSED(mask);
179 
180     /* Calculate latency only for the first read event. This means that the
181      * server already sent the reply and we need to parse it. Parsing overhead
182      * is not part of the latency, so calculate it only once, here. */
183     if (c->latency < 0) c->latency = ustime()-(c->start);
184 
185     if (redisBufferRead(c->context) != REDIS_OK) {
186         fprintf(stderr,"Error: %s\n",c->context->errstr);
187         exit(1);
188     } else {
189         while(c->pending) {
190             if (redisGetReply(c->context,&reply) != REDIS_OK) {
191                 fprintf(stderr,"Error: %s\n",c->context->errstr);
192                 exit(1);
193             }
194             if (reply != NULL) {
195                 if (reply == (void*)REDIS_REPLY_ERROR) {
196                     fprintf(stderr,"Unexpected error reply, exiting...\n");
197                     exit(1);
198                 }
199 
200                 freeReplyObject(reply);
201 
202                 if (config.requests_finished < config.requests)
203                     config.latency[config.requests_finished++] = c->latency;
204                 c->pending--;
205                 if (c->pending == 0) {
206                     clientDone(c);
207                     break;
208                 }
209             } else {
210                 break;
211             }
212         }
213     }
214 }
215 
216 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
217     client c = privdata;
218     REDIS_NOTUSED(el);
219     REDIS_NOTUSED(fd);
220     REDIS_NOTUSED(mask);
221 
222     /* Initialize request when nothing was written. */
223     if (c->written == 0) {
224         /* Enforce upper bound to number of requests. */
225         if (config.requests_issued++ >= config.requests) {
226             freeClient(c);
227             return;
228         }
229 
230         /* Really initialize: randomize keys and set start time. */
231         if (config.randomkeys) randomizeClientKey(c);
232         c->start = ustime();
233         c->latency = -1;
234     }
235 
236     if (sdslen(c->obuf) > c->written) {
237         void *ptr = c->obuf+c->written;
238         int nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
239         if (nwritten == -1) {
240             if (errno != EPIPE)
241                 fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
242             freeClient(c);
243             return;
244         }
245         c->written += nwritten;
246         if (sdslen(c->obuf) == c->written) {
247             aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
248             aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c);
249         }
250     }
251 }
252 
253 static client createClient(char *cmd, size_t len) {
254     int j;
255     client c = zmalloc(sizeof(struct _client));
256 
257     if (config.hostsocket == NULL) {
258         c->context = redisConnectNonBlock(config.hostip,config.hostport);
259     } else {
260         c->context = redisConnectUnixNonBlock(config.hostsocket);
261     }
262     if (c->context->err) {
263         fprintf(stderr,"Could not connect to Redis at ");
264         if (config.hostsocket == NULL)
265             fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,c->context->errstr);
266         else
267             fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr);
268         exit(1);
269     }
270     /* Suppress hiredis cleanup of unused buffers for max speed. */
271     c->context->reader->maxbuf = 0;
272     /* Queue N requests accordingly to the pipeline size. */
273     c->obuf = sdsempty();
274     for (j = 0; j < config.pipeline; j++)
275         c->obuf = sdscatlen(c->obuf,cmd,len);
276     c->randlen = 0;
277     c->written = 0;
278     c->pending = config.pipeline;
279 
280     /* Find substrings in the output buffer that need to be randomized. */
281     if (config.randomkeys) {
282         char *p = c->obuf;
283         while ((p = strstr(p,":rand:")) != NULL) {
284             assert(c->randlen < (signed)(sizeof(c->randptr)/sizeof(char*)));
285             c->randptr[c->randlen++] = p+6;
286             p += 6;
287         }
288     }
289 
290 /*    redisSetReplyObjectFunctions(c->context,NULL); */
291     aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
292     listAddNodeTail(config.clients,c);
293     config.liveclients++;
294     return c;
295 }
296 
297 static void createMissingClients(client c) {
298     int n = 0;
299 
300     while(config.liveclients < config.numclients) {
301         createClient(c->obuf,sdslen(c->obuf)/config.pipeline);
302 
303         /* Listen backlog is quite limited on most systems */
304         if (++n > 64) {
305             usleep(50000);
306             n = 0;
307         }
308     }
309 }
310 
311 static int compareLatency(const void *a, const void *b) {
312     return (*(long long*)a)-(*(long long*)b);
313 }
314 
315 static void showLatencyReport(void) {
316     int i, curlat = 0;
317     float perc, reqpersec;
318 
319     reqpersec = (float)config.requests_finished/((float)config.totlatency/1000);
320     if (!config.quiet && !config.csv) {
321         printf("====== %s ======\n", config.title);
322         printf("  %d requests completed in %.2f seconds\n", config.requests_finished,
323             (float)config.totlatency/1000);
324         printf("  %d parallel clients\n", config.numclients);
325         printf("  %d bytes payload\n", config.datasize);
326         printf("  keep alive: %d\n", config.keepalive);
327         printf("\n");
328 
329         qsort(config.latency,config.requests,sizeof(long long),compareLatency);
330         for (i = 0; i < config.requests; i++) {
331             if (config.latency[i]/1000 != curlat || i == (config.requests-1)) {
332                 curlat = config.latency[i]/1000;
333                 perc = ((float)(i+1)*100)/config.requests;
334                 printf("%.2f%% <= %d milliseconds\n", perc, curlat);
335             }
336         }
337         printf("%.2f requests per second\n\n", reqpersec);
338     } else if (config.csv) {
339         printf("\"%s\",\"%.2f\"\n", config.title, reqpersec);
340     } else {
341         printf("%s: %.2f requests per second\n", config.title, reqpersec);
342     }
343 }
344 
345 static void benchmark(char *title, char *cmd, int len) {
346     client c;
347 
348     config.title = title;
349     config.requests_issued = 0;
350     config.requests_finished = 0;
351 
352     c = createClient(cmd,len);
353     createMissingClients(c);
354 
355     config.start = mstime();
356     aeMain(config.el);
357     config.totlatency = mstime()-config.start;
358 
359     showLatencyReport();
360     freeAllClients();
361 }
362 
363 /* Returns number of consumed options. */
364 int parseOptions(int argc, const char **argv) {
365     int i;
366     int lastarg;
367     int exit_status = 1;
368 
369     for (i = 1; i < argc; i++) {
370         lastarg = (i == (argc-1));
371 
372         if (!strcmp(argv[i],"-c")) {
373             if (lastarg) goto invalid;
374             config.numclients = atoi(argv[++i]);
375         } else if (!strcmp(argv[i],"-n")) {
376             if (lastarg) goto invalid;
377             config.requests = atoi(argv[++i]);
378         } else if (!strcmp(argv[i],"-k")) {
379             if (lastarg) goto invalid;
380             config.keepalive = atoi(argv[++i]);
381         } else if (!strcmp(argv[i],"-h")) {
382             if (lastarg) goto invalid;
383             config.hostip = strdup(argv[++i]);
384         } else if (!strcmp(argv[i],"-p")) {
385             if (lastarg) goto invalid;
386             config.hostport = atoi(argv[++i]);
387         } else if (!strcmp(argv[i],"-s")) {
388             if (lastarg) goto invalid;
389             config.hostsocket = strdup(argv[++i]);
390         } else if (!strcmp(argv[i],"-d")) {
391             if (lastarg) goto invalid;
392             config.datasize = atoi(argv[++i]);
393             if (config.datasize < 1) config.datasize=1;
394             if (config.datasize > 1024*1024*1024) config.datasize = 1024*1024*1024;
395         } else if (!strcmp(argv[i],"-P")) {
396             if (lastarg) goto invalid;
397             config.pipeline = atoi(argv[++i]);
398             if (config.pipeline <= 0) config.pipeline=1;
399         } else if (!strcmp(argv[i],"-r")) {
400             if (lastarg) goto invalid;
401             config.randomkeys = 1;
402             config.randomkeys_keyspacelen = atoi(argv[++i]);
403             if (config.randomkeys_keyspacelen < 0)
404                 config.randomkeys_keyspacelen = 0;
405         } else if (!strcmp(argv[i],"-q")) {
406             config.quiet = 1;
407         } else if (!strcmp(argv[i],"--csv")) {
408             config.csv = 1;
409         } else if (!strcmp(argv[i],"-l")) {
410             config.loop = 1;
411         } else if (!strcmp(argv[i],"-I")) {
412             config.idlemode = 1;
413         } else if (!strcmp(argv[i],"-t")) {
414             if (lastarg) goto invalid;
415             /* We get the list of tests to run as a string in the form
416              * get,set,lrange,...,test_N. Then we add a comma before and
417              * after the string in order to make sure that searching
418              * for ",testname," will always get a match if the test is
419              * enabled. */
420             config.tests = sdsnew(",");
421             config.tests = sdscat(config.tests,(char*)argv[++i]);
422             config.tests = sdscat(config.tests,",");
423             sdstolower(config.tests);
424         } else if (!strcmp(argv[i],"--help")) {
425             exit_status = 0;
426             goto usage;
427         } else {
428             /* Assume the user meant to provide an option when the arg starts
429              * with a dash. We're done otherwise and should use the remainder
430              * as the command and arguments for running the benchmark. */
431             if (argv[i][0] == '-') goto invalid;
432             return i;
433         }
434     }
435 
436     return i;
437 
438 invalid:
439     printf("Invalid option \"%s\" or option argument missing\n\n",argv[i]);
440 
441 usage:
442     printf(
443 "Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n"
444 " -h <hostname>      Server hostname (default 127.0.0.1)\n"
445 " -p <port>          Server port (default 6379)\n"
446 " -s <socket>        Server socket (overrides host and port)\n"
447 " -c <clients>       Number of parallel connections (default 50)\n"
448 " -n <requests>      Total number of requests (default 10000)\n"
449 " -d <size>          Data size of SET/GET value in bytes (default 2)\n"
450 " -k <boolean>       1=keep alive 0=reconnect (default 1)\n"
451 " -r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD\n"
452 "  Using this option the benchmark will get/set keys\n"
453 "  in the form mykey_rand:000000012456 instead of constant\n"
454 "  keys, the <keyspacelen> argument determines the max\n"
455 "  number of values for the random number. For instance\n"
456 "  if set to 10 only rand:000000000000 - rand:000000000009\n"
457 "  range will be allowed.\n"
458 " -P <numreq>        Pipeline <numreq> requests. Default 1 (no pipeline).\n"
459 " -q                 Quiet. Just show query/sec values\n"
460 " --csv              Output in CSV format\n"
461 " -l                 Loop. Run the tests forever\n"
462 " -t <tests>         Only run the comma separated list of tests. The test\n"
463 "                    names are the same as the ones produced as output.\n"
464 " -I                 Idle mode. Just open N idle connections and wait.\n\n"
465 "Examples:\n\n"
466 " Run the benchmark with the default configuration against 127.0.0.1:6379:\n"
467 "   $ redis-benchmark\n\n"
468 " Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:\n"
469 "   $ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20\n\n"
470 " Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:\n"
471 "   $ redis-benchmark -t set -n 1000000 -r 100000000\n\n"
472 " Benchmark 127.0.0.1:6379 for a few commands producing CSV output:\n"
473 "   $ redis-benchmark -t ping,set,get -n 100000 --csv\n\n"
474 " Fill a list with 10000 random elements:\n"
475 "   $ redis-benchmark -r 10000 -n 10000 lpush mylist ele:rand:000000000000\n\n"
476     );
477     exit(exit_status);
478 }
479 
480 int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) {
481     REDIS_NOTUSED(eventLoop);
482     REDIS_NOTUSED(id);
483     REDIS_NOTUSED(clientData);
484 
485     if (config.csv) return 250;
486     float dt = (float)(mstime()-config.start)/1000.0;
487     float rps = (float)config.requests_finished/dt;
488     printf("%s: %.2f\r", config.title, rps);
489     fflush(stdout);
490     return 250; /* every 250ms */
491 }
492 
493 /* Return true if the named test was selected using the -t command line
494  * switch, or if all the tests are selected (no -t passed by user). */
495 int test_is_selected(char *name) {
496     char buf[256];
497     int l = strlen(name);
498 
499     if (config.tests == NULL) return 1;
500     buf[0] = ',';
501     memcpy(buf+1,name,l);
502     buf[l+1] = ',';
503     buf[l+2] = '\0';
504     return strstr(config.tests,buf) != NULL;
505 }
506 
507 int main(int argc, const char **argv) {
508     int i;
509     char *data, *cmd;
510     int len;
511 
512     client c;
513 
514     srandom(time(NULL));
515     signal(SIGHUP, SIG_IGN);
516     signal(SIGPIPE, SIG_IGN);
517 
518     config.numclients = 50;
519     config.requests = 10000;
520     config.liveclients = 0;
521     config.el = aeCreateEventLoop(1024*10);
522     aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
523     config.keepalive = 1;
524     config.datasize = 3;
525     config.pipeline = 1;
526     config.randomkeys = 0;
527     config.randomkeys_keyspacelen = 0;
528     config.quiet = 0;
529     config.csv = 0;
530     config.loop = 0;
531     config.idlemode = 0;
532     config.latency = NULL;
533     config.clients = listCreate();
534     config.hostip = "127.0.0.1";
535     config.hostport = 6379;
536     config.hostsocket = NULL;
537     config.tests = NULL;
538 
539     i = parseOptions(argc,argv);
540     argc -= i;
541     argv += i;
542 
543     config.latency = zmalloc(sizeof(long long)*config.requests);
544 
545     if (config.keepalive == 0) {
546         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");
547     }
548 
549     if (config.idlemode) {
550         printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
551         c = createClient("",0); /* will never receive a reply */
552         createMissingClients(c);
553         aeMain(config.el);
554         /* and will wait for every */
555     }
556 
557     /* Run benchmark with command in the remainder of the arguments. */
558     if (argc) {
559         sds title = sdsnew(argv[0]);
560         for (i = 1; i < argc; i++) {
561             title = sdscatlen(title, " ", 1);
562             title = sdscatlen(title, (char*)argv[i], strlen(argv[i]));
563         }
564 
565         do {
566             len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
567             benchmark(title,cmd,len);
568             free(cmd);
569         } while(config.loop);
570 
571         return 0;
572     }
573 
574     /* Run default benchmark suite. */
575     do {
576         data = zmalloc(config.datasize+1);
577         memset(data,'x',config.datasize);
578         data[config.datasize] = '\0';
579 
580         if (test_is_selected("ping_inline") || test_is_selected("ping"))
581             benchmark("PING_INLINE","PING\r\n",6);
582 
583         if (test_is_selected("ping_mbulk") || test_is_selected("ping")) {
584             len = redisFormatCommand(&cmd,"PING");
585             benchmark("PING_BULK",cmd,len);
586             free(cmd);
587         }
588 
589         if (test_is_selected("set")) {
590             len = redisFormatCommand(&cmd,"SET foo:rand:000000000000 %s",data);
591             benchmark("SET",cmd,len);
592             free(cmd);
593         }
594 
595         if (test_is_selected("get")) {
596             len = redisFormatCommand(&cmd,"GET foo:rand:000000000000");
597             benchmark("GET",cmd,len);
598             free(cmd);
599         }
600 
601         if (test_is_selected("incr")) {
602             len = redisFormatCommand(&cmd,"INCR counter:rand:000000000000");
603             benchmark("INCR",cmd,len);
604             free(cmd);
605         }
606 
607         if (test_is_selected("lpush")) {
608             len = redisFormatCommand(&cmd,"LPUSH mylist %s",data);
609             benchmark("LPUSH",cmd,len);
610             free(cmd);
611         }
612 
613         if (test_is_selected("lpop")) {
614             len = redisFormatCommand(&cmd,"LPOP mylist");
615             benchmark("LPOP",cmd,len);
616             free(cmd);
617         }
618 
619         if (test_is_selected("sadd")) {
620             len = redisFormatCommand(&cmd,
621                 "SADD myset counter:rand:000000000000");
622             benchmark("SADD",cmd,len);
623             free(cmd);
624         }
625 
626         if (test_is_selected("spop")) {
627             len = redisFormatCommand(&cmd,"SPOP myset");
628             benchmark("SPOP",cmd,len);
629             free(cmd);
630         }
631 
632         if (test_is_selected("lrange") ||
633             test_is_selected("lrange_100") ||
634             test_is_selected("lrange_300") ||
635             test_is_selected("lrange_500") ||
636             test_is_selected("lrange_600"))
637         {
638             len = redisFormatCommand(&cmd,"LPUSH mylist %s",data);
639             benchmark("LPUSH (needed to benchmark LRANGE)",cmd,len);
640             free(cmd);
641         }
642 
643         if (test_is_selected("lrange") || test_is_selected("lrange_100")) {
644             len = redisFormatCommand(&cmd,"LRANGE mylist 0 99");
645             benchmark("LRANGE_100 (first 100 elements)",cmd,len);
646             free(cmd);
647         }
648 
649         if (test_is_selected("lrange") || test_is_selected("lrange_300")) {
650             len = redisFormatCommand(&cmd,"LRANGE mylist 0 299");
651             benchmark("LRANGE_300 (first 300 elements)",cmd,len);
652             free(cmd);
653         }
654 
655         if (test_is_selected("lrange") || test_is_selected("lrange_500")) {
656             len = redisFormatCommand(&cmd,"LRANGE mylist 0 449");
657             benchmark("LRANGE_500 (first 450 elements)",cmd,len);
658             free(cmd);
659         }
660 
661         if (test_is_selected("lrange") || test_is_selected("lrange_600")) {
662             len = redisFormatCommand(&cmd,"LRANGE mylist 0 599");
663             benchmark("LRANGE_600 (first 600 elements)",cmd,len);
664             free(cmd);
665         }
666 
667         if (test_is_selected("mset")) {
668             const char *argv[21];
669             argv[0] = "MSET";
670             for (i = 1; i < 21; i += 2) {
671                 argv[i] = "foo:rand:000000000000";
672                 argv[i+1] = data;
673             }
674             len = redisFormatCommandArgv(&cmd,21,argv,NULL);
675             benchmark("MSET (10 keys)",cmd,len);
676             free(cmd);
677         }
678 
679         if (!config.csv) printf("\n");
680     } while(config.loop);
681 
682     return 0;
683 }
684