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 <sds.h> /* Use hiredis sds. */ 44 #include "ae.h" 45 #include "hiredis.h" 46 #include "adlist.h" 47 #include "zmalloc.h" 48 49 #define UNUSED(V) ((void) V) 50 #define RANDPTR_INITIAL_SIZE 8 51 52 static struct config { 53 aeEventLoop *el; 54 const char *hostip; 55 int hostport; 56 const char *hostsocket; 57 int numclients; 58 int liveclients; 59 int requests; 60 int requests_issued; 61 int requests_finished; 62 int keysize; 63 int datasize; 64 int randomkeys; 65 int randomkeys_keyspacelen; 66 int keepalive; 67 int pipeline; 68 int showerrors; 69 long long start; 70 long long totlatency; 71 long long *latency; 72 const char *title; 73 list *clients; 74 int quiet; 75 int csv; 76 int loop; 77 int idlemode; 78 int dbnum; 79 sds dbnumstr; 80 char *tests; 81 char *auth; 82 } config; 83 84 typedef struct _client { 85 redisContext *context; 86 sds obuf; 87 char **randptr; /* Pointers to :rand: strings inside the command buf */ 88 size_t randlen; /* Number of pointers in client->randptr */ 89 size_t randfree; /* Number of unused pointers in client->randptr */ 90 size_t written; /* Bytes of 'obuf' already written */ 91 long long start; /* Start time of a request */ 92 long long latency; /* Request latency */ 93 int pending; /* Number of pending requests (replies to consume) */ 94 int prefix_pending; /* If non-zero, number of pending prefix commands. Commands 95 such as auth and select are prefixed to the pipeline of 96 benchmark commands and discarded after the first send. */ 97 int prefixlen; /* Size in bytes of the pending prefix commands */ 98 } *client; 99 100 /* Prototypes */ 101 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask); 102 static void createMissingClients(client c); 103 104 /* Implementation */ 105 static long long ustime(void) { 106 struct timeval tv; 107 long long ust; 108 109 gettimeofday(&tv, NULL); 110 ust = ((long)tv.tv_sec)*1000000; 111 ust += tv.tv_usec; 112 return ust; 113 } 114 115 static long long mstime(void) { 116 struct timeval tv; 117 long long mst; 118 119 gettimeofday(&tv, NULL); 120 mst = ((long long)tv.tv_sec)*1000; 121 mst += tv.tv_usec/1000; 122 return mst; 123 } 124 125 static void freeClient(client c) { 126 listNode *ln; 127 aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE); 128 aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE); 129 redisFree(c->context); 130 sdsfree(c->obuf); 131 zfree(c->randptr); 132 zfree(c); 133 config.liveclients--; 134 ln = listSearchKey(config.clients,c); 135 assert(ln != NULL); 136 listDelNode(config.clients,ln); 137 } 138 139 static void freeAllClients(void) { 140 listNode *ln = config.clients->head, *next; 141 142 while(ln) { 143 next = ln->next; 144 freeClient(ln->value); 145 ln = next; 146 } 147 } 148 149 static void resetClient(client c) { 150 aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE); 151 aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE); 152 aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c); 153 c->written = 0; 154 c->pending = config.pipeline; 155 } 156 157 static void randomizeClientKey(client c) { 158 size_t i; 159 160 for (i = 0; i < c->randlen; i++) { 161 char *p = c->randptr[i]+11; 162 size_t r = random() % config.randomkeys_keyspacelen; 163 size_t j; 164 165 for (j = 0; j < 12; j++) { 166 *p = '0'+r%10; 167 r/=10; 168 p--; 169 } 170 } 171 } 172 173 static void clientDone(client c) { 174 if (config.requests_finished == config.requests) { 175 freeClient(c); 176 aeStop(config.el); 177 return; 178 } 179 if (config.keepalive) { 180 resetClient(c); 181 } else { 182 config.liveclients--; 183 createMissingClients(c); 184 config.liveclients++; 185 freeClient(c); 186 } 187 } 188 189 static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) { 190 client c = privdata; 191 void *reply = NULL; 192 UNUSED(el); 193 UNUSED(fd); 194 UNUSED(mask); 195 196 /* Calculate latency only for the first read event. This means that the 197 * server already sent the reply and we need to parse it. Parsing overhead 198 * is not part of the latency, so calculate it only once, here. */ 199 if (c->latency < 0) c->latency = ustime()-(c->start); 200 201 if (redisBufferRead(c->context) != REDIS_OK) { 202 fprintf(stderr,"Error: %s\n",c->context->errstr); 203 exit(1); 204 } else { 205 while(c->pending) { 206 if (redisGetReply(c->context,&reply) != REDIS_OK) { 207 fprintf(stderr,"Error: %s\n",c->context->errstr); 208 exit(1); 209 } 210 if (reply != NULL) { 211 if (reply == (void*)REDIS_REPLY_ERROR) { 212 fprintf(stderr,"Unexpected error reply, exiting...\n"); 213 exit(1); 214 } 215 216 if (config.showerrors) { 217 static time_t lasterr_time = 0; 218 time_t now = time(NULL); 219 redisReply *r = reply; 220 if (r->type == REDIS_REPLY_ERROR && lasterr_time != now) { 221 lasterr_time = now; 222 printf("Error from server: %s\n", r->str); 223 } 224 } 225 226 freeReplyObject(reply); 227 /* This is an OK for prefix commands such as auth and select.*/ 228 if (c->prefix_pending > 0) { 229 c->prefix_pending--; 230 c->pending--; 231 /* Discard prefix commands on first response.*/ 232 if (c->prefixlen > 0) { 233 size_t j; 234 sdsrange(c->obuf, c->prefixlen, -1); 235 /* We also need to fix the pointers to the strings 236 * we need to randomize. */ 237 for (j = 0; j < c->randlen; j++) 238 c->randptr[j] -= c->prefixlen; 239 c->prefixlen = 0; 240 } 241 continue; 242 } 243 244 if (config.requests_finished < config.requests) 245 config.latency[config.requests_finished++] = c->latency; 246 c->pending--; 247 if (c->pending == 0) { 248 clientDone(c); 249 break; 250 } 251 } else { 252 break; 253 } 254 } 255 } 256 } 257 258 static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) { 259 client c = privdata; 260 UNUSED(el); 261 UNUSED(fd); 262 UNUSED(mask); 263 264 /* Initialize request when nothing was written. */ 265 if (c->written == 0) { 266 /* Enforce upper bound to number of requests. */ 267 if (config.requests_issued++ >= config.requests) { 268 freeClient(c); 269 return; 270 } 271 272 /* Really initialize: randomize keys and set start time. */ 273 if (config.randomkeys) randomizeClientKey(c); 274 c->start = ustime(); 275 c->latency = -1; 276 } 277 278 if (sdslen(c->obuf) > c->written) { 279 void *ptr = c->obuf+c->written; 280 ssize_t nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written); 281 if (nwritten == -1) { 282 if (errno != EPIPE) 283 fprintf(stderr, "Writing to socket: %s\n", strerror(errno)); 284 freeClient(c); 285 return; 286 } 287 c->written += nwritten; 288 if (sdslen(c->obuf) == c->written) { 289 aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE); 290 aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c); 291 } 292 } 293 } 294 295 /* Create a benchmark client, configured to send the command passed as 'cmd' of 296 * 'len' bytes. 297 * 298 * The command is copied N times in the client output buffer (that is reused 299 * again and again to send the request to the server) accordingly to the configured 300 * pipeline size. 301 * 302 * Also an initial SELECT command is prepended in order to make sure the right 303 * database is selected, if needed. The initial SELECT will be discarded as soon 304 * as the first reply is received. 305 * 306 * To create a client from scratch, the 'from' pointer is set to NULL. If instead 307 * we want to create a client using another client as reference, the 'from' pointer 308 * points to the client to use as reference. In such a case the following 309 * information is take from the 'from' client: 310 * 311 * 1) The command line to use. 312 * 2) The offsets of the __rand_int__ elements inside the command line, used 313 * for arguments randomization. 314 * 315 * Even when cloning another client, prefix commands are applied if needed.*/ 316 static client createClient(char *cmd, size_t len, client from) { 317 int j; 318 client c = zmalloc(sizeof(struct _client)); 319 320 if (config.hostsocket == NULL) { 321 c->context = redisConnectNonBlock(config.hostip,config.hostport); 322 } else { 323 c->context = redisConnectUnixNonBlock(config.hostsocket); 324 } 325 if (c->context->err) { 326 fprintf(stderr,"Could not connect to Redis at "); 327 if (config.hostsocket == NULL) 328 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,c->context->errstr); 329 else 330 fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr); 331 exit(1); 332 } 333 /* Suppress hiredis cleanup of unused buffers for max speed. */ 334 c->context->reader->maxbuf = 0; 335 336 /* Build the request buffer: 337 * Queue N requests accordingly to the pipeline size, or simply clone 338 * the example client buffer. */ 339 c->obuf = sdsempty(); 340 /* Prefix the request buffer with AUTH and/or SELECT commands, if applicable. 341 * These commands are discarded after the first response, so if the client is 342 * reused the commands will not be used again. */ 343 c->prefix_pending = 0; 344 if (config.auth) { 345 char *buf = NULL; 346 int len = redisFormatCommand(&buf, "AUTH %s", config.auth); 347 c->obuf = sdscatlen(c->obuf, buf, len); 348 free(buf); 349 c->prefix_pending++; 350 } 351 352 /* If a DB number different than zero is selected, prefix our request 353 * buffer with the SELECT command, that will be discarded the first 354 * time the replies are received, so if the client is reused the 355 * SELECT command will not be used again. */ 356 if (config.dbnum != 0) { 357 c->obuf = sdscatprintf(c->obuf,"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n", 358 (int)sdslen(config.dbnumstr),config.dbnumstr); 359 c->prefix_pending++; 360 } 361 c->prefixlen = sdslen(c->obuf); 362 /* Append the request itself. */ 363 if (from) { 364 c->obuf = sdscatlen(c->obuf, 365 from->obuf+from->prefixlen, 366 sdslen(from->obuf)-from->prefixlen); 367 } else { 368 for (j = 0; j < config.pipeline; j++) 369 c->obuf = sdscatlen(c->obuf,cmd,len); 370 } 371 372 c->written = 0; 373 c->pending = config.pipeline+c->prefix_pending; 374 c->randptr = NULL; 375 c->randlen = 0; 376 377 /* Find substrings in the output buffer that need to be randomized. */ 378 if (config.randomkeys) { 379 if (from) { 380 c->randlen = from->randlen; 381 c->randfree = 0; 382 c->randptr = zmalloc(sizeof(char*)*c->randlen); 383 /* copy the offsets. */ 384 for (j = 0; j < (int)c->randlen; j++) { 385 c->randptr[j] = c->obuf + (from->randptr[j]-from->obuf); 386 /* Adjust for the different select prefix length. */ 387 c->randptr[j] += c->prefixlen - from->prefixlen; 388 } 389 } else { 390 char *p = c->obuf; 391 392 c->randlen = 0; 393 c->randfree = RANDPTR_INITIAL_SIZE; 394 c->randptr = zmalloc(sizeof(char*)*c->randfree); 395 while ((p = strstr(p,"__rand_int__")) != NULL) { 396 if (c->randfree == 0) { 397 c->randptr = zrealloc(c->randptr,sizeof(char*)*c->randlen*2); 398 c->randfree += c->randlen; 399 } 400 c->randptr[c->randlen++] = p; 401 c->randfree--; 402 p += 12; /* 12 is strlen("__rand_int__). */ 403 } 404 } 405 } 406 if (config.idlemode == 0) 407 aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c); 408 listAddNodeTail(config.clients,c); 409 config.liveclients++; 410 return c; 411 } 412 413 static void createMissingClients(client c) { 414 int n = 0; 415 416 while(config.liveclients < config.numclients) { 417 createClient(NULL,0,c); 418 419 /* Listen backlog is quite limited on most systems */ 420 if (++n > 64) { 421 usleep(50000); 422 n = 0; 423 } 424 } 425 } 426 427 static int compareLatency(const void *a, const void *b) { 428 return (*(long long*)a)-(*(long long*)b); 429 } 430 431 static void showLatencyReport(void) { 432 int i, curlat = 0; 433 float perc, reqpersec; 434 435 reqpersec = (float)config.requests_finished/((float)config.totlatency/1000); 436 if (!config.quiet && !config.csv) { 437 printf("====== %s ======\n", config.title); 438 printf(" %d requests completed in %.2f seconds\n", config.requests_finished, 439 (float)config.totlatency/1000); 440 printf(" %d parallel clients\n", config.numclients); 441 printf(" %d bytes payload\n", config.datasize); 442 printf(" keep alive: %d\n", config.keepalive); 443 printf("\n"); 444 445 qsort(config.latency,config.requests,sizeof(long long),compareLatency); 446 for (i = 0; i < config.requests; i++) { 447 if (config.latency[i]/1000 != curlat || i == (config.requests-1)) { 448 curlat = config.latency[i]/1000; 449 perc = ((float)(i+1)*100)/config.requests; 450 printf("%.2f%% <= %d milliseconds\n", perc, curlat); 451 } 452 } 453 printf("%.2f requests per second\n\n", reqpersec); 454 } else if (config.csv) { 455 printf("\"%s\",\"%.2f\"\n", config.title, reqpersec); 456 } else { 457 printf("%s: %.2f requests per second\n", config.title, reqpersec); 458 } 459 } 460 461 static void benchmark(char *title, char *cmd, int len) { 462 client c; 463 464 config.title = title; 465 config.requests_issued = 0; 466 config.requests_finished = 0; 467 468 c = createClient(cmd,len,NULL); 469 createMissingClients(c); 470 471 config.start = mstime(); 472 aeMain(config.el); 473 config.totlatency = mstime()-config.start; 474 475 showLatencyReport(); 476 freeAllClients(); 477 } 478 479 /* Returns number of consumed options. */ 480 int parseOptions(int argc, const char **argv) { 481 int i; 482 int lastarg; 483 int exit_status = 1; 484 485 for (i = 1; i < argc; i++) { 486 lastarg = (i == (argc-1)); 487 488 if (!strcmp(argv[i],"-c")) { 489 if (lastarg) goto invalid; 490 config.numclients = atoi(argv[++i]); 491 } else if (!strcmp(argv[i],"-n")) { 492 if (lastarg) goto invalid; 493 config.requests = atoi(argv[++i]); 494 } else if (!strcmp(argv[i],"-k")) { 495 if (lastarg) goto invalid; 496 config.keepalive = atoi(argv[++i]); 497 } else if (!strcmp(argv[i],"-h")) { 498 if (lastarg) goto invalid; 499 config.hostip = strdup(argv[++i]); 500 } else if (!strcmp(argv[i],"-p")) { 501 if (lastarg) goto invalid; 502 config.hostport = atoi(argv[++i]); 503 } else if (!strcmp(argv[i],"-s")) { 504 if (lastarg) goto invalid; 505 config.hostsocket = strdup(argv[++i]); 506 } else if (!strcmp(argv[i],"-a") ) { 507 if (lastarg) goto invalid; 508 config.auth = strdup(argv[++i]); 509 } else if (!strcmp(argv[i],"-d")) { 510 if (lastarg) goto invalid; 511 config.datasize = atoi(argv[++i]); 512 if (config.datasize < 1) config.datasize=1; 513 if (config.datasize > 1024*1024*1024) config.datasize = 1024*1024*1024; 514 } else if (!strcmp(argv[i],"-P")) { 515 if (lastarg) goto invalid; 516 config.pipeline = atoi(argv[++i]); 517 if (config.pipeline <= 0) config.pipeline=1; 518 } else if (!strcmp(argv[i],"-r")) { 519 if (lastarg) goto invalid; 520 config.randomkeys = 1; 521 config.randomkeys_keyspacelen = atoi(argv[++i]); 522 if (config.randomkeys_keyspacelen < 0) 523 config.randomkeys_keyspacelen = 0; 524 } else if (!strcmp(argv[i],"-q")) { 525 config.quiet = 1; 526 } else if (!strcmp(argv[i],"--csv")) { 527 config.csv = 1; 528 } else if (!strcmp(argv[i],"-l")) { 529 config.loop = 1; 530 } else if (!strcmp(argv[i],"-I")) { 531 config.idlemode = 1; 532 } else if (!strcmp(argv[i],"-e")) { 533 config.showerrors = 1; 534 } else if (!strcmp(argv[i],"-t")) { 535 if (lastarg) goto invalid; 536 /* We get the list of tests to run as a string in the form 537 * get,set,lrange,...,test_N. Then we add a comma before and 538 * after the string in order to make sure that searching 539 * for ",testname," will always get a match if the test is 540 * enabled. */ 541 config.tests = sdsnew(","); 542 config.tests = sdscat(config.tests,(char*)argv[++i]); 543 config.tests = sdscat(config.tests,","); 544 sdstolower(config.tests); 545 } else if (!strcmp(argv[i],"--dbnum")) { 546 if (lastarg) goto invalid; 547 config.dbnum = atoi(argv[++i]); 548 config.dbnumstr = sdsfromlonglong(config.dbnum); 549 } else if (!strcmp(argv[i],"--help")) { 550 exit_status = 0; 551 goto usage; 552 } else { 553 /* Assume the user meant to provide an option when the arg starts 554 * with a dash. We're done otherwise and should use the remainder 555 * as the command and arguments for running the benchmark. */ 556 if (argv[i][0] == '-') goto invalid; 557 return i; 558 } 559 } 560 561 return i; 562 563 invalid: 564 printf("Invalid option \"%s\" or option argument missing\n\n",argv[i]); 565 566 usage: 567 printf( 568 "Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests>] [-k <boolean>]\n\n" 569 " -h <hostname> Server hostname (default 127.0.0.1)\n" 570 " -p <port> Server port (default 6379)\n" 571 " -s <socket> Server socket (overrides host and port)\n" 572 " -a <password> Password for Redis Auth\n" 573 " -c <clients> Number of parallel connections (default 50)\n" 574 " -n <requests> Total number of requests (default 100000)\n" 575 " -d <size> Data size of SET/GET value in bytes (default 3)\n" 576 " --dbnum <db> SELECT the specified db number (default 0)\n" 577 " -k <boolean> 1=keep alive 0=reconnect (default 1)\n" 578 " -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n" 579 " Using this option the benchmark will expand the string __rand_int__\n" 580 " inside an argument with a 12 digits number in the specified range\n" 581 " from 0 to keyspacelen-1. The substitution changes every time a command\n" 582 " is executed. Default tests use this to hit random keys in the\n" 583 " specified range.\n" 584 " -P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline).\n" 585 " -e If server replies with errors, show them on stdout.\n" 586 " (no more than 1 error per second is displayed)\n" 587 " -q Quiet. Just show query/sec values\n" 588 " --csv Output in CSV format\n" 589 " -l Loop. Run the tests forever\n" 590 " -t <tests> Only run the comma separated list of tests. The test\n" 591 " names are the same as the ones produced as output.\n" 592 " -I Idle mode. Just open N idle connections and wait.\n\n" 593 "Examples:\n\n" 594 " Run the benchmark with the default configuration against 127.0.0.1:6379:\n" 595 " $ redis-benchmark\n\n" 596 " Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:\n" 597 " $ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20\n\n" 598 " Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:\n" 599 " $ redis-benchmark -t set -n 1000000 -r 100000000\n\n" 600 " Benchmark 127.0.0.1:6379 for a few commands producing CSV output:\n" 601 " $ redis-benchmark -t ping,set,get -n 100000 --csv\n\n" 602 " Benchmark a specific command line:\n" 603 " $ redis-benchmark -r 10000 -n 10000 eval 'return redis.call(\"ping\")' 0\n\n" 604 " Fill a list with 10000 random elements:\n" 605 " $ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__\n\n" 606 " On user specified command lines __rand_int__ is replaced with a random integer\n" 607 " with a range of values selected by the -r option.\n" 608 ); 609 exit(exit_status); 610 } 611 612 int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) { 613 UNUSED(eventLoop); 614 UNUSED(id); 615 UNUSED(clientData); 616 617 if (config.liveclients == 0 && config.requests_finished != config.requests) { 618 fprintf(stderr,"All clients disconnected... aborting.\n"); 619 exit(1); 620 } 621 if (config.csv) return 250; 622 if (config.idlemode == 1) { 623 printf("clients: %d\r", config.liveclients); 624 fflush(stdout); 625 return 250; 626 } 627 float dt = (float)(mstime()-config.start)/1000.0; 628 float rps = (float)config.requests_finished/dt; 629 printf("%s: %.2f\r", config.title, rps); 630 fflush(stdout); 631 return 250; /* every 250ms */ 632 } 633 634 /* Return true if the named test was selected using the -t command line 635 * switch, or if all the tests are selected (no -t passed by user). */ 636 int test_is_selected(char *name) { 637 char buf[256]; 638 int l = strlen(name); 639 640 if (config.tests == NULL) return 1; 641 buf[0] = ','; 642 memcpy(buf+1,name,l); 643 buf[l+1] = ','; 644 buf[l+2] = '\0'; 645 return strstr(config.tests,buf) != NULL; 646 } 647 648 int main(int argc, const char **argv) { 649 int i; 650 char *data, *cmd; 651 int len; 652 653 client c; 654 655 srandom(time(NULL)); 656 signal(SIGHUP, SIG_IGN); 657 signal(SIGPIPE, SIG_IGN); 658 659 config.numclients = 50; 660 config.requests = 100000; 661 config.liveclients = 0; 662 config.el = aeCreateEventLoop(1024*10); 663 aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL); 664 config.keepalive = 1; 665 config.datasize = 3; 666 config.pipeline = 1; 667 config.showerrors = 0; 668 config.randomkeys = 0; 669 config.randomkeys_keyspacelen = 0; 670 config.quiet = 0; 671 config.csv = 0; 672 config.loop = 0; 673 config.idlemode = 0; 674 config.latency = NULL; 675 config.clients = listCreate(); 676 config.hostip = "127.0.0.1"; 677 config.hostport = 6379; 678 config.hostsocket = NULL; 679 config.tests = NULL; 680 config.dbnum = 0; 681 config.auth = NULL; 682 683 i = parseOptions(argc,argv); 684 argc -= i; 685 argv += i; 686 687 config.latency = zmalloc(sizeof(long long)*config.requests); 688 689 if (config.keepalive == 0) { 690 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"); 691 } 692 693 if (config.idlemode) { 694 printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients); 695 c = createClient("",0,NULL); /* will never receive a reply */ 696 createMissingClients(c); 697 aeMain(config.el); 698 /* and will wait for every */ 699 } 700 701 /* Run benchmark with command in the remainder of the arguments. */ 702 if (argc) { 703 sds title = sdsnew(argv[0]); 704 for (i = 1; i < argc; i++) { 705 title = sdscatlen(title, " ", 1); 706 title = sdscatlen(title, (char*)argv[i], strlen(argv[i])); 707 } 708 709 do { 710 len = redisFormatCommandArgv(&cmd,argc,argv,NULL); 711 benchmark(title,cmd,len); 712 free(cmd); 713 } while(config.loop); 714 715 return 0; 716 } 717 718 /* Run default benchmark suite. */ 719 data = zmalloc(config.datasize+1); 720 do { 721 memset(data,'x',config.datasize); 722 data[config.datasize] = '\0'; 723 724 if (test_is_selected("ping_inline") || test_is_selected("ping")) 725 benchmark("PING_INLINE","PING\r\n",6); 726 727 if (test_is_selected("ping_mbulk") || test_is_selected("ping")) { 728 len = redisFormatCommand(&cmd,"PING"); 729 benchmark("PING_BULK",cmd,len); 730 free(cmd); 731 } 732 733 if (test_is_selected("set")) { 734 len = redisFormatCommand(&cmd,"SET key:__rand_int__ %s",data); 735 benchmark("SET",cmd,len); 736 free(cmd); 737 } 738 739 if (test_is_selected("get")) { 740 len = redisFormatCommand(&cmd,"GET key:__rand_int__"); 741 benchmark("GET",cmd,len); 742 free(cmd); 743 } 744 745 if (test_is_selected("incr")) { 746 len = redisFormatCommand(&cmd,"INCR counter:__rand_int__"); 747 benchmark("INCR",cmd,len); 748 free(cmd); 749 } 750 751 if (test_is_selected("lpush")) { 752 len = redisFormatCommand(&cmd,"LPUSH mylist %s",data); 753 benchmark("LPUSH",cmd,len); 754 free(cmd); 755 } 756 757 if (test_is_selected("rpush")) { 758 len = redisFormatCommand(&cmd,"RPUSH mylist %s",data); 759 benchmark("RPUSH",cmd,len); 760 free(cmd); 761 } 762 763 if (test_is_selected("lpop")) { 764 len = redisFormatCommand(&cmd,"LPOP mylist"); 765 benchmark("LPOP",cmd,len); 766 free(cmd); 767 } 768 769 if (test_is_selected("rpop")) { 770 len = redisFormatCommand(&cmd,"RPOP mylist"); 771 benchmark("RPOP",cmd,len); 772 free(cmd); 773 } 774 775 if (test_is_selected("sadd")) { 776 len = redisFormatCommand(&cmd, 777 "SADD myset element:__rand_int__"); 778 benchmark("SADD",cmd,len); 779 free(cmd); 780 } 781 782 if (test_is_selected("hset")) { 783 len = redisFormatCommand(&cmd, 784 "HSET myset:__rand_int__ element:__rand_int__ %s",data); 785 benchmark("HSET",cmd,len); 786 free(cmd); 787 } 788 789 if (test_is_selected("spop")) { 790 len = redisFormatCommand(&cmd,"SPOP myset"); 791 benchmark("SPOP",cmd,len); 792 free(cmd); 793 } 794 795 if (test_is_selected("lrange") || 796 test_is_selected("lrange_100") || 797 test_is_selected("lrange_300") || 798 test_is_selected("lrange_500") || 799 test_is_selected("lrange_600")) 800 { 801 len = redisFormatCommand(&cmd,"LPUSH mylist %s",data); 802 benchmark("LPUSH (needed to benchmark LRANGE)",cmd,len); 803 free(cmd); 804 } 805 806 if (test_is_selected("lrange") || test_is_selected("lrange_100")) { 807 len = redisFormatCommand(&cmd,"LRANGE mylist 0 99"); 808 benchmark("LRANGE_100 (first 100 elements)",cmd,len); 809 free(cmd); 810 } 811 812 if (test_is_selected("lrange") || test_is_selected("lrange_300")) { 813 len = redisFormatCommand(&cmd,"LRANGE mylist 0 299"); 814 benchmark("LRANGE_300 (first 300 elements)",cmd,len); 815 free(cmd); 816 } 817 818 if (test_is_selected("lrange") || test_is_selected("lrange_500")) { 819 len = redisFormatCommand(&cmd,"LRANGE mylist 0 449"); 820 benchmark("LRANGE_500 (first 450 elements)",cmd,len); 821 free(cmd); 822 } 823 824 if (test_is_selected("lrange") || test_is_selected("lrange_600")) { 825 len = redisFormatCommand(&cmd,"LRANGE mylist 0 599"); 826 benchmark("LRANGE_600 (first 600 elements)",cmd,len); 827 free(cmd); 828 } 829 830 if (test_is_selected("mset")) { 831 const char *argv[21]; 832 argv[0] = "MSET"; 833 for (i = 1; i < 21; i += 2) { 834 argv[i] = "key:__rand_int__"; 835 argv[i+1] = data; 836 } 837 len = redisFormatCommandArgv(&cmd,21,argv,NULL); 838 benchmark("MSET (10 keys)",cmd,len); 839 free(cmd); 840 } 841 842 if (!config.csv) printf("\n"); 843 } while(config.loop); 844 845 return 0; 846 } 847