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