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