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