/benchmark.c
C | 460 lines | 380 code | 48 blank | 32 comment | 77 complexity | c96ce71da8d70bf4e46a1b92e594d1d5 MD5 | raw file
Possible License(s): BSD-3-Clause
1/* Redis benchmark utility. 2 * 3 * Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * * Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * * Neither the name of Redis nor the names of its contributors may be used 15 * to endorse or promote products derived from this software without 16 * specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31#include <stdio.h> 32#include <string.h> 33#include <stdlib.h> 34#include <unistd.h> 35#include <errno.h> 36#include <sys/time.h> 37#include <signal.h> 38#include <assert.h> 39 40#include "ae.h" 41#include "anet.h" 42#include "sds.h" 43#include "adlist.h" 44#include "zmalloc.h" 45 46#define REPLY_INT 0 47#define REPLY_RETCODE 1 48#define REPLY_BULK 2 49 50#define CLIENT_CONNECTING 0 51#define CLIENT_SENDQUERY 1 52#define CLIENT_READREPLY 2 53 54#define MAX_LATENCY 5000 55 56#define REDIS_NOTUSED(V) ((void) V) 57 58static struct config { 59 int numclients; 60 int requests; 61 int liveclients; 62 int donerequests; 63 int keysize; 64 int datasize; 65 aeEventLoop *el; 66 char *hostip; 67 int hostport; 68 int keepalive; 69 long long start; 70 long long totlatency; 71 int *latency; 72 list *clients; 73 int quiet; 74 int loop; 75} config; 76 77typedef struct _client { 78 int state; 79 int fd; 80 sds obuf; 81 sds ibuf; 82 int readlen; /* readlen == -1 means read a single line */ 83 unsigned int written; /* bytes of 'obuf' already written */ 84 int replytype; 85 long long start; /* start time in milliseconds */ 86} *client; 87 88/* Prototypes */ 89static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask); 90static void createMissingClients(client c); 91 92/* Implementation */ 93static long long mstime(void) { 94 struct timeval tv; 95 long long mst; 96 97 gettimeofday(&tv, NULL); 98 mst = ((long)tv.tv_sec)*1000; 99 mst += tv.tv_usec/1000; 100 return mst; 101} 102 103static void freeClient(client c) { 104 listNode *ln; 105 106 aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE); 107 aeDeleteFileEvent(config.el,c->fd,AE_READABLE); 108 sdsfree(c->ibuf); 109 sdsfree(c->obuf); 110 close(c->fd); 111 zfree(c); 112 config.liveclients--; 113 ln = listSearchKey(config.clients,c); 114 assert(ln != NULL); 115 listDelNode(config.clients,ln); 116} 117 118static void freeAllClients(void) { 119 listNode *ln = config.clients->head, *next; 120 121 while(ln) { 122 next = ln->next; 123 freeClient(ln->value); 124 ln = next; 125 } 126} 127 128static void resetClient(client c) { 129 aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE); 130 aeDeleteFileEvent(config.el,c->fd,AE_READABLE); 131 aeCreateFileEvent(config.el,c->fd, AE_WRITABLE,writeHandler,c,NULL); 132 sdsfree(c->ibuf); 133 c->ibuf = sdsempty(); 134 c->readlen = (c->replytype == REPLY_BULK) ? -1 : 0; 135 c->written = 0; 136 c->state = CLIENT_SENDQUERY; 137 c->start = mstime(); 138} 139 140static void clientDone(client c) { 141 long long latency; 142 config.donerequests ++; 143 latency = mstime() - c->start; 144 if (latency > MAX_LATENCY) latency = MAX_LATENCY; 145 config.latency[latency]++; 146 147 if (config.donerequests == config.requests) { 148 freeClient(c); 149 aeStop(config.el); 150 return; 151 } 152 if (config.keepalive) { 153 resetClient(c); 154 } else { 155 config.liveclients--; 156 createMissingClients(c); 157 config.liveclients++; 158 freeClient(c); 159 } 160} 161 162static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) 163{ 164 char buf[1024]; 165 int nread; 166 client c = privdata; 167 REDIS_NOTUSED(el); 168 REDIS_NOTUSED(fd); 169 REDIS_NOTUSED(mask); 170 171 nread = read(c->fd, buf, 1024); 172 if (nread == -1) { 173 fprintf(stderr, "Reading from socket: %s\n", strerror(errno)); 174 freeClient(c); 175 return; 176 } 177 if (nread == 0) { 178 fprintf(stderr, "EOF from client\n"); 179 freeClient(c); 180 return; 181 } 182 c->ibuf = sdscatlen(c->ibuf,buf,nread); 183 184 if (c->replytype == REPLY_INT || 185 c->replytype == REPLY_RETCODE || 186 (c->replytype == REPLY_BULK && c->readlen == -1)) { 187 char *p; 188 189 if ((p = strchr(c->ibuf,'\n')) != NULL) { 190 if (c->replytype == REPLY_BULK) { 191 *p = '\0'; 192 *(p-1) = '\0'; 193 if (memcmp(c->ibuf,"nil",3) == 0) { 194 clientDone(c); 195 return; 196 } 197 c->readlen = atoi(c->ibuf)+2; 198 c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1); 199 } else { 200 c->ibuf = sdstrim(c->ibuf,"\r\n"); 201 clientDone(c); 202 return; 203 } 204 } 205 } 206 /* bulk read */ 207 if ((unsigned)c->readlen == sdslen(c->ibuf)) 208 clientDone(c); 209} 210 211static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) 212{ 213 client c = privdata; 214 REDIS_NOTUSED(el); 215 REDIS_NOTUSED(fd); 216 REDIS_NOTUSED(mask); 217 218 if (c->state == CLIENT_CONNECTING) { 219 c->state = CLIENT_SENDQUERY; 220 c->start = mstime(); 221 } 222 if (sdslen(c->obuf) > c->written) { 223 void *ptr = c->obuf+c->written; 224 int len = sdslen(c->obuf) - c->written; 225 int nwritten = write(c->fd, ptr, len); 226 if (nwritten == -1) { 227 fprintf(stderr, "Writing to socket: %s\n", strerror(errno)); 228 freeClient(c); 229 return; 230 } 231 c->written += nwritten; 232 if (sdslen(c->obuf) == c->written) { 233 aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE); 234 aeCreateFileEvent(config.el,c->fd,AE_READABLE,readHandler,c,NULL); 235 c->state = CLIENT_READREPLY; 236 } 237 } 238} 239 240static client createClient(void) { 241 client c = zmalloc(sizeof(struct _client)); 242 char err[ANET_ERR_LEN]; 243 244 c->fd = anetTcpNonBlockConnect(err,config.hostip,config.hostport); 245 if (c->fd == ANET_ERR) { 246 zfree(c); 247 fprintf(stderr,"Connect: %s\n",err); 248 return NULL; 249 } 250 anetTcpNoDelay(NULL,c->fd); 251 c->obuf = sdsempty(); 252 c->ibuf = sdsempty(); 253 c->readlen = 0; 254 c->written = 0; 255 c->state = CLIENT_CONNECTING; 256 aeCreateFileEvent(config.el, c->fd, AE_WRITABLE, writeHandler, c, NULL); 257 config.liveclients++; 258 listAddNodeTail(config.clients,c); 259 return c; 260} 261 262static void createMissingClients(client c) { 263 while(config.liveclients < config.numclients) { 264 client new = createClient(); 265 if (!new) continue; 266 sdsfree(new->obuf); 267 new->obuf = sdsdup(c->obuf); 268 new->replytype = c->replytype; 269 if (c->replytype == REPLY_BULK) 270 new->readlen = -1; 271 } 272} 273 274static void showLatencyReport(char *title) { 275 int j, seen = 0; 276 float perc, reqpersec; 277 278 reqpersec = (float)config.donerequests/((float)config.totlatency/1000); 279 if (!config.quiet) { 280 printf("====== %s ======\n", title); 281 printf(" %d requests completed in %.2f seconds\n", config.donerequests, 282 (float)config.totlatency/1000); 283 printf(" %d parallel clients\n", config.numclients); 284 printf(" %d bytes payload\n", config.datasize); 285 printf(" keep alive: %d\n", config.keepalive); 286 printf("\n"); 287 for (j = 0; j <= MAX_LATENCY; j++) { 288 if (config.latency[j]) { 289 seen += config.latency[j]; 290 perc = ((float)seen*100)/config.donerequests; 291 printf("%.2f%% <= %d milliseconds\n", perc, j); 292 } 293 } 294 printf("%.2f requests per second\n\n", reqpersec); 295 } else { 296 printf("%s: %.2f requests per second\n", title, reqpersec); 297 } 298} 299 300static void prepareForBenchmark(void) 301{ 302 memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1)); 303 config.start = mstime(); 304 config.donerequests = 0; 305} 306 307static void endBenchmark(char *title) { 308 config.totlatency = mstime()-config.start; 309 showLatencyReport(title); 310 freeAllClients(); 311} 312 313void parseOptions(int argc, char **argv) { 314 int i; 315 316 for (i = 1; i < argc; i++) { 317 int lastarg = i==argc-1; 318 319 if (!strcmp(argv[i],"-c") && !lastarg) { 320 config.numclients = atoi(argv[i+1]); 321 i++; 322 } else if (!strcmp(argv[i],"-n") && !lastarg) { 323 config.requests = atoi(argv[i+1]); 324 i++; 325 } else if (!strcmp(argv[i],"-k") && !lastarg) { 326 config.keepalive = atoi(argv[i+1]); 327 i++; 328 } else if (!strcmp(argv[i],"-h") && !lastarg) { 329 char *ip = zmalloc(32); 330 if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) { 331 printf("Can't resolve %s\n", argv[i]); 332 exit(1); 333 } 334 config.hostip = ip; 335 i++; 336 } else if (!strcmp(argv[i],"-p") && !lastarg) { 337 config.hostport = atoi(argv[i+1]); 338 i++; 339 } else if (!strcmp(argv[i],"-d") && !lastarg) { 340 config.datasize = atoi(argv[i+1]); 341 i++; 342 if (config.datasize < 1) config.datasize=1; 343 if (config.datasize > 1024*1024) config.datasize = 1024*1024; 344 } else if (!strcmp(argv[i],"-q")) { 345 config.quiet = 1; 346 } else if (!strcmp(argv[i],"-l")) { 347 config.loop = 1; 348 } else { 349 printf("Wrong option '%s' or option argument missing\n\n",argv[i]); 350 printf("Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n"); 351 printf(" -h <hostname> Server hostname (default 127.0.0.1)\n"); 352 printf(" -p <hostname> Server port (default 6379)\n"); 353 printf(" -c <clients> Number of parallel connections (default 50)\n"); 354 printf(" -n <requests> Total number of requests (default 10000)\n"); 355 printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n"); 356 printf(" -k <boolean> 1=keep alive 0=reconnect (default 1)\n"); 357 printf(" -q Quiet. Just show query/sec values\n"); 358 printf(" -l Loop. Run the tests forever\n"); 359 exit(1); 360 } 361 } 362} 363 364int main(int argc, char **argv) { 365 client c; 366 367 signal(SIGHUP, SIG_IGN); 368 signal(SIGPIPE, SIG_IGN); 369 370 config.numclients = 50; 371 config.requests = 10000; 372 config.liveclients = 0; 373 config.el = aeCreateEventLoop(); 374 config.keepalive = 1; 375 config.donerequests = 0; 376 config.datasize = 3; 377 config.quiet = 0; 378 config.loop = 0; 379 config.latency = NULL; 380 config.clients = listCreate(); 381 config.latency = zmalloc(sizeof(int)*(MAX_LATENCY+1)); 382 383 config.hostip = "127.0.0.1"; 384 config.hostport = 6379; 385 386 parseOptions(argc,argv); 387 388 if (config.keepalive == 0) { 389 printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' in order to use a lot of clients/requests\n"); 390 } 391 392 do { 393 prepareForBenchmark(); 394 c = createClient(); 395 if (!c) exit(1); 396 c->obuf = sdscat(c->obuf,"PING\r\n"); 397 c->replytype = REPLY_RETCODE; 398 createMissingClients(c); 399 aeMain(config.el); 400 endBenchmark("PING"); 401 402 prepareForBenchmark(); 403 c = createClient(); 404 if (!c) exit(1); 405 c->obuf = sdscatprintf(c->obuf,"SET foo %d\r\n",config.datasize); 406 { 407 char *data = zmalloc(config.datasize+2); 408 memset(data,'x',config.datasize); 409 data[config.datasize] = '\r'; 410 data[config.datasize+1] = '\n'; 411 c->obuf = sdscatlen(c->obuf,data,config.datasize+2); 412 } 413 c->replytype = REPLY_RETCODE; 414 createMissingClients(c); 415 aeMain(config.el); 416 endBenchmark("SET"); 417 418 prepareForBenchmark(); 419 c = createClient(); 420 if (!c) exit(1); 421 c->obuf = sdscat(c->obuf,"GET foo\r\n"); 422 c->replytype = REPLY_BULK; 423 c->readlen = -1; 424 createMissingClients(c); 425 aeMain(config.el); 426 endBenchmark("GET"); 427 428 prepareForBenchmark(); 429 c = createClient(); 430 if (!c) exit(1); 431 c->obuf = sdscat(c->obuf,"INCR counter\r\n"); 432 c->replytype = REPLY_INT; 433 createMissingClients(c); 434 aeMain(config.el); 435 endBenchmark("INCR"); 436 437 prepareForBenchmark(); 438 c = createClient(); 439 if (!c) exit(1); 440 c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n"); 441 c->replytype = REPLY_INT; 442 createMissingClients(c); 443 aeMain(config.el); 444 endBenchmark("LPUSH"); 445 446 prepareForBenchmark(); 447 c = createClient(); 448 if (!c) exit(1); 449 c->obuf = sdscat(c->obuf,"LPOP mylist\r\n"); 450 c->replytype = REPLY_BULK; 451 c->readlen = -1; 452 createMissingClients(c); 453 aeMain(config.el); 454 endBenchmark("LPOP"); 455 456 printf("\n"); 457 } while(config.loop); 458 459 return 0; 460}