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