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