1 /* Redis CLI (command line interface) 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 #include "version.h" 33 34 #include <stdio.h> 35 #include <string.h> 36 #include <stdlib.h> 37 #include <signal.h> 38 #include <unistd.h> 39 #include <time.h> 40 #include <ctype.h> 41 #include <errno.h> 42 #include <sys/stat.h> 43 #include <sys/time.h> 44 #include <assert.h> 45 #include <fcntl.h> 46 #include <limits.h> 47 #include <math.h> 48 49 #include <hiredis.h> 50 #include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */ 51 #include "zmalloc.h" 52 #include "linenoise.h" 53 #include "help.h" 54 #include "anet.h" 55 #include "ae.h" 56 57 #define UNUSED(V) ((void) V) 58 59 #define OUTPUT_STANDARD 0 60 #define OUTPUT_RAW 1 61 #define OUTPUT_CSV 2 62 #define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */ 63 #define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */ 64 #define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE" 65 #define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history" 66 67 /* --latency-dist palettes. */ 68 int spectrum_palette_color_size = 19; 69 int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196}; 70 71 int spectrum_palette_mono_size = 13; 72 int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253}; 73 74 /* The actual palette in use. */ 75 int *spectrum_palette; 76 int spectrum_palette_size; 77 78 static redisContext *context; 79 static struct config { 80 char *hostip; 81 int hostport; 82 char *hostsocket; 83 long repeat; 84 long interval; 85 int dbnum; 86 int interactive; 87 int shutdown; 88 int monitor_mode; 89 int pubsub_mode; 90 int latency_mode; 91 int latency_dist_mode; 92 int latency_history; 93 int lru_test_mode; 94 long long lru_test_sample_size; 95 int cluster_mode; 96 int cluster_reissue_command; 97 int slave_mode; 98 int pipe_mode; 99 int pipe_timeout; 100 int getrdb_mode; 101 int stat_mode; 102 int scan_mode; 103 int intrinsic_latency_mode; 104 int intrinsic_latency_duration; 105 char *pattern; 106 char *rdb_filename; 107 int bigkeys; 108 int stdinarg; /* get last arg from stdin. (-x option) */ 109 char *auth; 110 int output; /* output mode, see OUTPUT_* defines */ 111 sds mb_delim; 112 char prompt[128]; 113 char *eval; 114 int eval_ldb; 115 int eval_ldb_sync; /* Ask for synchronous mode of the Lua debugger. */ 116 int eval_ldb_end; /* Lua debugging session ended. */ 117 int last_cmd_type; 118 } config; 119 120 static volatile sig_atomic_t force_cancel_loop = 0; 121 static void usage(void); 122 static void slaveMode(void); 123 char *redisGitSHA1(void); 124 char *redisGitDirty(void); 125 126 /*------------------------------------------------------------------------------ 127 * Utility functions 128 *--------------------------------------------------------------------------- */ 129 130 static long long ustime(void) { 131 struct timeval tv; 132 long long ust; 133 134 gettimeofday(&tv, NULL); 135 ust = ((long long)tv.tv_sec)*1000000; 136 ust += tv.tv_usec; 137 return ust; 138 } 139 140 static long long mstime(void) { 141 return ustime()/1000; 142 } 143 144 static void cliRefreshPrompt(void) { 145 int len; 146 147 if (config.eval_ldb) return; 148 if (config.hostsocket != NULL) 149 len = snprintf(config.prompt,sizeof(config.prompt),"redis %s", 150 config.hostsocket); 151 else 152 len = anetFormatAddr(config.prompt, sizeof(config.prompt), 153 config.hostip, config.hostport); 154 /* Add [dbnum] if needed */ 155 if (config.dbnum != 0 && config.last_cmd_type != REDIS_REPLY_ERROR) 156 len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]", 157 config.dbnum); 158 snprintf(config.prompt+len,sizeof(config.prompt)-len,"> "); 159 } 160 161 static sds getHistoryPath() { 162 char *path = NULL; 163 sds historyPath = NULL; 164 165 /* check the env for a histfile override */ 166 path = getenv(REDIS_CLI_HISTFILE_ENV); 167 if (path != NULL && *path != '\0') { 168 if (!strcmp("/dev/null", path)) { 169 return NULL; 170 } 171 172 /* if the env is set, return it */ 173 historyPath = sdscatprintf(sdsempty(), "%s", path); 174 } else { 175 char *home = getenv("HOME"); 176 if (home != NULL && *home != '\0') { 177 /* otherwise, return the default */ 178 historyPath = sdscatprintf(sdsempty(), "%s/%s", home, REDIS_CLI_HISTFILE_DEFAULT); 179 } 180 } 181 182 return historyPath; 183 } 184 185 /*------------------------------------------------------------------------------ 186 * Help functions 187 *--------------------------------------------------------------------------- */ 188 189 #define CLI_HELP_COMMAND 1 190 #define CLI_HELP_GROUP 2 191 192 typedef struct { 193 int type; 194 int argc; 195 sds *argv; 196 sds full; 197 198 /* Only used for help on commands */ 199 struct commandHelp *org; 200 } helpEntry; 201 202 static helpEntry *helpEntries; 203 static int helpEntriesLen; 204 205 static sds cliVersion(void) { 206 sds version; 207 version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); 208 209 /* Add git commit and working tree status when available */ 210 if (strtoll(redisGitSHA1(),NULL,16)) { 211 version = sdscatprintf(version, " (git:%s", redisGitSHA1()); 212 if (strtoll(redisGitDirty(),NULL,10)) 213 version = sdscatprintf(version, "-dirty"); 214 version = sdscat(version, ")"); 215 } 216 return version; 217 } 218 219 static void cliInitHelp(void) { 220 int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp); 221 int groupslen = sizeof(commandGroups)/sizeof(char*); 222 int i, len, pos = 0; 223 helpEntry tmp; 224 225 helpEntriesLen = len = commandslen+groupslen; 226 helpEntries = malloc(sizeof(helpEntry)*len); 227 228 for (i = 0; i < groupslen; i++) { 229 tmp.argc = 1; 230 tmp.argv = malloc(sizeof(sds)); 231 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]); 232 tmp.full = tmp.argv[0]; 233 tmp.type = CLI_HELP_GROUP; 234 tmp.org = NULL; 235 helpEntries[pos++] = tmp; 236 } 237 238 for (i = 0; i < commandslen; i++) { 239 tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc); 240 tmp.full = sdsnew(commandHelp[i].name); 241 tmp.type = CLI_HELP_COMMAND; 242 tmp.org = &commandHelp[i]; 243 helpEntries[pos++] = tmp; 244 } 245 } 246 247 /* Output command help to stdout. */ 248 static void cliOutputCommandHelp(struct commandHelp *help, int group) { 249 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params); 250 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary); 251 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since); 252 if (group) { 253 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]); 254 } 255 } 256 257 /* Print generic help. */ 258 static void cliOutputGenericHelp(void) { 259 sds version = cliVersion(); 260 printf( 261 "redis-cli %s\r\n" 262 "Type: \"help @<group>\" to get a list of commands in <group>\r\n" 263 " \"help <command>\" for help on <command>\r\n" 264 " \"help <tab>\" to get a list of possible help topics\r\n" 265 " \"quit\" to exit\r\n", 266 version 267 ); 268 sdsfree(version); 269 } 270 271 /* Output all command help, filtering by group or command name. */ 272 static void cliOutputHelp(int argc, char **argv) { 273 int i, j, len; 274 int group = -1; 275 helpEntry *entry; 276 struct commandHelp *help; 277 278 if (argc == 0) { 279 cliOutputGenericHelp(); 280 return; 281 } else if (argc > 0 && argv[0][0] == '@') { 282 len = sizeof(commandGroups)/sizeof(char*); 283 for (i = 0; i < len; i++) { 284 if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) { 285 group = i; 286 break; 287 } 288 } 289 } 290 291 assert(argc > 0); 292 for (i = 0; i < helpEntriesLen; i++) { 293 entry = &helpEntries[i]; 294 if (entry->type != CLI_HELP_COMMAND) continue; 295 296 help = entry->org; 297 if (group == -1) { 298 /* Compare all arguments */ 299 if (argc == entry->argc) { 300 for (j = 0; j < argc; j++) { 301 if (strcasecmp(argv[j],entry->argv[j]) != 0) break; 302 } 303 if (j == argc) { 304 cliOutputCommandHelp(help,1); 305 } 306 } 307 } else { 308 if (group == help->group) { 309 cliOutputCommandHelp(help,0); 310 } 311 } 312 } 313 printf("\r\n"); 314 } 315 316 static void completionCallback(const char *buf, linenoiseCompletions *lc) { 317 size_t startpos = 0; 318 int mask; 319 int i; 320 size_t matchlen; 321 sds tmp; 322 323 if (strncasecmp(buf,"help ",5) == 0) { 324 startpos = 5; 325 while (isspace(buf[startpos])) startpos++; 326 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP; 327 } else { 328 mask = CLI_HELP_COMMAND; 329 } 330 331 for (i = 0; i < helpEntriesLen; i++) { 332 if (!(helpEntries[i].type & mask)) continue; 333 334 matchlen = strlen(buf+startpos); 335 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) { 336 tmp = sdsnewlen(buf,startpos); 337 tmp = sdscat(tmp,helpEntries[i].full); 338 linenoiseAddCompletion(lc,tmp); 339 sdsfree(tmp); 340 } 341 } 342 } 343 344 /*------------------------------------------------------------------------------ 345 * Networking / parsing 346 *--------------------------------------------------------------------------- */ 347 348 /* Send AUTH command to the server */ 349 static int cliAuth(void) { 350 redisReply *reply; 351 if (config.auth == NULL) return REDIS_OK; 352 353 reply = redisCommand(context,"AUTH %s",config.auth); 354 if (reply != NULL) { 355 freeReplyObject(reply); 356 return REDIS_OK; 357 } 358 return REDIS_ERR; 359 } 360 361 /* Send SELECT dbnum to the server */ 362 static int cliSelect(void) { 363 redisReply *reply; 364 if (config.dbnum == 0) return REDIS_OK; 365 366 reply = redisCommand(context,"SELECT %d",config.dbnum); 367 if (reply != NULL) { 368 int result = REDIS_OK; 369 if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR; 370 freeReplyObject(reply); 371 return result; 372 } 373 return REDIS_ERR; 374 } 375 376 /* Connect to the server. If force is not zero the connection is performed 377 * even if there is already a connected socket. */ 378 static int cliConnect(int force) { 379 if (context == NULL || force) { 380 if (context != NULL) { 381 redisFree(context); 382 } 383 384 if (config.hostsocket == NULL) { 385 context = redisConnect(config.hostip,config.hostport); 386 } else { 387 context = redisConnectUnix(config.hostsocket); 388 } 389 390 if (context->err) { 391 fprintf(stderr,"Could not connect to Redis at "); 392 if (config.hostsocket == NULL) 393 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr); 394 else 395 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr); 396 redisFree(context); 397 context = NULL; 398 return REDIS_ERR; 399 } 400 401 /* Set aggressive KEEP_ALIVE socket option in the Redis context socket 402 * in order to prevent timeouts caused by the execution of long 403 * commands. At the same time this improves the detection of real 404 * errors. */ 405 anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL); 406 407 /* Do AUTH and select the right DB. */ 408 if (cliAuth() != REDIS_OK) 409 return REDIS_ERR; 410 if (cliSelect() != REDIS_OK) 411 return REDIS_ERR; 412 } 413 return REDIS_OK; 414 } 415 416 static void cliPrintContextError(void) { 417 if (context == NULL) return; 418 fprintf(stderr,"Error: %s\n",context->errstr); 419 } 420 421 static sds cliFormatReplyTTY(redisReply *r, char *prefix) { 422 sds out = sdsempty(); 423 switch (r->type) { 424 case REDIS_REPLY_ERROR: 425 out = sdscatprintf(out,"(error) %s\n", r->str); 426 break; 427 case REDIS_REPLY_STATUS: 428 out = sdscat(out,r->str); 429 out = sdscat(out,"\n"); 430 break; 431 case REDIS_REPLY_INTEGER: 432 out = sdscatprintf(out,"(integer) %lld\n",r->integer); 433 break; 434 case REDIS_REPLY_STRING: 435 /* If you are producing output for the standard output we want 436 * a more interesting output with quoted characters and so forth */ 437 out = sdscatrepr(out,r->str,r->len); 438 out = sdscat(out,"\n"); 439 break; 440 case REDIS_REPLY_NIL: 441 out = sdscat(out,"(nil)\n"); 442 break; 443 case REDIS_REPLY_ARRAY: 444 if (r->elements == 0) { 445 out = sdscat(out,"(empty list or set)\n"); 446 } else { 447 unsigned int i, idxlen = 0; 448 char _prefixlen[16]; 449 char _prefixfmt[16]; 450 sds _prefix; 451 sds tmp; 452 453 /* Calculate chars needed to represent the largest index */ 454 i = r->elements; 455 do { 456 idxlen++; 457 i /= 10; 458 } while(i); 459 460 /* Prefix for nested multi bulks should grow with idxlen+2 spaces */ 461 memset(_prefixlen,' ',idxlen+2); 462 _prefixlen[idxlen+2] = '\0'; 463 _prefix = sdscat(sdsnew(prefix),_prefixlen); 464 465 /* Setup prefix format for every entry */ 466 snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud) ",idxlen); 467 468 for (i = 0; i < r->elements; i++) { 469 /* Don't use the prefix for the first element, as the parent 470 * caller already prepended the index number. */ 471 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1); 472 473 /* Format the multi bulk entry */ 474 tmp = cliFormatReplyTTY(r->element[i],_prefix); 475 out = sdscatlen(out,tmp,sdslen(tmp)); 476 sdsfree(tmp); 477 } 478 sdsfree(_prefix); 479 } 480 break; 481 default: 482 fprintf(stderr,"Unknown reply type: %d\n", r->type); 483 exit(1); 484 } 485 return out; 486 } 487 488 int isColorTerm(void) { 489 char *t = getenv("TERM"); 490 return t != NULL && strstr(t,"xterm") != NULL; 491 } 492 493 /* Helpe function for sdsCatColorizedLdbReply() appending colorize strings 494 * to an SDS string. */ 495 sds sdscatcolor(sds o, char *s, size_t len, char *color) { 496 if (!isColorTerm()) return sdscatlen(o,s,len); 497 498 int bold = strstr(color,"bold") != NULL; 499 int ccode = 37; /* Defaults to white. */ 500 if (strstr(color,"red")) ccode = 31; 501 else if (strstr(color,"red")) ccode = 31; 502 else if (strstr(color,"green")) ccode = 32; 503 else if (strstr(color,"yellow")) ccode = 33; 504 else if (strstr(color,"blue")) ccode = 34; 505 else if (strstr(color,"magenta")) ccode = 35; 506 else if (strstr(color,"cyan")) ccode = 36; 507 else if (strstr(color,"white")) ccode = 37; 508 509 o = sdscatfmt(o,"\033[%i;%i;49m",bold,ccode); 510 o = sdscatlen(o,s,len); 511 o = sdscat(o,"\033[0m"); 512 return o; 513 } 514 515 /* Colorize Lua debugger status replies according to the prefix they 516 * have. */ 517 sds sdsCatColorizedLdbReply(sds o, char *s, size_t len) { 518 char *color = "white"; 519 520 if (strstr(s,"<debug>")) color = "bold"; 521 if (strstr(s,"<redis>")) color = "green"; 522 if (strstr(s,"<reply>")) color = "cyan"; 523 if (strstr(s,"<error>")) color = "red"; 524 if (strstr(s,"<hint>")) color = "bold"; 525 if (strstr(s,"<value>") || strstr(s,"<retval>")) color = "magenta"; 526 if (len > 4 && isdigit(s[3])) { 527 if (s[1] == '>') color = "yellow"; /* Current line. */ 528 else if (s[2] == '#') color = "bold"; /* Break point. */ 529 } 530 return sdscatcolor(o,s,len,color); 531 } 532 533 static sds cliFormatReplyRaw(redisReply *r) { 534 sds out = sdsempty(), tmp; 535 size_t i; 536 537 switch (r->type) { 538 case REDIS_REPLY_NIL: 539 /* Nothing... */ 540 break; 541 case REDIS_REPLY_ERROR: 542 out = sdscatlen(out,r->str,r->len); 543 out = sdscatlen(out,"\n",1); 544 break; 545 case REDIS_REPLY_STATUS: 546 case REDIS_REPLY_STRING: 547 if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) { 548 /* The Lua debugger replies with arrays of simple (status) 549 * strings. We colorize the output for more fun if this 550 * is a debugging session. */ 551 552 /* Detect the end of a debugging session. */ 553 if (strstr(r->str,"<endsession>") == r->str) { 554 config.eval_ldb = 0; 555 config.eval_ldb_end = 1; /* Signal the caller session ended. */ 556 config.output = OUTPUT_STANDARD; 557 cliRefreshPrompt(); 558 } else { 559 out = sdsCatColorizedLdbReply(out,r->str,r->len); 560 } 561 } else { 562 out = sdscatlen(out,r->str,r->len); 563 } 564 break; 565 case REDIS_REPLY_INTEGER: 566 out = sdscatprintf(out,"%lld",r->integer); 567 break; 568 case REDIS_REPLY_ARRAY: 569 for (i = 0; i < r->elements; i++) { 570 if (i > 0) out = sdscat(out,config.mb_delim); 571 tmp = cliFormatReplyRaw(r->element[i]); 572 out = sdscatlen(out,tmp,sdslen(tmp)); 573 sdsfree(tmp); 574 } 575 break; 576 default: 577 fprintf(stderr,"Unknown reply type: %d\n", r->type); 578 exit(1); 579 } 580 return out; 581 } 582 583 static sds cliFormatReplyCSV(redisReply *r) { 584 unsigned int i; 585 586 sds out = sdsempty(); 587 switch (r->type) { 588 case REDIS_REPLY_ERROR: 589 out = sdscat(out,"ERROR,"); 590 out = sdscatrepr(out,r->str,strlen(r->str)); 591 break; 592 case REDIS_REPLY_STATUS: 593 out = sdscatrepr(out,r->str,r->len); 594 break; 595 case REDIS_REPLY_INTEGER: 596 out = sdscatprintf(out,"%lld",r->integer); 597 break; 598 case REDIS_REPLY_STRING: 599 out = sdscatrepr(out,r->str,r->len); 600 break; 601 case REDIS_REPLY_NIL: 602 out = sdscat(out,"NIL"); 603 break; 604 case REDIS_REPLY_ARRAY: 605 for (i = 0; i < r->elements; i++) { 606 sds tmp = cliFormatReplyCSV(r->element[i]); 607 out = sdscatlen(out,tmp,sdslen(tmp)); 608 if (i != r->elements-1) out = sdscat(out,","); 609 sdsfree(tmp); 610 } 611 break; 612 default: 613 fprintf(stderr,"Unknown reply type: %d\n", r->type); 614 exit(1); 615 } 616 return out; 617 } 618 619 static int cliReadReply(int output_raw_strings) { 620 void *_reply; 621 redisReply *reply; 622 sds out = NULL; 623 int output = 1; 624 625 if (redisGetReply(context,&_reply) != REDIS_OK) { 626 if (config.shutdown) { 627 redisFree(context); 628 context = NULL; 629 return REDIS_OK; 630 } 631 if (config.interactive) { 632 /* Filter cases where we should reconnect */ 633 if (context->err == REDIS_ERR_IO && 634 (errno == ECONNRESET || errno == EPIPE)) 635 return REDIS_ERR; 636 if (context->err == REDIS_ERR_EOF) 637 return REDIS_ERR; 638 } 639 cliPrintContextError(); 640 exit(1); 641 return REDIS_ERR; /* avoid compiler warning */ 642 } 643 644 reply = (redisReply*)_reply; 645 646 config.last_cmd_type = reply->type; 647 648 /* Check if we need to connect to a different node and reissue the 649 * request. */ 650 if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR && 651 (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK"))) 652 { 653 char *p = reply->str, *s; 654 int slot; 655 656 output = 0; 657 /* Comments show the position of the pointer as: 658 * 659 * [S] for pointer 's' 660 * [P] for pointer 'p' 661 */ 662 s = strchr(p,' '); /* MOVED[S]3999 127.0.0.1:6381 */ 663 p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */ 664 *p = '\0'; 665 slot = atoi(s+1); 666 s = strchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */ 667 *s = '\0'; 668 sdsfree(config.hostip); 669 config.hostip = sdsnew(p+1); 670 config.hostport = atoi(s+1); 671 if (config.interactive) 672 printf("-> Redirected to slot [%d] located at %s:%d\n", 673 slot, config.hostip, config.hostport); 674 config.cluster_reissue_command = 1; 675 cliRefreshPrompt(); 676 } 677 678 if (output) { 679 if (output_raw_strings) { 680 out = cliFormatReplyRaw(reply); 681 } else { 682 if (config.output == OUTPUT_RAW) { 683 out = cliFormatReplyRaw(reply); 684 out = sdscat(out,"\n"); 685 } else if (config.output == OUTPUT_STANDARD) { 686 out = cliFormatReplyTTY(reply,""); 687 } else if (config.output == OUTPUT_CSV) { 688 out = cliFormatReplyCSV(reply); 689 out = sdscat(out,"\n"); 690 } 691 } 692 fwrite(out,sdslen(out),1,stdout); 693 sdsfree(out); 694 } 695 freeReplyObject(reply); 696 return REDIS_OK; 697 } 698 699 static int cliSendCommand(int argc, char **argv, int repeat) { 700 char *command = argv[0]; 701 size_t *argvlen; 702 int j, output_raw; 703 704 if (!config.eval_ldb && /* In debugging mode, let's pass "help" to Redis. */ 705 (!strcasecmp(command,"help") || !strcasecmp(command,"?"))) { 706 cliOutputHelp(--argc, ++argv); 707 return REDIS_OK; 708 } 709 710 if (context == NULL) return REDIS_ERR; 711 712 output_raw = 0; 713 if (!strcasecmp(command,"info") || 714 (argc >= 2 && !strcasecmp(command,"debug") && 715 (!strcasecmp(argv[1],"jemalloc") || 716 !strcasecmp(argv[1],"htstats"))) || 717 (argc == 2 && !strcasecmp(command,"cluster") && 718 (!strcasecmp(argv[1],"nodes") || 719 !strcasecmp(argv[1],"info"))) || 720 (argc == 2 && !strcasecmp(command,"client") && 721 !strcasecmp(argv[1],"list")) || 722 (argc == 3 && !strcasecmp(command,"latency") && 723 !strcasecmp(argv[1],"graph")) || 724 (argc == 2 && !strcasecmp(command,"latency") && 725 !strcasecmp(argv[1],"doctor"))) 726 { 727 output_raw = 1; 728 } 729 730 if (!strcasecmp(command,"shutdown")) config.shutdown = 1; 731 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; 732 if (!strcasecmp(command,"subscribe") || 733 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; 734 if (!strcasecmp(command,"sync") || 735 !strcasecmp(command,"psync")) config.slave_mode = 1; 736 737 /* Setup argument length */ 738 argvlen = malloc(argc*sizeof(size_t)); 739 for (j = 0; j < argc; j++) 740 argvlen[j] = sdslen(argv[j]); 741 742 while(repeat--) { 743 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); 744 while (config.monitor_mode) { 745 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 746 fflush(stdout); 747 } 748 749 if (config.pubsub_mode) { 750 if (config.output != OUTPUT_RAW) 751 printf("Reading messages... (press Ctrl-C to quit)\n"); 752 while (1) { 753 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 754 } 755 } 756 757 if (config.slave_mode) { 758 printf("Entering slave output mode... (press Ctrl-C to quit)\n"); 759 slaveMode(); 760 config.slave_mode = 0; 761 free(argvlen); 762 return REDIS_ERR; /* Error = slaveMode lost connection to master */ 763 } 764 765 if (cliReadReply(output_raw) != REDIS_OK) { 766 free(argvlen); 767 return REDIS_ERR; 768 } else { 769 /* Store database number when SELECT was successfully executed. */ 770 if (!strcasecmp(command,"select") && argc == 2) { 771 config.dbnum = atoi(argv[1]); 772 cliRefreshPrompt(); 773 } else if (!strcasecmp(command,"auth") && argc == 2) { 774 cliSelect(); 775 } 776 } 777 if (config.interval) usleep(config.interval); 778 fflush(stdout); /* Make it grep friendly */ 779 } 780 781 free(argvlen); 782 return REDIS_OK; 783 } 784 785 /* Send a command reconnecting the link if needed. */ 786 static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ...) { 787 redisReply *reply = NULL; 788 int tries = 0; 789 va_list ap; 790 791 assert(!c->err); 792 while(reply == NULL) { 793 while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) { 794 printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */ 795 printf("Reconnecting... %d\r", ++tries); 796 fflush(stdout); 797 798 redisFree(c); 799 c = redisConnect(config.hostip,config.hostport); 800 usleep(1000000); 801 } 802 803 va_start(ap,fmt); 804 reply = redisvCommand(c,fmt,ap); 805 va_end(ap); 806 807 if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) { 808 fprintf(stderr, "Error: %s\n", c->errstr); 809 exit(1); 810 } else if (tries > 0) { 811 printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */ 812 } 813 } 814 815 context = c; 816 return reply; 817 } 818 819 /*------------------------------------------------------------------------------ 820 * User interface 821 *--------------------------------------------------------------------------- */ 822 823 static int parseOptions(int argc, char **argv) { 824 int i; 825 826 for (i = 1; i < argc; i++) { 827 int lastarg = i==argc-1; 828 829 if (!strcmp(argv[i],"-h") && !lastarg) { 830 sdsfree(config.hostip); 831 config.hostip = sdsnew(argv[++i]); 832 } else if (!strcmp(argv[i],"-h") && lastarg) { 833 usage(); 834 } else if (!strcmp(argv[i],"--help")) { 835 usage(); 836 } else if (!strcmp(argv[i],"-x")) { 837 config.stdinarg = 1; 838 } else if (!strcmp(argv[i],"-p") && !lastarg) { 839 config.hostport = atoi(argv[++i]); 840 } else if (!strcmp(argv[i],"-s") && !lastarg) { 841 config.hostsocket = argv[++i]; 842 } else if (!strcmp(argv[i],"-r") && !lastarg) { 843 config.repeat = strtoll(argv[++i],NULL,10); 844 } else if (!strcmp(argv[i],"-i") && !lastarg) { 845 double seconds = atof(argv[++i]); 846 config.interval = seconds*1000000; 847 } else if (!strcmp(argv[i],"-n") && !lastarg) { 848 config.dbnum = atoi(argv[++i]); 849 } else if (!strcmp(argv[i],"-a") && !lastarg) { 850 config.auth = argv[++i]; 851 } else if (!strcmp(argv[i],"--raw")) { 852 config.output = OUTPUT_RAW; 853 } else if (!strcmp(argv[i],"--no-raw")) { 854 config.output = OUTPUT_STANDARD; 855 } else if (!strcmp(argv[i],"--csv")) { 856 config.output = OUTPUT_CSV; 857 } else if (!strcmp(argv[i],"--latency")) { 858 config.latency_mode = 1; 859 } else if (!strcmp(argv[i],"--latency-dist")) { 860 config.latency_dist_mode = 1; 861 } else if (!strcmp(argv[i],"--mono")) { 862 spectrum_palette = spectrum_palette_mono; 863 spectrum_palette_size = spectrum_palette_mono_size; 864 } else if (!strcmp(argv[i],"--latency-history")) { 865 config.latency_mode = 1; 866 config.latency_history = 1; 867 } else if (!strcmp(argv[i],"--lru-test") && !lastarg) { 868 config.lru_test_mode = 1; 869 config.lru_test_sample_size = strtoll(argv[++i],NULL,10); 870 } else if (!strcmp(argv[i],"--slave")) { 871 config.slave_mode = 1; 872 } else if (!strcmp(argv[i],"--stat")) { 873 config.stat_mode = 1; 874 } else if (!strcmp(argv[i],"--scan")) { 875 config.scan_mode = 1; 876 } else if (!strcmp(argv[i],"--pattern") && !lastarg) { 877 config.pattern = argv[++i]; 878 } else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) { 879 config.intrinsic_latency_mode = 1; 880 config.intrinsic_latency_duration = atoi(argv[++i]); 881 } else if (!strcmp(argv[i],"--rdb") && !lastarg) { 882 config.getrdb_mode = 1; 883 config.rdb_filename = argv[++i]; 884 } else if (!strcmp(argv[i],"--pipe")) { 885 config.pipe_mode = 1; 886 } else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) { 887 config.pipe_timeout = atoi(argv[++i]); 888 } else if (!strcmp(argv[i],"--bigkeys")) { 889 config.bigkeys = 1; 890 } else if (!strcmp(argv[i],"--eval") && !lastarg) { 891 config.eval = argv[++i]; 892 } else if (!strcmp(argv[i],"--ldb")) { 893 config.eval_ldb = 1; 894 config.output = OUTPUT_RAW; 895 } else if (!strcmp(argv[i],"--ldb-sync-mode")) { 896 config.eval_ldb = 1; 897 config.eval_ldb_sync = 1; 898 config.output = OUTPUT_RAW; 899 } else if (!strcmp(argv[i],"-c")) { 900 config.cluster_mode = 1; 901 } else if (!strcmp(argv[i],"-d") && !lastarg) { 902 sdsfree(config.mb_delim); 903 config.mb_delim = sdsnew(argv[++i]); 904 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { 905 sds version = cliVersion(); 906 printf("redis-cli %s\n", version); 907 sdsfree(version); 908 exit(0); 909 } else { 910 if (argv[i][0] == '-') { 911 fprintf(stderr, 912 "Unrecognized option or bad number of args for: '%s'\n", 913 argv[i]); 914 exit(1); 915 } else { 916 /* Likely the command name, stop here. */ 917 break; 918 } 919 } 920 } 921 922 /* --ldb requires --eval. */ 923 if (config.eval_ldb && config.eval == NULL) { 924 fprintf(stderr,"Options --ldb and --ldb-sync-mode require --eval.\n"); 925 fprintf(stderr,"Try %s --help for more information.\n", argv[0]); 926 exit(1); 927 } 928 return i; 929 } 930 931 static sds readArgFromStdin(void) { 932 char buf[1024]; 933 sds arg = sdsempty(); 934 935 while(1) { 936 int nread = read(fileno(stdin),buf,1024); 937 938 if (nread == 0) break; 939 else if (nread == -1) { 940 perror("Reading from standard input"); 941 exit(1); 942 } 943 arg = sdscatlen(arg,buf,nread); 944 } 945 return arg; 946 } 947 948 static void usage(void) { 949 sds version = cliVersion(); 950 fprintf(stderr, 951 "redis-cli %s\n" 952 "\n" 953 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" 954 " -h <hostname> Server hostname (default: 127.0.0.1).\n" 955 " -p <port> Server port (default: 6379).\n" 956 " -s <socket> Server socket (overrides hostname and port).\n" 957 " -a <password> Password to use when connecting to the server.\n" 958 " -r <repeat> Execute specified command N times.\n" 959 " -i <interval> When -r is used, waits <interval> seconds per command.\n" 960 " It is possible to specify sub-second times like -i 0.1.\n" 961 " -n <db> Database number.\n" 962 " -x Read last argument from STDIN.\n" 963 " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n" 964 " -c Enable cluster mode (follow -ASK and -MOVED redirections).\n" 965 " --raw Use raw formatting for replies (default when STDOUT is\n" 966 " not a tty).\n" 967 " --no-raw Force formatted output even when STDOUT is not a tty.\n" 968 " --csv Output in CSV format.\n" 969 " --stat Print rolling stats about server: mem, clients, ...\n" 970 " --latency Enter a special mode continuously sampling latency.\n" 971 " --latency-history Like --latency but tracking latency changes over time.\n" 972 " Default time interval is 15 sec. Change it using -i.\n" 973 " --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n" 974 " Default time interval is 1 sec. Change it using -i.\n" 975 " --lru-test <keys> Simulate a cache workload with an 80-20 distribution.\n" 976 " --slave Simulate a slave showing commands received from the master.\n" 977 " --rdb <filename> Transfer an RDB dump from remote server to local file.\n" 978 " --pipe Transfer raw Redis protocol from stdin to server.\n" 979 " --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n" 980 " no reply is received within <n> seconds.\n" 981 " Default timeout: %d. Use 0 to wait forever.\n" 982 " --bigkeys Sample Redis keys looking for big keys.\n" 983 " --scan List all keys using the SCAN command.\n" 984 " --pattern <pat> Useful with --scan to specify a SCAN pattern.\n" 985 " --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n" 986 " The test will run for the specified amount of seconds.\n" 987 " --eval <file> Send an EVAL command using the Lua script at <file>.\n" 988 " --ldb Used with --eval enable the Redis Lua debugger.\n" 989 " --ldb-sync-mode Like --ldb but uses the synchronous Lua debugger, in\n" 990 " this mode the server is blocked and script changes are\n" 991 " are not rolled back from the server memory.\n" 992 " --help Output this help and exit.\n" 993 " --version Output version and exit.\n" 994 "\n" 995 "Examples:\n" 996 " cat /etc/passwd | redis-cli -x set mypasswd\n" 997 " redis-cli get mypasswd\n" 998 " redis-cli -r 100 lpush mylist x\n" 999 " redis-cli -r 100 -i 1 info | grep used_memory_human:\n" 1000 " redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n" 1001 " redis-cli --scan --pattern '*:12345*'\n" 1002 "\n" 1003 " (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n" 1004 "\n" 1005 "When no command is given, redis-cli starts in interactive mode.\n" 1006 "Type \"help\" in interactive mode for information on available commands.\n" 1007 "\n", 1008 version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT); 1009 sdsfree(version); 1010 exit(1); 1011 } 1012 1013 /* Turn the plain C strings into Sds strings */ 1014 static char **convertToSds(int count, char** args) { 1015 int j; 1016 char **sds = zmalloc(sizeof(char*)*count); 1017 1018 for(j = 0; j < count; j++) 1019 sds[j] = sdsnew(args[j]); 1020 1021 return sds; 1022 } 1023 1024 static int issueCommandRepeat(int argc, char **argv, long repeat) { 1025 while (1) { 1026 config.cluster_reissue_command = 0; 1027 if (cliSendCommand(argc,argv,repeat) != REDIS_OK) { 1028 cliConnect(1); 1029 1030 /* If we still cannot send the command print error. 1031 * We'll try to reconnect the next time. */ 1032 if (cliSendCommand(argc,argv,repeat) != REDIS_OK) { 1033 cliPrintContextError(); 1034 return REDIS_ERR; 1035 } 1036 } 1037 /* Issue the command again if we got redirected in cluster mode */ 1038 if (config.cluster_mode && config.cluster_reissue_command) { 1039 cliConnect(1); 1040 } else { 1041 break; 1042 } 1043 } 1044 return REDIS_OK; 1045 } 1046 1047 static int issueCommand(int argc, char **argv) { 1048 return issueCommandRepeat(argc, argv, config.repeat); 1049 } 1050 1051 /* Split the user provided command into multiple SDS arguments. 1052 * This function normally uses sdssplitargs() from sds.c which is able 1053 * to understand "quoted strings", escapes and so forth. However when 1054 * we are in Lua debugging mode and the "eval" command is used, we want 1055 * the remaining Lua script (after "e " or "eval ") to be passed verbatim 1056 * as a single big argument. */ 1057 static sds *cliSplitArgs(char *line, int *argc) { 1058 if (config.eval_ldb && (strstr(line,"eval ") == line || 1059 strstr(line,"e ") == line)) 1060 { 1061 sds *argv = sds_malloc(sizeof(sds)*2); 1062 *argc = 2; 1063 int len = strlen(line); 1064 int elen = line[1] == ' ' ? 2 : 5; /* "e " or "eval "? */ 1065 argv[0] = sdsnewlen(line,elen-1); 1066 argv[1] = sdsnewlen(line+elen,len-elen); 1067 return argv; 1068 } else { 1069 return sdssplitargs(line,argc); 1070 } 1071 } 1072 1073 static void repl(void) { 1074 sds historyfile = NULL; 1075 int history = 0; 1076 char *line; 1077 int argc; 1078 sds *argv; 1079 1080 config.interactive = 1; 1081 linenoiseSetMultiLine(1); 1082 linenoiseSetCompletionCallback(completionCallback); 1083 1084 /* Only use history when stdin is a tty. */ 1085 if (isatty(fileno(stdin))) { 1086 historyfile = getHistoryPath(); 1087 if (historyfile != NULL) { 1088 history = 1; 1089 linenoiseHistoryLoad(historyfile); 1090 } 1091 } 1092 1093 cliRefreshPrompt(); 1094 while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) { 1095 if (line[0] != '\0') { 1096 argv = cliSplitArgs(line,&argc); 1097 if (history) linenoiseHistoryAdd(line); 1098 if (historyfile) linenoiseHistorySave(historyfile); 1099 1100 if (argv == NULL) { 1101 printf("Invalid argument(s)\n"); 1102 free(line); 1103 continue; 1104 } else if (argc > 0) { 1105 if (strcasecmp(argv[0],"quit") == 0 || 1106 strcasecmp(argv[0],"exit") == 0) 1107 { 1108 exit(0); 1109 } else if (strcasecmp(argv[0],"restart") == 0) { 1110 if (config.eval) { 1111 config.eval_ldb = 1; 1112 config.output = OUTPUT_RAW; 1113 return; /* Return to evalMode to restart the session. */ 1114 } else { 1115 printf("Use 'restart' only in Lua debugging mode."); 1116 } 1117 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { 1118 sdsfree(config.hostip); 1119 config.hostip = sdsnew(argv[1]); 1120 config.hostport = atoi(argv[2]); 1121 cliRefreshPrompt(); 1122 cliConnect(1); 1123 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { 1124 linenoiseClearScreen(); 1125 } else { 1126 long long start_time = mstime(), elapsed; 1127 int repeat, skipargs = 0; 1128 1129 repeat = atoi(argv[0]); 1130 if (argc > 1 && repeat) { 1131 skipargs = 1; 1132 } else { 1133 repeat = 1; 1134 } 1135 1136 issueCommandRepeat(argc-skipargs, argv+skipargs, repeat); 1137 1138 /* If our debugging session ended, show the EVAL final 1139 * reply. */ 1140 if (config.eval_ldb_end) { 1141 config.eval_ldb_end = 0; 1142 cliReadReply(0); 1143 printf("\n(Lua debugging session ended%s)\n\n", 1144 config.eval_ldb_sync ? "" : 1145 " -- dataset changes rolled back"); 1146 } 1147 1148 elapsed = mstime()-start_time; 1149 if (elapsed >= 500) { 1150 printf("(%.2fs)\n",(double)elapsed/1000); 1151 } 1152 } 1153 } 1154 /* Free the argument vector */ 1155 sdsfreesplitres(argv,argc); 1156 } 1157 /* linenoise() returns malloc-ed lines like readline() */ 1158 free(line); 1159 } 1160 exit(0); 1161 } 1162 1163 static int noninteractive(int argc, char **argv) { 1164 int retval = 0; 1165 if (config.stdinarg) { 1166 argv = zrealloc(argv, (argc+1)*sizeof(char*)); 1167 argv[argc] = readArgFromStdin(); 1168 retval = issueCommand(argc+1, argv); 1169 } else { 1170 retval = issueCommand(argc, argv); 1171 } 1172 return retval; 1173 } 1174 1175 /*------------------------------------------------------------------------------ 1176 * Eval mode 1177 *--------------------------------------------------------------------------- */ 1178 1179 static int evalMode(int argc, char **argv) { 1180 sds script = NULL; 1181 FILE *fp; 1182 char buf[1024]; 1183 size_t nread; 1184 char **argv2; 1185 int j, got_comma, keys; 1186 int retval = REDIS_OK; 1187 1188 while(1) { 1189 if (config.eval_ldb) { 1190 printf( 1191 "Lua debugging session started, please use:\n" 1192 "quit -- End the session.\n" 1193 "restart -- Restart the script in debug mode again.\n" 1194 "help -- Show Lua script debugging commands.\n\n" 1195 ); 1196 } 1197 1198 sdsfree(script); 1199 script = sdsempty(); 1200 got_comma = 0; 1201 keys = 0; 1202 1203 /* Load the script from the file, as an sds string. */ 1204 fp = fopen(config.eval,"r"); 1205 if (!fp) { 1206 fprintf(stderr, 1207 "Can't open file '%s': %s\n", config.eval, strerror(errno)); 1208 exit(1); 1209 } 1210 while((nread = fread(buf,1,sizeof(buf),fp)) != 0) { 1211 script = sdscatlen(script,buf,nread); 1212 } 1213 fclose(fp); 1214 1215 /* If we are debugging a script, enable the Lua debugger. */ 1216 if (config.eval_ldb) { 1217 redisReply *reply = redisCommand(context, 1218 config.eval_ldb_sync ? 1219 "SCRIPT DEBUG sync": "SCRIPT DEBUG yes"); 1220 if (reply) freeReplyObject(reply); 1221 } 1222 1223 /* Create our argument vector */ 1224 argv2 = zmalloc(sizeof(sds)*(argc+3)); 1225 argv2[0] = sdsnew("EVAL"); 1226 argv2[1] = script; 1227 for (j = 0; j < argc; j++) { 1228 if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) { 1229 got_comma = 1; 1230 continue; 1231 } 1232 argv2[j+3-got_comma] = sdsnew(argv[j]); 1233 if (!got_comma) keys++; 1234 } 1235 argv2[2] = sdscatprintf(sdsempty(),"%d",keys); 1236 1237 /* Call it */ 1238 int eval_ldb = config.eval_ldb; /* Save it, may be reverteed. */ 1239 retval = issueCommand(argc+3-got_comma, argv2); 1240 if (eval_ldb) { 1241 if (!config.eval_ldb) { 1242 /* If the debugging session ended immediately, there was an 1243 * error compiling the script. Show it and don't enter 1244 * the REPL at all. */ 1245 printf("Eval debugging session can't start:\n"); 1246 cliReadReply(0); 1247 break; /* Return to the caller. */ 1248 } else { 1249 strncpy(config.prompt,"lua debugger> ",sizeof(config.prompt)); 1250 repl(); 1251 /* Restart the session if repl() returned. */ 1252 cliConnect(1); 1253 printf("\n"); 1254 } 1255 } else { 1256 break; /* Return to the caller. */ 1257 } 1258 } 1259 return retval; 1260 } 1261 1262 /*------------------------------------------------------------------------------ 1263 * Latency and latency history modes 1264 *--------------------------------------------------------------------------- */ 1265 1266 #define LATENCY_SAMPLE_RATE 10 /* milliseconds. */ 1267 #define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */ 1268 static void latencyMode(void) { 1269 redisReply *reply; 1270 long long start, latency, min = 0, max = 0, tot = 0, count = 0; 1271 long long history_interval = 1272 config.interval ? config.interval/1000 : 1273 LATENCY_HISTORY_DEFAULT_INTERVAL; 1274 double avg; 1275 long long history_start = mstime(); 1276 1277 if (!context) exit(1); 1278 while(1) { 1279 start = mstime(); 1280 reply = reconnectingRedisCommand(context,"PING"); 1281 if (reply == NULL) { 1282 fprintf(stderr,"\nI/O error\n"); 1283 exit(1); 1284 } 1285 latency = mstime()-start; 1286 freeReplyObject(reply); 1287 count++; 1288 if (count == 1) { 1289 min = max = tot = latency; 1290 avg = (double) latency; 1291 } else { 1292 if (latency < min) min = latency; 1293 if (latency > max) max = latency; 1294 tot += latency; 1295 avg = (double) tot/count; 1296 } 1297 printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)", 1298 min, max, avg, count); 1299 fflush(stdout); 1300 if (config.latency_history && mstime()-history_start > history_interval) 1301 { 1302 printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000); 1303 history_start = mstime(); 1304 min = max = tot = count = 0; 1305 } 1306 usleep(LATENCY_SAMPLE_RATE * 1000); 1307 } 1308 } 1309 1310 /*------------------------------------------------------------------------------ 1311 * Latency distribution mode -- requires 256 colors xterm 1312 *--------------------------------------------------------------------------- */ 1313 1314 #define LATENCY_DIST_DEFAULT_INTERVAL 1000 /* milliseconds. */ 1315 1316 /* Structure to store samples distribution. */ 1317 struct distsamples { 1318 long long max; /* Max latency to fit into this interval (usec). */ 1319 long long count; /* Number of samples in this interval. */ 1320 int character; /* Associated character in visualization. */ 1321 }; 1322 1323 /* Helper function for latencyDistMode(). Performs the spectrum visualization 1324 * of the collected samples targeting an xterm 256 terminal. 1325 * 1326 * Takes an array of distsamples structures, ordered from smaller to bigger 1327 * 'max' value. Last sample max must be 0, to mean that it olds all the 1328 * samples greater than the previous one, and is also the stop sentinel. 1329 * 1330 * "tot' is the total number of samples in the different buckets, so it 1331 * is the SUM(samples[i].conut) for i to 0 up to the max sample. 1332 * 1333 * As a side effect the function sets all the buckets count to 0. */ 1334 void showLatencyDistSamples(struct distsamples *samples, long long tot) { 1335 int j; 1336 1337 /* We convert samples into a index inside the palette 1338 * proportional to the percentage a given bucket represents. 1339 * This way intensity of the different parts of the spectrum 1340 * don't change relative to the number of requests, which avoids to 1341 * pollute the visualization with non-latency related info. */ 1342 printf("\033[38;5;0m"); /* Set foreground color to black. */ 1343 for (j = 0; ; j++) { 1344 int coloridx = 1345 ceil((float) samples[j].count / tot * (spectrum_palette_size-1)); 1346 int color = spectrum_palette[coloridx]; 1347 printf("\033[48;5;%dm%c", (int)color, samples[j].character); 1348 samples[j].count = 0; 1349 if (samples[j].max == 0) break; /* Last sample. */ 1350 } 1351 printf("\033[0m\n"); 1352 fflush(stdout); 1353 } 1354 1355 /* Show the legend: different buckets values and colors meaning, so 1356 * that the spectrum is more easily readable. */ 1357 void showLatencyDistLegend(void) { 1358 int j; 1359 1360 printf("---------------------------------------------\n"); 1361 printf(". - * # .01 .125 .25 .5 milliseconds\n"); 1362 printf("1,2,3,...,9 from 1 to 9 milliseconds\n"); 1363 printf("A,B,C,D,E 10,20,30,40,50 milliseconds\n"); 1364 printf("F,G,H,I,J .1,.2,.3,.4,.5 seconds\n"); 1365 printf("K,L,M,N,O,P,Q,? 1,2,4,8,16,30,60,>60 seconds\n"); 1366 printf("From 0 to 100%%: "); 1367 for (j = 0; j < spectrum_palette_size; j++) { 1368 printf("\033[48;5;%dm ", spectrum_palette[j]); 1369 } 1370 printf("\033[0m\n"); 1371 printf("---------------------------------------------\n"); 1372 } 1373 1374 static void latencyDistMode(void) { 1375 redisReply *reply; 1376 long long start, latency, count = 0; 1377 long long history_interval = 1378 config.interval ? config.interval/1000 : 1379 LATENCY_DIST_DEFAULT_INTERVAL; 1380 long long history_start = ustime(); 1381 int j, outputs = 0; 1382 1383 struct distsamples samples[] = { 1384 /* We use a mostly logarithmic scale, with certain linear intervals 1385 * which are more interesting than others, like 1-10 milliseconds 1386 * range. */ 1387 {10,0,'.'}, /* 0.01 ms */ 1388 {125,0,'-'}, /* 0.125 ms */ 1389 {250,0,'*'}, /* 0.25 ms */ 1390 {500,0,'#'}, /* 0.5 ms */ 1391 {1000,0,'1'}, /* 1 ms */ 1392 {2000,0,'2'}, /* 2 ms */ 1393 {3000,0,'3'}, /* 3 ms */ 1394 {4000,0,'4'}, /* 4 ms */ 1395 {5000,0,'5'}, /* 5 ms */ 1396 {6000,0,'6'}, /* 6 ms */ 1397 {7000,0,'7'}, /* 7 ms */ 1398 {8000,0,'8'}, /* 8 ms */ 1399 {9000,0,'9'}, /* 9 ms */ 1400 {10000,0,'A'}, /* 10 ms */ 1401 {20000,0,'B'}, /* 20 ms */ 1402 {30000,0,'C'}, /* 30 ms */ 1403 {40000,0,'D'}, /* 40 ms */ 1404 {50000,0,'E'}, /* 50 ms */ 1405 {100000,0,'F'}, /* 0.1 s */ 1406 {200000,0,'G'}, /* 0.2 s */ 1407 {300000,0,'H'}, /* 0.3 s */ 1408 {400000,0,'I'}, /* 0.4 s */ 1409 {500000,0,'J'}, /* 0.5 s */ 1410 {1000000,0,'K'}, /* 1 s */ 1411 {2000000,0,'L'}, /* 2 s */ 1412 {4000000,0,'M'}, /* 4 s */ 1413 {8000000,0,'N'}, /* 8 s */ 1414 {16000000,0,'O'}, /* 16 s */ 1415 {30000000,0,'P'}, /* 30 s */ 1416 {60000000,0,'Q'}, /* 1 minute */ 1417 {0,0,'?'}, /* > 1 minute */ 1418 }; 1419 1420 if (!context) exit(1); 1421 while(1) { 1422 start = ustime(); 1423 reply = reconnectingRedisCommand(context,"PING"); 1424 if (reply == NULL) { 1425 fprintf(stderr,"\nI/O error\n"); 1426 exit(1); 1427 } 1428 latency = ustime()-start; 1429 freeReplyObject(reply); 1430 count++; 1431 1432 /* Populate the relevant bucket. */ 1433 for (j = 0; ; j++) { 1434 if (samples[j].max == 0 || latency <= samples[j].max) { 1435 samples[j].count++; 1436 break; 1437 } 1438 } 1439 1440 /* From time to time show the spectrum. */ 1441 if (count && (ustime()-history_start)/1000 > history_interval) { 1442 if ((outputs++ % 20) == 0) 1443 showLatencyDistLegend(); 1444 showLatencyDistSamples(samples,count); 1445 history_start = ustime(); 1446 count = 0; 1447 } 1448 usleep(LATENCY_SAMPLE_RATE * 1000); 1449 } 1450 } 1451 1452 /*------------------------------------------------------------------------------ 1453 * Slave mode 1454 *--------------------------------------------------------------------------- */ 1455 1456 /* Sends SYNC and reads the number of bytes in the payload. Used both by 1457 * slaveMode() and getRDB(). */ 1458 unsigned long long sendSync(int fd) { 1459 /* To start we need to send the SYNC command and return the payload. 1460 * The hiredis client lib does not understand this part of the protocol 1461 * and we don't want to mess with its buffers, so everything is performed 1462 * using direct low-level I/O. */ 1463 char buf[4096], *p; 1464 ssize_t nread; 1465 1466 /* Send the SYNC command. */ 1467 if (write(fd,"SYNC\r\n",6) != 6) { 1468 fprintf(stderr,"Error writing to master\n"); 1469 exit(1); 1470 } 1471 1472 /* Read $<payload>\r\n, making sure to read just up to "\n" */ 1473 p = buf; 1474 while(1) { 1475 nread = read(fd,p,1); 1476 if (nread <= 0) { 1477 fprintf(stderr,"Error reading bulk length while SYNCing\n"); 1478 exit(1); 1479 } 1480 if (*p == '\n' && p != buf) break; 1481 if (*p != '\n') p++; 1482 } 1483 *p = '\0'; 1484 if (buf[0] == '-') { 1485 printf("SYNC with master failed: %s\n", buf); 1486 exit(1); 1487 } 1488 return strtoull(buf+1,NULL,10); 1489 } 1490 1491 static void slaveMode(void) { 1492 int fd = context->fd; 1493 unsigned long long payload = sendSync(fd); 1494 char buf[1024]; 1495 int original_output = config.output; 1496 1497 fprintf(stderr,"SYNC with master, discarding %llu " 1498 "bytes of bulk transfer...\n", payload); 1499 1500 /* Discard the payload. */ 1501 while(payload) { 1502 ssize_t nread; 1503 1504 nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); 1505 if (nread <= 0) { 1506 fprintf(stderr,"Error reading RDB payload while SYNCing\n"); 1507 exit(1); 1508 } 1509 payload -= nread; 1510 } 1511 fprintf(stderr,"SYNC done. Logging commands from master.\n"); 1512 1513 /* Now we can use hiredis to read the incoming protocol. */ 1514 config.output = OUTPUT_CSV; 1515 while (cliReadReply(0) == REDIS_OK); 1516 config.output = original_output; 1517 } 1518 1519 /*------------------------------------------------------------------------------ 1520 * RDB transfer mode 1521 *--------------------------------------------------------------------------- */ 1522 1523 /* This function implements --rdb, so it uses the replication protocol in order 1524 * to fetch the RDB file from a remote server. */ 1525 static void getRDB(void) { 1526 int s = context->fd; 1527 int fd; 1528 unsigned long long payload = sendSync(s); 1529 char buf[4096]; 1530 1531 fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n", 1532 payload, config.rdb_filename); 1533 1534 /* Write to file. */ 1535 if (!strcmp(config.rdb_filename,"-")) { 1536 fd = STDOUT_FILENO; 1537 } else { 1538 fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644); 1539 if (fd == -1) { 1540 fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename, 1541 strerror(errno)); 1542 exit(1); 1543 } 1544 } 1545 1546 while(payload) { 1547 ssize_t nread, nwritten; 1548 1549 nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); 1550 if (nread <= 0) { 1551 fprintf(stderr,"I/O Error reading RDB payload from socket\n"); 1552 exit(1); 1553 } 1554 nwritten = write(fd, buf, nread); 1555 if (nwritten != nread) { 1556 fprintf(stderr,"Error writing data to file: %s\n", 1557 strerror(errno)); 1558 exit(1); 1559 } 1560 payload -= nread; 1561 } 1562 close(s); /* Close the file descriptor ASAP as fsync() may take time. */ 1563 fsync(fd); 1564 fprintf(stderr,"Transfer finished with success.\n"); 1565 exit(0); 1566 } 1567 1568 /*------------------------------------------------------------------------------ 1569 * Bulk import (pipe) mode 1570 *--------------------------------------------------------------------------- */ 1571 1572 #define PIPEMODE_WRITE_LOOP_MAX_BYTES (128*1024) 1573 static void pipeMode(void) { 1574 int fd = context->fd; 1575 long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0; 1576 char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */ 1577 char aneterr[ANET_ERR_LEN]; 1578 redisReader *reader = redisReaderCreate(); 1579 redisReply *reply; 1580 int eof = 0; /* True once we consumed all the standard input. */ 1581 int done = 0; 1582 char magic[20]; /* Special reply we recognize. */ 1583 time_t last_read_time = time(NULL); 1584 1585 srand(time(NULL)); 1586 1587 /* Use non blocking I/O. */ 1588 if (anetNonBlock(aneterr,fd) == ANET_ERR) { 1589 fprintf(stderr, "Can't set the socket in non blocking mode: %s\n", 1590 aneterr); 1591 exit(1); 1592 } 1593 1594 /* Transfer raw protocol and read replies from the server at the same 1595 * time. */ 1596 while(!done) { 1597 int mask = AE_READABLE; 1598 1599 if (!eof || obuf_len != 0) mask |= AE_WRITABLE; 1600 mask = aeWait(fd,mask,1000); 1601 1602 /* Handle the readable state: we can read replies from the server. */ 1603 if (mask & AE_READABLE) { 1604 ssize_t nread; 1605 1606 /* Read from socket and feed the hiredis reader. */ 1607 do { 1608 nread = read(fd,ibuf,sizeof(ibuf)); 1609 if (nread == -1 && errno != EAGAIN && errno != EINTR) { 1610 fprintf(stderr, "Error reading from the server: %s\n", 1611 strerror(errno)); 1612 exit(1); 1613 } 1614 if (nread > 0) { 1615 redisReaderFeed(reader,ibuf,nread); 1616 last_read_time = time(NULL); 1617 } 1618 } while(nread > 0); 1619 1620 /* Consume replies. */ 1621 do { 1622 if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) { 1623 fprintf(stderr, "Error reading replies from server\n"); 1624 exit(1); 1625 } 1626 if (reply) { 1627 if (reply->type == REDIS_REPLY_ERROR) { 1628 fprintf(stderr,"%s\n", reply->str); 1629 errors++; 1630 } else if (eof && reply->type == REDIS_REPLY_STRING && 1631 reply->len == 20) { 1632 /* Check if this is the reply to our final ECHO 1633 * command. If so everything was received 1634 * from the server. */ 1635 if (memcmp(reply->str,magic,20) == 0) { 1636 printf("Last reply received from server.\n"); 1637 done = 1; 1638 replies--; 1639 } 1640 } 1641 replies++; 1642 freeReplyObject(reply); 1643 } 1644 } while(reply); 1645 } 1646 1647 /* Handle the writable state: we can send protocol to the server. */ 1648 if (mask & AE_WRITABLE) { 1649 ssize_t loop_nwritten = 0; 1650 1651 while(1) { 1652 /* Transfer current buffer to server. */ 1653 if (obuf_len != 0) { 1654 ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len); 1655 1656 if (nwritten == -1) { 1657 if (errno != EAGAIN && errno != EINTR) { 1658 fprintf(stderr, "Error writing to the server: %s\n", 1659 strerror(errno)); 1660 exit(1); 1661 } else { 1662 nwritten = 0; 1663 } 1664 } 1665 obuf_len -= nwritten; 1666 obuf_pos += nwritten; 1667 loop_nwritten += nwritten; 1668 if (obuf_len != 0) break; /* Can't accept more data. */ 1669 } 1670 /* If buffer is empty, load from stdin. */ 1671 if (obuf_len == 0 && !eof) { 1672 ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf)); 1673 1674 if (nread == 0) { 1675 /* The ECHO sequence starts with a "\r\n" so that if there 1676 * is garbage in the protocol we read from stdin, the ECHO 1677 * will likely still be properly formatted. 1678 * CRLF is ignored by Redis, so it has no effects. */ 1679 char echo[] = 1680 "\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n"; 1681 int j; 1682 1683 eof = 1; 1684 /* Everything transferred, so we queue a special 1685 * ECHO command that we can match in the replies 1686 * to make sure everything was read from the server. */ 1687 for (j = 0; j < 20; j++) 1688 magic[j] = rand() & 0xff; 1689 memcpy(echo+21,magic,20); 1690 memcpy(obuf,echo,sizeof(echo)-1); 1691 obuf_len = sizeof(echo)-1; 1692 obuf_pos = 0; 1693 printf("All data transferred. Waiting for the last reply...\n"); 1694 } else if (nread == -1) { 1695 fprintf(stderr, "Error reading from stdin: %s\n", 1696 strerror(errno)); 1697 exit(1); 1698 } else { 1699 obuf_len = nread; 1700 obuf_pos = 0; 1701 } 1702 } 1703 if ((obuf_len == 0 && eof) || 1704 loop_nwritten > PIPEMODE_WRITE_LOOP_MAX_BYTES) break; 1705 } 1706 } 1707 1708 /* Handle timeout, that is, we reached EOF, and we are not getting 1709 * replies from the server for a few seconds, nor the final ECHO is 1710 * received. */ 1711 if (eof && config.pipe_timeout > 0 && 1712 time(NULL)-last_read_time > config.pipe_timeout) 1713 { 1714 fprintf(stderr,"No replies for %d seconds: exiting.\n", 1715 config.pipe_timeout); 1716 errors++; 1717 break; 1718 } 1719 } 1720 redisReaderFree(reader); 1721 printf("errors: %lld, replies: %lld\n", errors, replies); 1722 if (errors) 1723 exit(1); 1724 else 1725 exit(0); 1726 } 1727 1728 /*------------------------------------------------------------------------------ 1729 * Find big keys 1730 *--------------------------------------------------------------------------- */ 1731 1732 #define TYPE_STRING 0 1733 #define TYPE_LIST 1 1734 #define TYPE_SET 2 1735 #define TYPE_HASH 3 1736 #define TYPE_ZSET 4 1737 #define TYPE_NONE 5 1738 1739 static redisReply *sendScan(unsigned long long *it) { 1740 redisReply *reply = redisCommand(context, "SCAN %llu", *it); 1741 1742 /* Handle any error conditions */ 1743 if(reply == NULL) { 1744 fprintf(stderr, "\nI/O error\n"); 1745 exit(1); 1746 } else if(reply->type == REDIS_REPLY_ERROR) { 1747 fprintf(stderr, "SCAN error: %s\n", reply->str); 1748 exit(1); 1749 } else if(reply->type != REDIS_REPLY_ARRAY) { 1750 fprintf(stderr, "Non ARRAY response from SCAN!\n"); 1751 exit(1); 1752 } else if(reply->elements != 2) { 1753 fprintf(stderr, "Invalid element count from SCAN!\n"); 1754 exit(1); 1755 } 1756 1757 /* Validate our types are correct */ 1758 assert(reply->element[0]->type == REDIS_REPLY_STRING); 1759 assert(reply->element[1]->type == REDIS_REPLY_ARRAY); 1760 1761 /* Update iterator */ 1762 *it = strtoull(reply->element[0]->str, NULL, 10); 1763 1764 return reply; 1765 } 1766 1767 static int getDbSize(void) { 1768 redisReply *reply; 1769 int size; 1770 1771 reply = redisCommand(context, "DBSIZE"); 1772 1773 if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) { 1774 fprintf(stderr, "Couldn't determine DBSIZE!\n"); 1775 exit(1); 1776 } 1777 1778 /* Grab the number of keys and free our reply */ 1779 size = reply->integer; 1780 freeReplyObject(reply); 1781 1782 return size; 1783 } 1784 1785 static int toIntType(char *key, char *type) { 1786 if(!strcmp(type, "string")) { 1787 return TYPE_STRING; 1788 } else if(!strcmp(type, "list")) { 1789 return TYPE_LIST; 1790 } else if(!strcmp(type, "set")) { 1791 return TYPE_SET; 1792 } else if(!strcmp(type, "hash")) { 1793 return TYPE_HASH; 1794 } else if(!strcmp(type, "zset")) { 1795 return TYPE_ZSET; 1796 } else if(!strcmp(type, "none")) { 1797 return TYPE_NONE; 1798 } else { 1799 fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key); 1800 exit(1); 1801 } 1802 } 1803 1804 static void getKeyTypes(redisReply *keys, int *types) { 1805 redisReply *reply; 1806 unsigned int i; 1807 1808 /* Pipeline TYPE commands */ 1809 for(i=0;i<keys->elements;i++) { 1810 redisAppendCommand(context, "TYPE %s", keys->element[i]->str); 1811 } 1812 1813 /* Retrieve types */ 1814 for(i=0;i<keys->elements;i++) { 1815 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) { 1816 fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n", 1817 keys->element[i]->str, context->err, context->errstr); 1818 exit(1); 1819 } else if(reply->type != REDIS_REPLY_STATUS) { 1820 fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n", 1821 reply->type, keys->element[i]->str); 1822 exit(1); 1823 } 1824 1825 types[i] = toIntType(keys->element[i]->str, reply->str); 1826 freeReplyObject(reply); 1827 } 1828 } 1829 1830 static void getKeySizes(redisReply *keys, int *types, 1831 unsigned long long *sizes) 1832 { 1833 redisReply *reply; 1834 char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"}; 1835 unsigned int i; 1836 1837 /* Pipeline size commands */ 1838 for(i=0;i<keys->elements;i++) { 1839 /* Skip keys that were deleted */ 1840 if(types[i]==TYPE_NONE) 1841 continue; 1842 1843 redisAppendCommand(context, "%s %s", sizecmds[types[i]], 1844 keys->element[i]->str); 1845 } 1846 1847 /* Retreive sizes */ 1848 for(i=0;i<keys->elements;i++) { 1849 /* Skip keys that dissapeared between SCAN and TYPE */ 1850 if(types[i] == TYPE_NONE) { 1851 sizes[i] = 0; 1852 continue; 1853 } 1854 1855 /* Retreive size */ 1856 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) { 1857 fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n", 1858 keys->element[i]->str, context->err, context->errstr); 1859 exit(1); 1860 } else if(reply->type != REDIS_REPLY_INTEGER) { 1861 /* Theoretically the key could have been removed and 1862 * added as a different type between TYPE and SIZE */ 1863 fprintf(stderr, 1864 "Warning: %s on '%s' failed (may have changed type)\n", 1865 sizecmds[types[i]], keys->element[i]->str); 1866 sizes[i] = 0; 1867 } else { 1868 sizes[i] = reply->integer; 1869 } 1870 1871 freeReplyObject(reply); 1872 } 1873 } 1874 1875 static void findBigKeys(void) { 1876 unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0}; 1877 unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0; 1878 sds maxkeys[5] = {0}; 1879 char *typename[] = {"string","list","set","hash","zset"}; 1880 char *typeunit[] = {"bytes","items","members","fields","members"}; 1881 redisReply *reply, *keys; 1882 unsigned int arrsize=0, i; 1883 int type, *types=NULL; 1884 double pct; 1885 1886 /* Total keys pre scanning */ 1887 total_keys = getDbSize(); 1888 1889 /* Status message */ 1890 printf("\n# Scanning the entire keyspace to find biggest keys as well as\n"); 1891 printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n"); 1892 printf("# per 100 SCAN commands (not usually needed).\n\n"); 1893 1894 /* New up sds strings to keep track of overall biggest per type */ 1895 for(i=0;i<TYPE_NONE; i++) { 1896 maxkeys[i] = sdsempty(); 1897 if(!maxkeys[i]) { 1898 fprintf(stderr, "Failed to allocate memory for largest key names!\n"); 1899 exit(1); 1900 } 1901 } 1902 1903 /* SCAN loop */ 1904 do { 1905 /* Calculate approximate percentage completion */ 1906 pct = 100 * (double)sampled/total_keys; 1907 1908 /* Grab some keys and point to the keys array */ 1909 reply = sendScan(&it); 1910 keys = reply->element[1]; 1911 1912 /* Reallocate our type and size array if we need to */ 1913 if(keys->elements > arrsize) { 1914 types = zrealloc(types, sizeof(int)*keys->elements); 1915 sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements); 1916 1917 if(!types || !sizes) { 1918 fprintf(stderr, "Failed to allocate storage for keys!\n"); 1919 exit(1); 1920 } 1921 1922 arrsize = keys->elements; 1923 } 1924 1925 /* Retreive types and then sizes */ 1926 getKeyTypes(keys, types); 1927 getKeySizes(keys, types, sizes); 1928 1929 /* Now update our stats */ 1930 for(i=0;i<keys->elements;i++) { 1931 if((type = types[i]) == TYPE_NONE) 1932 continue; 1933 1934 totalsize[type] += sizes[i]; 1935 counts[type]++; 1936 totlen += keys->element[i]->len; 1937 sampled++; 1938 1939 if(biggest[type]<sizes[i]) { 1940 printf( 1941 "[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n", 1942 pct, typename[type], keys->element[i]->str, sizes[i], 1943 typeunit[type]); 1944 1945 /* Keep track of biggest key name for this type */ 1946 maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str); 1947 if(!maxkeys[type]) { 1948 fprintf(stderr, "Failed to allocate memory for key!\n"); 1949 exit(1); 1950 } 1951 1952 /* Keep track of the biggest size for this type */ 1953 biggest[type] = sizes[i]; 1954 } 1955 1956 /* Update overall progress */ 1957 if(sampled % 1000000 == 0) { 1958 printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled); 1959 } 1960 } 1961 1962 /* Sleep if we've been directed to do so */ 1963 if(sampled && (sampled %100) == 0 && config.interval) { 1964 usleep(config.interval); 1965 } 1966 1967 freeReplyObject(reply); 1968 } while(it != 0); 1969 1970 if(types) zfree(types); 1971 if(sizes) zfree(sizes); 1972 1973 /* We're done */ 1974 printf("\n-------- summary -------\n\n"); 1975 1976 printf("Sampled %llu keys in the keyspace!\n", sampled); 1977 printf("Total key length in bytes is %llu (avg len %.2f)\n\n", 1978 totlen, totlen ? (double)totlen/sampled : 0); 1979 1980 /* Output the biggest keys we found, for types we did find */ 1981 for(i=0;i<TYPE_NONE;i++) { 1982 if(sdslen(maxkeys[i])>0) { 1983 printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i], 1984 biggest[i], typeunit[i]); 1985 } 1986 } 1987 1988 printf("\n"); 1989 1990 for(i=0;i<TYPE_NONE;i++) { 1991 printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n", 1992 counts[i], typename[i], totalsize[i], typeunit[i], 1993 sampled ? 100 * (double)counts[i]/sampled : 0, 1994 counts[i] ? (double)totalsize[i]/counts[i] : 0); 1995 } 1996 1997 /* Free sds strings containing max keys */ 1998 for(i=0;i<TYPE_NONE;i++) { 1999 sdsfree(maxkeys[i]); 2000 } 2001 2002 /* Success! */ 2003 exit(0); 2004 } 2005 2006 /*------------------------------------------------------------------------------ 2007 * Stats mode 2008 *--------------------------------------------------------------------------- */ 2009 2010 /* Return the specified INFO field from the INFO command output "info". 2011 * A new buffer is allocated for the result, that needs to be free'd. 2012 * If the field is not found NULL is returned. */ 2013 static char *getInfoField(char *info, char *field) { 2014 char *p = strstr(info,field); 2015 char *n1, *n2; 2016 char *result; 2017 2018 if (!p) return NULL; 2019 p += strlen(field)+1; 2020 n1 = strchr(p,'\r'); 2021 n2 = strchr(p,','); 2022 if (n2 && n2 < n1) n1 = n2; 2023 result = malloc(sizeof(char)*(n1-p)+1); 2024 memcpy(result,p,(n1-p)); 2025 result[n1-p] = '\0'; 2026 return result; 2027 } 2028 2029 /* Like the above function but automatically convert the result into 2030 * a long. On error (missing field) LONG_MIN is returned. */ 2031 static long getLongInfoField(char *info, char *field) { 2032 char *value = getInfoField(info,field); 2033 long l; 2034 2035 if (!value) return LONG_MIN; 2036 l = strtol(value,NULL,10); 2037 free(value); 2038 return l; 2039 } 2040 2041 /* Convert number of bytes into a human readable string of the form: 2042 * 100B, 2G, 100M, 4K, and so forth. */ 2043 void bytesToHuman(char *s, long long n) { 2044 double d; 2045 2046 if (n < 0) { 2047 *s = '-'; 2048 s++; 2049 n = -n; 2050 } 2051 if (n < 1024) { 2052 /* Bytes */ 2053 sprintf(s,"%lldB",n); 2054 return; 2055 } else if (n < (1024*1024)) { 2056 d = (double)n/(1024); 2057 sprintf(s,"%.2fK",d); 2058 } else if (n < (1024LL*1024*1024)) { 2059 d = (double)n/(1024*1024); 2060 sprintf(s,"%.2fM",d); 2061 } else if (n < (1024LL*1024*1024*1024)) { 2062 d = (double)n/(1024LL*1024*1024); 2063 sprintf(s,"%.2fG",d); 2064 } 2065 } 2066 2067 static void statMode(void) { 2068 redisReply *reply; 2069 long aux, requests = 0; 2070 int i = 0; 2071 2072 while(1) { 2073 char buf[64]; 2074 int j; 2075 2076 reply = reconnectingRedisCommand(context,"INFO"); 2077 if (reply->type == REDIS_REPLY_ERROR) { 2078 printf("ERROR: %s\n", reply->str); 2079 exit(1); 2080 } 2081 2082 if ((i++ % 20) == 0) { 2083 printf( 2084 "------- data ------ --------------------- load -------------------- - child -\n" 2085 "keys mem clients blocked requests connections \n"); 2086 } 2087 2088 /* Keys */ 2089 aux = 0; 2090 for (j = 0; j < 20; j++) { 2091 long k; 2092 2093 sprintf(buf,"db%d:keys",j); 2094 k = getLongInfoField(reply->str,buf); 2095 if (k == LONG_MIN) continue; 2096 aux += k; 2097 } 2098 sprintf(buf,"%ld",aux); 2099 printf("%-11s",buf); 2100 2101 /* Used memory */ 2102 aux = getLongInfoField(reply->str,"used_memory"); 2103 bytesToHuman(buf,aux); 2104 printf("%-8s",buf); 2105 2106 /* Clients */ 2107 aux = getLongInfoField(reply->str,"connected_clients"); 2108 sprintf(buf,"%ld",aux); 2109 printf(" %-8s",buf); 2110 2111 /* Blocked (BLPOPPING) Clients */ 2112 aux = getLongInfoField(reply->str,"blocked_clients"); 2113 sprintf(buf,"%ld",aux); 2114 printf("%-8s",buf); 2115 2116 /* Requets */ 2117 aux = getLongInfoField(reply->str,"total_commands_processed"); 2118 sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests); 2119 printf("%-19s",buf); 2120 requests = aux; 2121 2122 /* Connections */ 2123 aux = getLongInfoField(reply->str,"total_connections_received"); 2124 sprintf(buf,"%ld",aux); 2125 printf(" %-12s",buf); 2126 2127 /* Children */ 2128 aux = getLongInfoField(reply->str,"bgsave_in_progress"); 2129 aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1; 2130 aux |= getLongInfoField(reply->str,"loading") << 2; 2131 switch(aux) { 2132 case 0: break; 2133 case 1: 2134 printf("SAVE"); 2135 break; 2136 case 2: 2137 printf("AOF"); 2138 break; 2139 case 3: 2140 printf("SAVE+AOF"); 2141 break; 2142 case 4: 2143 printf("LOAD"); 2144 break; 2145 } 2146 2147 printf("\n"); 2148 freeReplyObject(reply); 2149 usleep(config.interval); 2150 } 2151 } 2152 2153 /*------------------------------------------------------------------------------ 2154 * Scan mode 2155 *--------------------------------------------------------------------------- */ 2156 2157 static void scanMode(void) { 2158 redisReply *reply; 2159 unsigned long long cur = 0; 2160 2161 do { 2162 if (config.pattern) 2163 reply = redisCommand(context,"SCAN %llu MATCH %s", 2164 cur,config.pattern); 2165 else 2166 reply = redisCommand(context,"SCAN %llu",cur); 2167 if (reply == NULL) { 2168 printf("I/O error\n"); 2169 exit(1); 2170 } else if (reply->type == REDIS_REPLY_ERROR) { 2171 printf("ERROR: %s\n", reply->str); 2172 exit(1); 2173 } else { 2174 unsigned int j; 2175 2176 cur = strtoull(reply->element[0]->str,NULL,10); 2177 for (j = 0; j < reply->element[1]->elements; j++) 2178 printf("%s\n", reply->element[1]->element[j]->str); 2179 } 2180 freeReplyObject(reply); 2181 } while(cur != 0); 2182 2183 exit(0); 2184 } 2185 2186 /*------------------------------------------------------------------------------ 2187 * LRU test mode 2188 *--------------------------------------------------------------------------- */ 2189 2190 /* Return an integer from min to max (both inclusive) using a power-law 2191 * distribution, depending on the value of alpha: the greater the alpha 2192 * the more bias towards lower values. 2193 * 2194 * With alpha = 6.2 the output follows the 80-20 rule where 20% of 2195 * the returned numbers will account for 80% of the frequency. */ 2196 long long powerLawRand(long long min, long long max, double alpha) { 2197 double pl, r; 2198 2199 max += 1; 2200 r = ((double)rand()) / RAND_MAX; 2201 pl = pow( 2202 ((pow(max,alpha+1) - pow(min,alpha+1))*r + pow(min,alpha+1)), 2203 (1.0/(alpha+1))); 2204 return (max-1-(long long)pl)+min; 2205 } 2206 2207 /* Generates a key name among a set of lru_test_sample_size keys, using 2208 * an 80-20 distribution. */ 2209 void LRUTestGenKey(char *buf, size_t buflen) { 2210 snprintf(buf, buflen, "lru:%lld\n", 2211 powerLawRand(1, config.lru_test_sample_size, 6.2)); 2212 } 2213 2214 #define LRU_CYCLE_PERIOD 1000 /* 1000 milliseconds. */ 2215 #define LRU_CYCLE_PIPELINE_SIZE 250 2216 static void LRUTestMode(void) { 2217 redisReply *reply; 2218 char key[128]; 2219 long long start_cycle; 2220 int j; 2221 2222 srand(time(NULL)^getpid()); 2223 while(1) { 2224 /* Perform cycles of 1 second with 50% writes and 50% reads. 2225 * We use pipelining batching writes / reads N times per cycle in order 2226 * to fill the target instance easily. */ 2227 start_cycle = mstime(); 2228 long long hits = 0, misses = 0; 2229 while(mstime() - start_cycle < 1000) { 2230 /* Write cycle. */ 2231 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) { 2232 LRUTestGenKey(key,sizeof(key)); 2233 redisAppendCommand(context, "SET %s val",key); 2234 } 2235 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) 2236 redisGetReply(context, (void**)&reply); 2237 2238 /* Read cycle. */ 2239 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) { 2240 LRUTestGenKey(key,sizeof(key)); 2241 redisAppendCommand(context, "GET %s",key); 2242 } 2243 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) { 2244 if (redisGetReply(context, (void**)&reply) == REDIS_OK) { 2245 switch(reply->type) { 2246 case REDIS_REPLY_ERROR: 2247 printf("%s\n", reply->str); 2248 break; 2249 case REDIS_REPLY_NIL: 2250 misses++; 2251 break; 2252 default: 2253 hits++; 2254 break; 2255 } 2256 } 2257 } 2258 2259 if (context->err) { 2260 fprintf(stderr,"I/O error during LRU test\n"); 2261 exit(1); 2262 } 2263 } 2264 /* Print stats. */ 2265 printf( 2266 "%lld Gets/sec | Hits: %lld (%.2f%%) | Misses: %lld (%.2f%%)\n", 2267 hits+misses, 2268 hits, (double)hits/(hits+misses)*100, 2269 misses, (double)misses/(hits+misses)*100); 2270 } 2271 exit(0); 2272 } 2273 2274 /*------------------------------------------------------------------------------ 2275 * Intrisic latency mode. 2276 * 2277 * Measure max latency of a running process that does not result from 2278 * syscalls. Basically this software should provide an hint about how much 2279 * time the kernel leaves the process without a chance to run. 2280 *--------------------------------------------------------------------------- */ 2281 2282 /* This is just some computation the compiler can't optimize out. 2283 * Should run in less than 100-200 microseconds even using very 2284 * slow hardware. Runs in less than 10 microseconds in modern HW. */ 2285 unsigned long compute_something_fast(void) { 2286 unsigned char s[256], i, j, t; 2287 int count = 1000, k; 2288 unsigned long output = 0; 2289 2290 for (k = 0; k < 256; k++) s[k] = k; 2291 2292 i = 0; 2293 j = 0; 2294 while(count--) { 2295 i++; 2296 j = j + s[i]; 2297 t = s[i]; 2298 s[i] = s[j]; 2299 s[j] = t; 2300 output += s[(s[i]+s[j])&255]; 2301 } 2302 return output; 2303 } 2304 2305 static void intrinsicLatencyModeStop(int s) { 2306 UNUSED(s); 2307 force_cancel_loop = 1; 2308 } 2309 2310 static void intrinsicLatencyMode(void) { 2311 long long test_end, run_time, max_latency = 0, runs = 0; 2312 2313 run_time = config.intrinsic_latency_duration*1000000; 2314 test_end = ustime() + run_time; 2315 signal(SIGINT, intrinsicLatencyModeStop); 2316 2317 while(1) { 2318 long long start, end, latency; 2319 2320 start = ustime(); 2321 compute_something_fast(); 2322 end = ustime(); 2323 latency = end-start; 2324 runs++; 2325 if (latency <= 0) continue; 2326 2327 /* Reporting */ 2328 if (latency > max_latency) { 2329 max_latency = latency; 2330 printf("Max latency so far: %lld microseconds.\n", max_latency); 2331 } 2332 2333 double avg_us = (double)run_time/runs; 2334 double avg_ns = avg_us * 10e3; 2335 if (force_cancel_loop || end > test_end) { 2336 printf("\n%lld total runs " 2337 "(avg latency: " 2338 "%.4f microseconds / %.2f nanoseconds per run).\n", 2339 runs, avg_us, avg_ns); 2340 printf("Worst run took %.0fx longer than the average latency.\n", 2341 max_latency / avg_us); 2342 exit(0); 2343 } 2344 } 2345 } 2346 2347 /*------------------------------------------------------------------------------ 2348 * Program main() 2349 *--------------------------------------------------------------------------- */ 2350 2351 int main(int argc, char **argv) { 2352 int firstarg; 2353 2354 config.hostip = sdsnew("127.0.0.1"); 2355 config.hostport = 6379; 2356 config.hostsocket = NULL; 2357 config.repeat = 1; 2358 config.interval = 0; 2359 config.dbnum = 0; 2360 config.interactive = 0; 2361 config.shutdown = 0; 2362 config.monitor_mode = 0; 2363 config.pubsub_mode = 0; 2364 config.latency_mode = 0; 2365 config.latency_dist_mode = 0; 2366 config.latency_history = 0; 2367 config.lru_test_mode = 0; 2368 config.lru_test_sample_size = 0; 2369 config.cluster_mode = 0; 2370 config.slave_mode = 0; 2371 config.getrdb_mode = 0; 2372 config.stat_mode = 0; 2373 config.scan_mode = 0; 2374 config.intrinsic_latency_mode = 0; 2375 config.pattern = NULL; 2376 config.rdb_filename = NULL; 2377 config.pipe_mode = 0; 2378 config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT; 2379 config.bigkeys = 0; 2380 config.stdinarg = 0; 2381 config.auth = NULL; 2382 config.eval = NULL; 2383 config.eval_ldb = 0; 2384 config.eval_ldb_end = 0; 2385 config.eval_ldb_sync = 0; 2386 config.last_cmd_type = -1; 2387 2388 spectrum_palette = spectrum_palette_color; 2389 spectrum_palette_size = spectrum_palette_color_size; 2390 2391 if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL)) 2392 config.output = OUTPUT_RAW; 2393 else 2394 config.output = OUTPUT_STANDARD; 2395 config.mb_delim = sdsnew("\n"); 2396 cliInitHelp(); 2397 2398 firstarg = parseOptions(argc,argv); 2399 argc -= firstarg; 2400 argv += firstarg; 2401 2402 /* Latency mode */ 2403 if (config.latency_mode) { 2404 if (cliConnect(0) == REDIS_ERR) exit(1); 2405 latencyMode(); 2406 } 2407 2408 /* Latency distribution mode */ 2409 if (config.latency_dist_mode) { 2410 if (cliConnect(0) == REDIS_ERR) exit(1); 2411 latencyDistMode(); 2412 } 2413 2414 /* Slave mode */ 2415 if (config.slave_mode) { 2416 if (cliConnect(0) == REDIS_ERR) exit(1); 2417 slaveMode(); 2418 } 2419 2420 /* Get RDB mode. */ 2421 if (config.getrdb_mode) { 2422 if (cliConnect(0) == REDIS_ERR) exit(1); 2423 getRDB(); 2424 } 2425 2426 /* Pipe mode */ 2427 if (config.pipe_mode) { 2428 if (cliConnect(0) == REDIS_ERR) exit(1); 2429 pipeMode(); 2430 } 2431 2432 /* Find big keys */ 2433 if (config.bigkeys) { 2434 if (cliConnect(0) == REDIS_ERR) exit(1); 2435 findBigKeys(); 2436 } 2437 2438 /* Stat mode */ 2439 if (config.stat_mode) { 2440 if (cliConnect(0) == REDIS_ERR) exit(1); 2441 if (config.interval == 0) config.interval = 1000000; 2442 statMode(); 2443 } 2444 2445 /* Scan mode */ 2446 if (config.scan_mode) { 2447 if (cliConnect(0) == REDIS_ERR) exit(1); 2448 scanMode(); 2449 } 2450 2451 /* LRU test mode */ 2452 if (config.lru_test_mode) { 2453 if (cliConnect(0) == REDIS_ERR) exit(1); 2454 LRUTestMode(); 2455 } 2456 2457 /* Intrinsic latency mode */ 2458 if (config.intrinsic_latency_mode) intrinsicLatencyMode(); 2459 2460 /* Start interactive mode when no command is provided */ 2461 if (argc == 0 && !config.eval) { 2462 /* Ignore SIGPIPE in interactive mode to force a reconnect */ 2463 signal(SIGPIPE, SIG_IGN); 2464 2465 /* Note that in repl mode we don't abort on connection error. 2466 * A new attempt will be performed for every command send. */ 2467 cliConnect(0); 2468 repl(); 2469 } 2470 2471 /* Otherwise, we have some arguments to execute */ 2472 if (cliConnect(0) != REDIS_OK) exit(1); 2473 if (config.eval) { 2474 return evalMode(argc,argv); 2475 } else { 2476 return noninteractive(argc,convertToSds(argc,argv)); 2477 } 2478 } 2479