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