]> git.saurik.com Git - redis.git/blob - benchmark.c
CPP client added thanks to Brian Hammond
[redis.git] / benchmark.c
1 /* Redis benchmark utility.
2 *
3 * Copyright (c) 2006-2009, 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 <stdio.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <sys/time.h>
37 #include <signal.h>
38 #include <assert.h>
39
40 #include "ae.h"
41 #include "anet.h"
42 #include "sds.h"
43 #include "adlist.h"
44 #include "zmalloc.h"
45
46 #define REPLY_INT 0
47 #define REPLY_RETCODE 1
48 #define REPLY_BULK 2
49
50 #define CLIENT_CONNECTING 0
51 #define CLIENT_SENDQUERY 1
52 #define CLIENT_READREPLY 2
53
54 #define MAX_LATENCY 5000
55
56 #define REDIS_NOTUSED(V) ((void) V)
57
58 static struct config {
59 int numclients;
60 int requests;
61 int liveclients;
62 int donerequests;
63 int keysize;
64 int datasize;
65 int randomkeys;
66 aeEventLoop *el;
67 char *hostip;
68 int hostport;
69 int keepalive;
70 long long start;
71 long long totlatency;
72 int *latency;
73 list *clients;
74 int quiet;
75 int loop;
76 } config;
77
78 typedef struct _client {
79 int state;
80 int fd;
81 sds obuf;
82 sds ibuf;
83 int readlen; /* readlen == -1 means read a single line */
84 unsigned int written; /* bytes of 'obuf' already written */
85 int replytype;
86 long long start; /* start time in milliseconds */
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 mstime(void) {
95 struct timeval tv;
96 long long mst;
97
98 gettimeofday(&tv, NULL);
99 mst = ((long)tv.tv_sec)*1000;
100 mst += tv.tv_usec/1000;
101 return mst;
102 }
103
104 static void freeClient(client c) {
105 listNode *ln;
106
107 aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
108 aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
109 sdsfree(c->ibuf);
110 sdsfree(c->obuf);
111 close(c->fd);
112 zfree(c);
113 config.liveclients--;
114 ln = listSearchKey(config.clients,c);
115 assert(ln != NULL);
116 listDelNode(config.clients,ln);
117 }
118
119 static void freeAllClients(void) {
120 listNode *ln = config.clients->head, *next;
121
122 while(ln) {
123 next = ln->next;
124 freeClient(ln->value);
125 ln = next;
126 }
127 }
128
129 static void resetClient(client c) {
130 aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
131 aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
132 aeCreateFileEvent(config.el,c->fd, AE_WRITABLE,writeHandler,c,NULL);
133 sdsfree(c->ibuf);
134 c->ibuf = sdsempty();
135 c->readlen = (c->replytype == REPLY_BULK) ? -1 : 0;
136 c->written = 0;
137 c->state = CLIENT_SENDQUERY;
138 c->start = mstime();
139 }
140
141 static void clientDone(client c) {
142 long long latency;
143 config.donerequests ++;
144 latency = mstime() - c->start;
145 if (latency > MAX_LATENCY) latency = MAX_LATENCY;
146 config.latency[latency]++;
147
148 if (config.donerequests == config.requests) {
149 freeClient(c);
150 aeStop(config.el);
151 return;
152 }
153 if (config.keepalive) {
154 resetClient(c);
155 } else {
156 config.liveclients--;
157 createMissingClients(c);
158 config.liveclients++;
159 freeClient(c);
160 }
161 }
162
163 static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
164 {
165 char buf[1024];
166 int nread;
167 client c = privdata;
168 REDIS_NOTUSED(el);
169 REDIS_NOTUSED(fd);
170 REDIS_NOTUSED(mask);
171
172 nread = read(c->fd, buf, 1024);
173 if (nread == -1) {
174 fprintf(stderr, "Reading from socket: %s\n", strerror(errno));
175 freeClient(c);
176 return;
177 }
178 if (nread == 0) {
179 fprintf(stderr, "EOF from client\n");
180 freeClient(c);
181 return;
182 }
183 c->ibuf = sdscatlen(c->ibuf,buf,nread);
184
185 if (c->replytype == REPLY_INT ||
186 c->replytype == REPLY_RETCODE ||
187 (c->replytype == REPLY_BULK && c->readlen == -1)) {
188 char *p;
189
190 if ((p = strchr(c->ibuf,'\n')) != NULL) {
191 if (c->replytype == REPLY_BULK) {
192 *p = '\0';
193 *(p-1) = '\0';
194 c->readlen = atoi(c->ibuf+1)+2;
195 if (c->readlen == -1) {
196 clientDone(c);
197 return;
198 }
199 c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
200 } else {
201 c->ibuf = sdstrim(c->ibuf,"\r\n");
202 clientDone(c);
203 return;
204 }
205 }
206 }
207 /* bulk read */
208 if ((unsigned)c->readlen == sdslen(c->ibuf))
209 clientDone(c);
210 }
211
212 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
213 {
214 client c = privdata;
215 REDIS_NOTUSED(el);
216 REDIS_NOTUSED(fd);
217 REDIS_NOTUSED(mask);
218
219 if (c->state == CLIENT_CONNECTING) {
220 c->state = CLIENT_SENDQUERY;
221 c->start = mstime();
222 }
223 if (sdslen(c->obuf) > c->written) {
224 void *ptr = c->obuf+c->written;
225 int len = sdslen(c->obuf) - c->written;
226 int nwritten = write(c->fd, ptr, len);
227 if (nwritten == -1) {
228 fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
229 freeClient(c);
230 return;
231 }
232 c->written += nwritten;
233 if (sdslen(c->obuf) == c->written) {
234 aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
235 aeCreateFileEvent(config.el,c->fd,AE_READABLE,readHandler,c,NULL);
236 c->state = CLIENT_READREPLY;
237 }
238 }
239 }
240
241 static client createClient(void) {
242 client c = zmalloc(sizeof(struct _client));
243 char err[ANET_ERR_LEN];
244
245 c->fd = anetTcpNonBlockConnect(err,config.hostip,config.hostport);
246 if (c->fd == ANET_ERR) {
247 zfree(c);
248 fprintf(stderr,"Connect: %s\n",err);
249 return NULL;
250 }
251 anetTcpNoDelay(NULL,c->fd);
252 c->obuf = sdsempty();
253 c->ibuf = sdsempty();
254 c->readlen = 0;
255 c->written = 0;
256 c->state = CLIENT_CONNECTING;
257 aeCreateFileEvent(config.el, c->fd, AE_WRITABLE, writeHandler, c, NULL);
258 config.liveclients++;
259 listAddNodeTail(config.clients,c);
260 return c;
261 }
262
263 static void createMissingClients(client c) {
264 while(config.liveclients < config.numclients) {
265 client new = createClient();
266 if (!new) continue;
267 sdsfree(new->obuf);
268 new->obuf = sdsdup(c->obuf);
269 new->replytype = c->replytype;
270 if (c->replytype == REPLY_BULK)
271 new->readlen = -1;
272 }
273 }
274
275 static void showLatencyReport(char *title) {
276 int j, seen = 0;
277 float perc, reqpersec;
278
279 reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
280 if (!config.quiet) {
281 printf("====== %s ======\n", title);
282 printf(" %d requests completed in %.2f seconds\n", config.donerequests,
283 (float)config.totlatency/1000);
284 printf(" %d parallel clients\n", config.numclients);
285 printf(" %d bytes payload\n", config.datasize);
286 printf(" keep alive: %d\n", config.keepalive);
287 printf("\n");
288 for (j = 0; j <= MAX_LATENCY; j++) {
289 if (config.latency[j]) {
290 seen += config.latency[j];
291 perc = ((float)seen*100)/config.donerequests;
292 printf("%.2f%% <= %d milliseconds\n", perc, j);
293 }
294 }
295 printf("%.2f requests per second\n\n", reqpersec);
296 } else {
297 printf("%s: %.2f requests per second\n", title, reqpersec);
298 }
299 }
300
301 static void prepareForBenchmark(void)
302 {
303 memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1));
304 config.start = mstime();
305 config.donerequests = 0;
306 }
307
308 static void endBenchmark(char *title) {
309 config.totlatency = mstime()-config.start;
310 showLatencyReport(title);
311 freeAllClients();
312 }
313
314 void parseOptions(int argc, char **argv) {
315 int i;
316
317 for (i = 1; i < argc; i++) {
318 int lastarg = i==argc-1;
319
320 if (!strcmp(argv[i],"-c") && !lastarg) {
321 config.numclients = atoi(argv[i+1]);
322 i++;
323 } else if (!strcmp(argv[i],"-n") && !lastarg) {
324 config.requests = atoi(argv[i+1]);
325 i++;
326 } else if (!strcmp(argv[i],"-k") && !lastarg) {
327 config.keepalive = atoi(argv[i+1]);
328 i++;
329 } else if (!strcmp(argv[i],"-h") && !lastarg) {
330 char *ip = zmalloc(32);
331 if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
332 printf("Can't resolve %s\n", argv[i]);
333 exit(1);
334 }
335 config.hostip = ip;
336 i++;
337 } else if (!strcmp(argv[i],"-p") && !lastarg) {
338 config.hostport = atoi(argv[i+1]);
339 i++;
340 } else if (!strcmp(argv[i],"-d") && !lastarg) {
341 config.datasize = atoi(argv[i+1]);
342 i++;
343 if (config.datasize < 1) config.datasize=1;
344 if (config.datasize > 1024*1024) config.datasize = 1024*1024;
345 } else if (!strcmp(argv[i],"-r")) {
346 config.randomkeys = 1;
347 } else if (!strcmp(argv[i],"-q")) {
348 config.quiet = 1;
349 } else if (!strcmp(argv[i],"-l")) {
350 config.loop = 1;
351 } else {
352 printf("Wrong option '%s' or option argument missing\n\n",argv[i]);
353 printf("Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n");
354 printf(" -h <hostname> Server hostname (default 127.0.0.1)\n");
355 printf(" -p <hostname> Server port (default 6379)\n");
356 printf(" -c <clients> Number of parallel connections (default 50)\n");
357 printf(" -n <requests> Total number of requests (default 10000)\n");
358 printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n");
359 printf(" -k <boolean> 1=keep alive 0=reconnect (default 1)\n");
360 printf(" -r Use random keys for SET/GET/INCR\n");
361 printf(" -q Quiet. Just show query/sec values\n");
362 printf(" -l Loop. Run the tests forever\n");
363 exit(1);
364 }
365 }
366 }
367
368 int main(int argc, char **argv) {
369 client c;
370
371 signal(SIGHUP, SIG_IGN);
372 signal(SIGPIPE, SIG_IGN);
373
374 config.numclients = 50;
375 config.requests = 10000;
376 config.liveclients = 0;
377 config.el = aeCreateEventLoop();
378 config.keepalive = 1;
379 config.donerequests = 0;
380 config.datasize = 3;
381 config.randomkeys = 0;
382 config.quiet = 0;
383 config.loop = 0;
384 config.latency = NULL;
385 config.clients = listCreate();
386 config.latency = zmalloc(sizeof(int)*(MAX_LATENCY+1));
387
388 config.hostip = "127.0.0.1";
389 config.hostport = 6379;
390
391 parseOptions(argc,argv);
392
393 if (config.keepalive == 0) {
394 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");
395 }
396
397 do {
398 prepareForBenchmark();
399 c = createClient();
400 if (!c) exit(1);
401 c->obuf = sdscat(c->obuf,"PING\r\n");
402 c->replytype = REPLY_RETCODE;
403 createMissingClients(c);
404 aeMain(config.el);
405 endBenchmark("PING");
406
407 prepareForBenchmark();
408 c = createClient();
409 if (!c) exit(1);
410 c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize);
411 {
412 char *data = zmalloc(config.datasize+2);
413 memset(data,'x',config.datasize);
414 data[config.datasize] = '\r';
415 data[config.datasize+1] = '\n';
416 c->obuf = sdscatlen(c->obuf,data,config.datasize+2);
417 }
418 c->replytype = REPLY_RETCODE;
419 createMissingClients(c);
420 aeMain(config.el);
421 endBenchmark("SET");
422
423 prepareForBenchmark();
424 c = createClient();
425 if (!c) exit(1);
426 c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
427 c->replytype = REPLY_BULK;
428 c->readlen = -1;
429 createMissingClients(c);
430 aeMain(config.el);
431 endBenchmark("GET");
432
433 prepareForBenchmark();
434 c = createClient();
435 if (!c) exit(1);
436 c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
437 c->replytype = REPLY_INT;
438 createMissingClients(c);
439 aeMain(config.el);
440 endBenchmark("INCR");
441
442 prepareForBenchmark();
443 c = createClient();
444 if (!c) exit(1);
445 c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
446 c->replytype = REPLY_INT;
447 createMissingClients(c);
448 aeMain(config.el);
449 endBenchmark("LPUSH");
450
451 prepareForBenchmark();
452 c = createClient();
453 if (!c) exit(1);
454 c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
455 c->replytype = REPLY_BULK;
456 c->readlen = -1;
457 createMissingClients(c);
458 aeMain(config.el);
459 endBenchmark("LPOP");
460
461 printf("\n");
462 } while(config.loop);
463
464 return 0;
465 }