xref: /redis-3.2.3/src/redis-benchmark.c (revision 4942145d)
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 CLIENT_CONNECTING 0
49 #define CLIENT_SENDQUERY 1
50 #define CLIENT_READREPLY 2
51 
52 #define REDIS_NOTUSED(V) ((void) V)
53 
54 static struct config {
55     int debug;
56     int numclients;
57     int requests;
58     int liveclients;
59     int donerequests;
60     int keysize;
61     int datasize;
62     int randomkeys;
63     int randomkeys_keyspacelen;
64     aeEventLoop *el;
65     char *hostip;
66     int hostport;
67     char *hostsocket;
68     int keepalive;
69     long long start;
70     long long totlatency;
71     long long *latency;
72     char *title;
73     list *clients;
74     int quiet;
75     int loop;
76     int idlemode;
77 } config;
78 
79 typedef struct _client {
80     redisContext *context;
81     int state;
82     sds obuf;
83     unsigned int written; /* bytes of 'obuf' already written */
84     int replytype;
85     long long start; /* start time of a request */
86     long long latency; /* request latency */
87 } *client;
88 
89 /* Prototypes */
90 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
91 static void createMissingClients(client c);
92 
93 /* Implementation */
94 static long long ustime(void) {
95     struct timeval tv;
96     long long ust;
97 
98     gettimeofday(&tv, NULL);
99     ust = ((long)tv.tv_sec)*1000000;
100     ust += tv.tv_usec;
101     return ust;
102 }
103 
104 static long long mstime(void) {
105     struct timeval tv;
106     long long mst;
107 
108     gettimeofday(&tv, NULL);
109     mst = ((long)tv.tv_sec)*1000;
110     mst += tv.tv_usec/1000;
111     return mst;
112 }
113 
114 static void freeClient(client c) {
115     listNode *ln;
116     aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
117     aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
118     redisFree(c->context);
119     sdsfree(c->obuf);
120     zfree(c);
121     config.liveclients--;
122     ln = listSearchKey(config.clients,c);
123     assert(ln != NULL);
124     listDelNode(config.clients,ln);
125 }
126 
127 static void freeAllClients(void) {
128     listNode *ln = config.clients->head, *next;
129 
130     while(ln) {
131         next = ln->next;
132         freeClient(ln->value);
133         ln = next;
134     }
135 }
136 
137 static void resetClient(client c) {
138     aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
139     aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
140     aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
141     c->written = 0;
142     c->state = CLIENT_SENDQUERY;
143     c->start = ustime();
144     c->latency = -1;
145 }
146 
147 static void randomizeClientKey(client c) {
148     char *p;
149     char buf[32];
150     long r;
151 
152     p = strstr(c->obuf, "_rand");
153     if (!p) return;
154     p += 5;
155     r = random() % config.randomkeys_keyspacelen;
156     sprintf(buf,"%ld",r);
157     memcpy(p,buf,strlen(buf));
158 }
159 
160 static void clientDone(client c) {
161     if (config.donerequests == config.requests) {
162         freeClient(c);
163         aeStop(config.el);
164         return;
165     }
166     if (config.keepalive) {
167         resetClient(c);
168         if (config.randomkeys) randomizeClientKey(c);
169     } else {
170         config.liveclients--;
171         createMissingClients(c);
172         config.liveclients++;
173         freeClient(c);
174     }
175 }
176 
177 static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
178     client c = privdata;
179     void *reply = NULL;
180     REDIS_NOTUSED(el);
181     REDIS_NOTUSED(fd);
182     REDIS_NOTUSED(mask);
183 
184     /* Calculate latency only for the first read event. This means that the
185      * server already sent the reply and we need to parse it. Parsing overhead
186      * is not part of the latency, so calculate it only once, here. */
187     if (c->latency < 0) c->latency = ustime()-(c->start);
188 
189     if (redisBufferRead(c->context) != REDIS_OK) {
190         fprintf(stderr,"Error: %s\n",c->context->errstr);
191         exit(1);
192     } else {
193         if (redisGetReply(c->context,&reply) != REDIS_OK) {
194             fprintf(stderr,"Error: %s\n",c->context->errstr);
195             exit(1);
196         }
197         if (reply != NULL) {
198             if (config.donerequests < config.requests)
199                 config.latency[config.donerequests++] = c->latency;
200             clientDone(c);
201         }
202     }
203 }
204 
205 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
206     client c = privdata;
207     REDIS_NOTUSED(el);
208     REDIS_NOTUSED(fd);
209     REDIS_NOTUSED(mask);
210 
211     if (c->state == CLIENT_CONNECTING) {
212         c->state = CLIENT_SENDQUERY;
213         c->start = ustime();
214         c->latency = -1;
215     }
216     if (sdslen(c->obuf) > c->written) {
217         void *ptr = c->obuf+c->written;
218         int nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
219         if (nwritten == -1) {
220             if (errno != EPIPE)
221                 fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
222             freeClient(c);
223             return;
224         }
225         c->written += nwritten;
226         if (sdslen(c->obuf) == c->written) {
227             aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
228             aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c);
229             c->state = CLIENT_READREPLY;
230         }
231     }
232 }
233 
234 static client createClient(int replytype) {
235     client c = zmalloc(sizeof(struct _client));
236     if (config.hostsocket == NULL) {
237         c->context = redisConnectNonBlock(config.hostip,config.hostport);
238     } else {
239         c->context = redisConnectUnixNonBlock(config.hostsocket);
240     }
241     if (c->context->err) {
242         fprintf(stderr,"Could not connect to Redis at ");
243         if (config.hostsocket == NULL)
244             fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,c->context->errstr);
245         else
246             fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr);
247         exit(1);
248     }
249     c->replytype = replytype;
250     c->state = CLIENT_CONNECTING;
251     c->obuf = sdsempty();
252     c->written = 0;
253     redisSetReplyObjectFunctions(c->context,NULL);
254     aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
255     listAddNodeTail(config.clients,c);
256     config.liveclients++;
257     return c;
258 }
259 
260 static void createMissingClients(client c) {
261     while(config.liveclients < config.numclients) {
262         client new = createClient(c->replytype);
263         sdsfree(new->obuf);
264         new->obuf = sdsdup(c->obuf);
265         if (config.randomkeys) randomizeClientKey(c);
266     }
267 }
268 
269 static int compareLatency(const void *a, const void *b) {
270     return (*(long long*)a)-(*(long long*)b);
271 }
272 
273 static void showLatencyReport(void) {
274     int i, curlat = 0;
275     float perc, reqpersec;
276 
277     reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
278     if (!config.quiet) {
279         printf("====== %s ======\n", config.title);
280         printf("  %d requests completed in %.2f seconds\n", config.donerequests,
281             (float)config.totlatency/1000);
282         printf("  %d parallel clients\n", config.numclients);
283         printf("  %d bytes payload\n", config.datasize);
284         printf("  keep alive: %d\n", config.keepalive);
285         printf("\n");
286 
287         qsort(config.latency,config.requests,sizeof(long long),compareLatency);
288         for (i = 0; i < config.requests; i++) {
289             if (config.latency[i]/1000 != curlat || i == (config.requests-1)) {
290                 curlat = config.latency[i]/1000;
291                 perc = ((float)(i+1)*100)/config.requests;
292                 printf("%.2f%% <= %d milliseconds\n", perc, curlat);
293             }
294         }
295         printf("%.2f requests per second\n\n", reqpersec);
296     } else {
297         printf("%s: %.2f requests per second\n", config.title, reqpersec);
298     }
299 }
300 
301 static void prepareForBenchmark(char *title) {
302     config.title = title;
303     config.start = mstime();
304     config.donerequests = 0;
305 }
306 
307 static void endBenchmark(void) {
308     config.totlatency = mstime()-config.start;
309     showLatencyReport();
310     freeAllClients();
311 }
312 
313 void parseOptions(int argc, char **argv) {
314     int i;
315 
316     for (i = 1; i < argc; i++) {
317         int lastarg = i==argc-1;
318 
319         if (!strcmp(argv[i],"-c") && !lastarg) {
320             config.numclients = atoi(argv[i+1]);
321             i++;
322         } else if (!strcmp(argv[i],"-n") && !lastarg) {
323             config.requests = atoi(argv[i+1]);
324             i++;
325         } else if (!strcmp(argv[i],"-k") && !lastarg) {
326             config.keepalive = atoi(argv[i+1]);
327             i++;
328         } else if (!strcmp(argv[i],"-h") && !lastarg) {
329             config.hostip = argv[i+1];
330             i++;
331         } else if (!strcmp(argv[i],"-p") && !lastarg) {
332             config.hostport = atoi(argv[i+1]);
333             i++;
334         } else if (!strcmp(argv[i],"-s") && !lastarg) {
335             config.hostsocket = argv[i+1];
336             i++;
337         } else if (!strcmp(argv[i],"-d") && !lastarg) {
338             config.datasize = atoi(argv[i+1]);
339             i++;
340             if (config.datasize < 1) config.datasize=1;
341             if (config.datasize > 1024*1024) config.datasize = 1024*1024;
342         } else if (!strcmp(argv[i],"-r") && !lastarg) {
343             config.randomkeys = 1;
344             config.randomkeys_keyspacelen = atoi(argv[i+1]);
345             if (config.randomkeys_keyspacelen < 0)
346                 config.randomkeys_keyspacelen = 0;
347             i++;
348         } else if (!strcmp(argv[i],"-q")) {
349             config.quiet = 1;
350         } else if (!strcmp(argv[i],"-l")) {
351             config.loop = 1;
352         } else if (!strcmp(argv[i],"-D")) {
353             config.debug = 1;
354         } else if (!strcmp(argv[i],"-I")) {
355             config.idlemode = 1;
356         } else {
357             printf("Wrong option '%s' or option argument missing\n\n",argv[i]);
358             printf("Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n");
359             printf(" -h <hostname>      Server hostname (default 127.0.0.1)\n");
360             printf(" -p <port>          Server port (default 6379)\n");
361             printf(" -s <socket>        Server socket (overrides host and port)\n");
362             printf(" -c <clients>       Number of parallel connections (default 50)\n");
363             printf(" -n <requests>      Total number of requests (default 10000)\n");
364             printf(" -d <size>          Data size of SET/GET value in bytes (default 2)\n");
365             printf(" -k <boolean>       1=keep alive 0=reconnect (default 1)\n");
366             printf(" -r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD\n");
367             printf("  Using this option the benchmark will get/set keys\n");
368             printf("  in the form mykey_rand000000012456 instead of constant\n");
369             printf("  keys, the <keyspacelen> argument determines the max\n");
370             printf("  number of values for the random number. For instance\n");
371             printf("  if set to 10 only rand000000000000 - rand000000000009\n");
372             printf("  range will be allowed.\n");
373             printf(" -q                 Quiet. Just show query/sec values\n");
374             printf(" -l                 Loop. Run the tests forever\n");
375             printf(" -I                 Idle mode. Just open N idle connections and wait.\n");
376             printf(" -D                 Debug mode. more verbose.\n");
377             exit(1);
378         }
379     }
380 }
381 
382 int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) {
383     REDIS_NOTUSED(eventLoop);
384     REDIS_NOTUSED(id);
385     REDIS_NOTUSED(clientData);
386 
387     float dt = (float)(mstime()-config.start)/1000.0;
388     float rps = (float)config.donerequests/dt;
389     printf("%s: %.2f\r", config.title, rps);
390     fflush(stdout);
391     return 250; /* every 250ms */
392 }
393 
394 int main(int argc, char **argv) {
395     client c;
396 
397     signal(SIGHUP, SIG_IGN);
398     signal(SIGPIPE, SIG_IGN);
399 
400     config.debug = 0;
401     config.numclients = 50;
402     config.requests = 10000;
403     config.liveclients = 0;
404     config.el = aeCreateEventLoop();
405     aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
406     config.keepalive = 1;
407     config.donerequests = 0;
408     config.datasize = 3;
409     config.randomkeys = 0;
410     config.randomkeys_keyspacelen = 0;
411     config.quiet = 0;
412     config.loop = 0;
413     config.idlemode = 0;
414     config.latency = NULL;
415     config.clients = listCreate();
416     config.hostip = "127.0.0.1";
417     config.hostport = 6379;
418     config.hostsocket = NULL;
419 
420     parseOptions(argc,argv);
421     config.latency = zmalloc(sizeof(long long)*config.requests);
422 
423     if (config.keepalive == 0) {
424         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");
425     }
426 
427     if (config.idlemode) {
428         printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
429         prepareForBenchmark("IDLE");
430         c = createClient(0); /* will never receive a reply */
431         c->obuf = sdsempty();
432         createMissingClients(c);
433         aeMain(config.el);
434         /* and will wait for every */
435     }
436 
437     do {
438         prepareForBenchmark("PING");
439         c = createClient(REDIS_REPLY_STATUS);
440         c->obuf = sdscat(c->obuf,"PING\r\n");
441         createMissingClients(c);
442         aeMain(config.el);
443         endBenchmark();
444 
445         prepareForBenchmark("PING (multi bulk)");
446         c = createClient(REDIS_REPLY_STATUS);
447         c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
448         createMissingClients(c);
449         aeMain(config.el);
450         endBenchmark();
451 
452         prepareForBenchmark("MSET (10 keys, multi bulk)");
453         c = createClient(REDIS_REPLY_ARRAY);
454         c->obuf = sdscatprintf(c->obuf,"*%d\r\n$4\r\nMSET\r\n", 11);
455         {
456             int i;
457             char *data = zmalloc(config.datasize+2);
458             memset(data,'x',config.datasize);
459             for (i = 0; i < 10; i++) {
460                 c->obuf = sdscatprintf(c->obuf,"$%d\r\n%s\r\n",config.datasize,data);
461             }
462             zfree(data);
463         }
464         createMissingClients(c);
465         aeMain(config.el);
466         endBenchmark();
467 
468         prepareForBenchmark("SET");
469         c = createClient(REDIS_REPLY_STATUS);
470         c->obuf = sdscat(c->obuf,"*3\r\n$3\r\nSET\r\n$20\r\nfoo_rand000000000000\r\n");
471         {
472             char *data = zmalloc(config.datasize+2);
473             memset(data,'x',config.datasize);
474             data[config.datasize] = '\r';
475             data[config.datasize+1] = '\n';
476             c->obuf = sdscatprintf(c->obuf,"$%d\r\n",config.datasize);
477             c->obuf = sdscatlen(c->obuf,data,config.datasize+2);
478         }
479         createMissingClients(c);
480         aeMain(config.el);
481         endBenchmark();
482 
483         prepareForBenchmark("GET");
484         c = createClient(REDIS_REPLY_STRING);
485         c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
486         createMissingClients(c);
487         aeMain(config.el);
488         endBenchmark();
489 
490         prepareForBenchmark("INCR");
491         c = createClient(REDIS_REPLY_INTEGER);
492         c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
493         createMissingClients(c);
494         aeMain(config.el);
495         endBenchmark();
496 
497         prepareForBenchmark("LPUSH");
498         c = createClient(REDIS_REPLY_INTEGER);
499         c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
500         createMissingClients(c);
501         aeMain(config.el);
502         endBenchmark();
503 
504         prepareForBenchmark("LPOP");
505         c = createClient(REDIS_REPLY_STRING);
506         c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
507         createMissingClients(c);
508         aeMain(config.el);
509         endBenchmark();
510 
511         prepareForBenchmark("SADD");
512         c = createClient(REDIS_REPLY_STATUS);
513         c->obuf = sdscat(c->obuf,"SADD myset counter_rand000000000000\r\n");
514         createMissingClients(c);
515         aeMain(config.el);
516         endBenchmark();
517 
518         prepareForBenchmark("SPOP");
519         c = createClient(REDIS_REPLY_STRING);
520         c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
521         createMissingClients(c);
522         aeMain(config.el);
523         endBenchmark();
524 
525         prepareForBenchmark("LPUSH (again, in order to bench LRANGE)");
526         c = createClient(REDIS_REPLY_STATUS);
527         c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
528         createMissingClients(c);
529         aeMain(config.el);
530         endBenchmark();
531 
532         prepareForBenchmark("LRANGE (first 100 elements)");
533         c = createClient(REDIS_REPLY_ARRAY);
534         c->obuf = sdscat(c->obuf,"LRANGE mylist 0 99\r\n");
535         createMissingClients(c);
536         aeMain(config.el);
537         endBenchmark();
538 
539         prepareForBenchmark("LRANGE (first 300 elements)");
540         c = createClient(REDIS_REPLY_ARRAY);
541         c->obuf = sdscat(c->obuf,"LRANGE mylist 0 299\r\n");
542         createMissingClients(c);
543         aeMain(config.el);
544         endBenchmark();
545 
546         prepareForBenchmark("LRANGE (first 450 elements)");
547         c = createClient(REDIS_REPLY_ARRAY);
548         c->obuf = sdscat(c->obuf,"LRANGE mylist 0 449\r\n");
549         createMissingClients(c);
550         aeMain(config.el);
551         endBenchmark();
552 
553         prepareForBenchmark("LRANGE (first 600 elements)");
554         c = createClient(REDIS_REPLY_ARRAY);
555         c->obuf = sdscat(c->obuf,"LRANGE mylist 0 599\r\n");
556         createMissingClients(c);
557         aeMain(config.el);
558         endBenchmark();
559 
560         printf("\n");
561     } while(config.loop);
562 
563     return 0;
564 }
565