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 48 #include "hiredis.h" 49 #include "sds.h" 50 #include "zmalloc.h" 51 #include "linenoise.h" 52 #include "help.h" 53 #include "anet.h" 54 #include "ae.h" 55 56 #define REDIS_NOTUSED(V) ((void) V) 57 58 #define OUTPUT_STANDARD 0 59 #define OUTPUT_RAW 1 60 #define OUTPUT_CSV 2 61 #define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */ 62 #define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */ 63 64 static redisContext *context; 65 static struct config { 66 char *hostip; 67 int hostport; 68 char *hostsocket; 69 long repeat; 70 long interval; 71 int dbnum; 72 int interactive; 73 int shutdown; 74 int monitor_mode; 75 int pubsub_mode; 76 int latency_mode; 77 int latency_history; 78 int cluster_mode; 79 int cluster_reissue_command; 80 int slave_mode; 81 int pipe_mode; 82 int pipe_timeout; 83 int getrdb_mode; 84 int stat_mode; 85 int scan_mode; 86 int intrinsic_latency_mode; 87 int intrinsic_latency_duration; 88 char *pattern; 89 char *rdb_filename; 90 int bigkeys; 91 int stdinarg; /* get last arg from stdin. (-x option) */ 92 char *auth; 93 int output; /* output mode, see OUTPUT_* defines */ 94 sds mb_delim; 95 char prompt[128]; 96 char *eval; 97 int last_cmd_type; 98 } config; 99 100 static volatile sig_atomic_t force_cancel_loop = 0; 101 static void usage(void); 102 static void slaveMode(void); 103 char *redisGitSHA1(void); 104 char *redisGitDirty(void); 105 106 /*------------------------------------------------------------------------------ 107 * Utility functions 108 *--------------------------------------------------------------------------- */ 109 110 static long long ustime(void) { 111 struct timeval tv; 112 long long ust; 113 114 gettimeofday(&tv, NULL); 115 ust = ((long long)tv.tv_sec)*1000000; 116 ust += tv.tv_usec; 117 return ust; 118 } 119 120 static long long mstime(void) { 121 return ustime()/1000; 122 } 123 124 static void cliRefreshPrompt(void) { 125 int len; 126 127 if (config.hostsocket != NULL) 128 len = snprintf(config.prompt,sizeof(config.prompt),"redis %s", 129 config.hostsocket); 130 else 131 len = snprintf(config.prompt,sizeof(config.prompt), 132 strchr(config.hostip,':') ? "[%s]:%d" : "%s:%d", 133 config.hostip, config.hostport); 134 /* Add [dbnum] if needed */ 135 if (config.dbnum != 0 && config.last_cmd_type != REDIS_REPLY_ERROR) 136 len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]", 137 config.dbnum); 138 snprintf(config.prompt+len,sizeof(config.prompt)-len,"> "); 139 } 140 141 /*------------------------------------------------------------------------------ 142 * Help functions 143 *--------------------------------------------------------------------------- */ 144 145 #define CLI_HELP_COMMAND 1 146 #define CLI_HELP_GROUP 2 147 148 typedef struct { 149 int type; 150 int argc; 151 sds *argv; 152 sds full; 153 154 /* Only used for help on commands */ 155 struct commandHelp *org; 156 } helpEntry; 157 158 static helpEntry *helpEntries; 159 static int helpEntriesLen; 160 161 static sds cliVersion(void) { 162 sds version; 163 version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); 164 165 /* Add git commit and working tree status when available */ 166 if (strtoll(redisGitSHA1(),NULL,16)) { 167 version = sdscatprintf(version, " (git:%s", redisGitSHA1()); 168 if (strtoll(redisGitDirty(),NULL,10)) 169 version = sdscatprintf(version, "-dirty"); 170 version = sdscat(version, ")"); 171 } 172 return version; 173 } 174 175 static void cliInitHelp(void) { 176 int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp); 177 int groupslen = sizeof(commandGroups)/sizeof(char*); 178 int i, len, pos = 0; 179 helpEntry tmp; 180 181 helpEntriesLen = len = commandslen+groupslen; 182 helpEntries = malloc(sizeof(helpEntry)*len); 183 184 for (i = 0; i < groupslen; i++) { 185 tmp.argc = 1; 186 tmp.argv = malloc(sizeof(sds)); 187 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]); 188 tmp.full = tmp.argv[0]; 189 tmp.type = CLI_HELP_GROUP; 190 tmp.org = NULL; 191 helpEntries[pos++] = tmp; 192 } 193 194 for (i = 0; i < commandslen; i++) { 195 tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc); 196 tmp.full = sdsnew(commandHelp[i].name); 197 tmp.type = CLI_HELP_COMMAND; 198 tmp.org = &commandHelp[i]; 199 helpEntries[pos++] = tmp; 200 } 201 } 202 203 /* Output command help to stdout. */ 204 static void cliOutputCommandHelp(struct commandHelp *help, int group) { 205 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params); 206 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary); 207 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since); 208 if (group) { 209 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]); 210 } 211 } 212 213 /* Print generic help. */ 214 static void cliOutputGenericHelp(void) { 215 sds version = cliVersion(); 216 printf( 217 "redis-cli %s\r\n" 218 "Type: \"help @<group>\" to get a list of commands in <group>\r\n" 219 " \"help <command>\" for help on <command>\r\n" 220 " \"help <tab>\" to get a list of possible help topics\r\n" 221 " \"quit\" to exit\r\n", 222 version 223 ); 224 sdsfree(version); 225 } 226 227 /* Output all command help, filtering by group or command name. */ 228 static void cliOutputHelp(int argc, char **argv) { 229 int i, j, len; 230 int group = -1; 231 helpEntry *entry; 232 struct commandHelp *help; 233 234 if (argc == 0) { 235 cliOutputGenericHelp(); 236 return; 237 } else if (argc > 0 && argv[0][0] == '@') { 238 len = sizeof(commandGroups)/sizeof(char*); 239 for (i = 0; i < len; i++) { 240 if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) { 241 group = i; 242 break; 243 } 244 } 245 } 246 247 assert(argc > 0); 248 for (i = 0; i < helpEntriesLen; i++) { 249 entry = &helpEntries[i]; 250 if (entry->type != CLI_HELP_COMMAND) continue; 251 252 help = entry->org; 253 if (group == -1) { 254 /* Compare all arguments */ 255 if (argc == entry->argc) { 256 for (j = 0; j < argc; j++) { 257 if (strcasecmp(argv[j],entry->argv[j]) != 0) break; 258 } 259 if (j == argc) { 260 cliOutputCommandHelp(help,1); 261 } 262 } 263 } else { 264 if (group == help->group) { 265 cliOutputCommandHelp(help,0); 266 } 267 } 268 } 269 printf("\r\n"); 270 } 271 272 static void completionCallback(const char *buf, linenoiseCompletions *lc) { 273 size_t startpos = 0; 274 int mask; 275 int i; 276 size_t matchlen; 277 sds tmp; 278 279 if (strncasecmp(buf,"help ",5) == 0) { 280 startpos = 5; 281 while (isspace(buf[startpos])) startpos++; 282 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP; 283 } else { 284 mask = CLI_HELP_COMMAND; 285 } 286 287 for (i = 0; i < helpEntriesLen; i++) { 288 if (!(helpEntries[i].type & mask)) continue; 289 290 matchlen = strlen(buf+startpos); 291 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) { 292 tmp = sdsnewlen(buf,startpos); 293 tmp = sdscat(tmp,helpEntries[i].full); 294 linenoiseAddCompletion(lc,tmp); 295 sdsfree(tmp); 296 } 297 } 298 } 299 300 /*------------------------------------------------------------------------------ 301 * Networking / parsing 302 *--------------------------------------------------------------------------- */ 303 304 /* Send AUTH command to the server */ 305 static int cliAuth() { 306 redisReply *reply; 307 if (config.auth == NULL) return REDIS_OK; 308 309 reply = redisCommand(context,"AUTH %s",config.auth); 310 if (reply != NULL) { 311 freeReplyObject(reply); 312 return REDIS_OK; 313 } 314 return REDIS_ERR; 315 } 316 317 /* Send SELECT dbnum to the server */ 318 static int cliSelect() { 319 redisReply *reply; 320 if (config.dbnum == 0) return REDIS_OK; 321 322 reply = redisCommand(context,"SELECT %d",config.dbnum); 323 if (reply != NULL) { 324 int result = REDIS_OK; 325 if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR; 326 freeReplyObject(reply); 327 return result; 328 } 329 return REDIS_ERR; 330 } 331 332 /* Connect to the server. If force is not zero the connection is performed 333 * even if there is already a connected socket. */ 334 static int cliConnect(int force) { 335 if (context == NULL || force) { 336 if (context != NULL) 337 redisFree(context); 338 339 if (config.hostsocket == NULL) { 340 context = redisConnect(config.hostip,config.hostport); 341 } else { 342 context = redisConnectUnix(config.hostsocket); 343 } 344 345 if (context->err) { 346 fprintf(stderr,"Could not connect to Redis at "); 347 if (config.hostsocket == NULL) 348 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr); 349 else 350 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr); 351 redisFree(context); 352 context = NULL; 353 return REDIS_ERR; 354 } 355 356 /* Set aggressive KEEP_ALIVE socket option in the Redis context socket 357 * in order to prevent timeouts caused by the execution of long 358 * commands. At the same time this improves the detection of real 359 * errors. */ 360 anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL); 361 362 /* Do AUTH and select the right DB. */ 363 if (cliAuth() != REDIS_OK) 364 return REDIS_ERR; 365 if (cliSelect() != REDIS_OK) 366 return REDIS_ERR; 367 } 368 return REDIS_OK; 369 } 370 371 static void cliPrintContextError(void) { 372 if (context == NULL) return; 373 fprintf(stderr,"Error: %s\n",context->errstr); 374 } 375 376 static sds cliFormatReplyTTY(redisReply *r, char *prefix) { 377 sds out = sdsempty(); 378 switch (r->type) { 379 case REDIS_REPLY_ERROR: 380 out = sdscatprintf(out,"(error) %s\n", r->str); 381 break; 382 case REDIS_REPLY_STATUS: 383 out = sdscat(out,r->str); 384 out = sdscat(out,"\n"); 385 break; 386 case REDIS_REPLY_INTEGER: 387 out = sdscatprintf(out,"(integer) %lld\n",r->integer); 388 break; 389 case REDIS_REPLY_STRING: 390 /* If you are producing output for the standard output we want 391 * a more interesting output with quoted characters and so forth */ 392 out = sdscatrepr(out,r->str,r->len); 393 out = sdscat(out,"\n"); 394 break; 395 case REDIS_REPLY_NIL: 396 out = sdscat(out,"(nil)\n"); 397 break; 398 case REDIS_REPLY_ARRAY: 399 if (r->elements == 0) { 400 out = sdscat(out,"(empty list or set)\n"); 401 } else { 402 unsigned int i, idxlen = 0; 403 char _prefixlen[16]; 404 char _prefixfmt[16]; 405 sds _prefix; 406 sds tmp; 407 408 /* Calculate chars needed to represent the largest index */ 409 i = r->elements; 410 do { 411 idxlen++; 412 i /= 10; 413 } while(i); 414 415 /* Prefix for nested multi bulks should grow with idxlen+2 spaces */ 416 memset(_prefixlen,' ',idxlen+2); 417 _prefixlen[idxlen+2] = '\0'; 418 _prefix = sdscat(sdsnew(prefix),_prefixlen); 419 420 /* Setup prefix format for every entry */ 421 snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen); 422 423 for (i = 0; i < r->elements; i++) { 424 /* Don't use the prefix for the first element, as the parent 425 * caller already prepended the index number. */ 426 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1); 427 428 /* Format the multi bulk entry */ 429 tmp = cliFormatReplyTTY(r->element[i],_prefix); 430 out = sdscatlen(out,tmp,sdslen(tmp)); 431 sdsfree(tmp); 432 } 433 sdsfree(_prefix); 434 } 435 break; 436 default: 437 fprintf(stderr,"Unknown reply type: %d\n", r->type); 438 exit(1); 439 } 440 return out; 441 } 442 443 static sds cliFormatReplyRaw(redisReply *r) { 444 sds out = sdsempty(), tmp; 445 size_t i; 446 447 switch (r->type) { 448 case REDIS_REPLY_NIL: 449 /* Nothing... */ 450 break; 451 case REDIS_REPLY_ERROR: 452 out = sdscatlen(out,r->str,r->len); 453 out = sdscatlen(out,"\n",1); 454 break; 455 case REDIS_REPLY_STATUS: 456 case REDIS_REPLY_STRING: 457 out = sdscatlen(out,r->str,r->len); 458 break; 459 case REDIS_REPLY_INTEGER: 460 out = sdscatprintf(out,"%lld",r->integer); 461 break; 462 case REDIS_REPLY_ARRAY: 463 for (i = 0; i < r->elements; i++) { 464 if (i > 0) out = sdscat(out,config.mb_delim); 465 tmp = cliFormatReplyRaw(r->element[i]); 466 out = sdscatlen(out,tmp,sdslen(tmp)); 467 sdsfree(tmp); 468 } 469 break; 470 default: 471 fprintf(stderr,"Unknown reply type: %d\n", r->type); 472 exit(1); 473 } 474 return out; 475 } 476 477 static sds cliFormatReplyCSV(redisReply *r) { 478 unsigned int i; 479 480 sds out = sdsempty(); 481 switch (r->type) { 482 case REDIS_REPLY_ERROR: 483 out = sdscat(out,"ERROR,"); 484 out = sdscatrepr(out,r->str,strlen(r->str)); 485 break; 486 case REDIS_REPLY_STATUS: 487 out = sdscatrepr(out,r->str,r->len); 488 break; 489 case REDIS_REPLY_INTEGER: 490 out = sdscatprintf(out,"%lld",r->integer); 491 break; 492 case REDIS_REPLY_STRING: 493 out = sdscatrepr(out,r->str,r->len); 494 break; 495 case REDIS_REPLY_NIL: 496 out = sdscat(out,"NIL\n"); 497 break; 498 case REDIS_REPLY_ARRAY: 499 for (i = 0; i < r->elements; i++) { 500 sds tmp = cliFormatReplyCSV(r->element[i]); 501 out = sdscatlen(out,tmp,sdslen(tmp)); 502 if (i != r->elements-1) out = sdscat(out,","); 503 sdsfree(tmp); 504 } 505 break; 506 default: 507 fprintf(stderr,"Unknown reply type: %d\n", r->type); 508 exit(1); 509 } 510 return out; 511 } 512 513 static int cliReadReply(int output_raw_strings) { 514 void *_reply; 515 redisReply *reply; 516 sds out = NULL; 517 int output = 1; 518 519 if (redisGetReply(context,&_reply) != REDIS_OK) { 520 if (config.shutdown) { 521 redisFree(context); 522 context = NULL; 523 return REDIS_OK; 524 } 525 if (config.interactive) { 526 /* Filter cases where we should reconnect */ 527 if (context->err == REDIS_ERR_IO && 528 (errno == ECONNRESET || errno == EPIPE)) 529 return REDIS_ERR; 530 if (context->err == REDIS_ERR_EOF) 531 return REDIS_ERR; 532 } 533 cliPrintContextError(); 534 exit(1); 535 return REDIS_ERR; /* avoid compiler warning */ 536 } 537 538 reply = (redisReply*)_reply; 539 540 config.last_cmd_type = reply->type; 541 542 /* Check if we need to connect to a different node and reissue the 543 * request. */ 544 if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR && 545 (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK"))) 546 { 547 char *p = reply->str, *s; 548 int slot; 549 550 output = 0; 551 /* Comments show the position of the pointer as: 552 * 553 * [S] for pointer 's' 554 * [P] for pointer 'p' 555 */ 556 s = strchr(p,' '); /* MOVED[S]3999 127.0.0.1:6381 */ 557 p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */ 558 *p = '\0'; 559 slot = atoi(s+1); 560 s = strchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */ 561 *s = '\0'; 562 sdsfree(config.hostip); 563 config.hostip = sdsnew(p+1); 564 config.hostport = atoi(s+1); 565 if (config.interactive) 566 printf("-> Redirected to slot [%d] located at %s:%d\n", 567 slot, config.hostip, config.hostport); 568 config.cluster_reissue_command = 1; 569 cliRefreshPrompt(); 570 } 571 572 if (output) { 573 if (output_raw_strings) { 574 out = cliFormatReplyRaw(reply); 575 } else { 576 if (config.output == OUTPUT_RAW) { 577 out = cliFormatReplyRaw(reply); 578 out = sdscat(out,"\n"); 579 } else if (config.output == OUTPUT_STANDARD) { 580 out = cliFormatReplyTTY(reply,""); 581 } else if (config.output == OUTPUT_CSV) { 582 out = cliFormatReplyCSV(reply); 583 out = sdscat(out,"\n"); 584 } 585 } 586 fwrite(out,sdslen(out),1,stdout); 587 sdsfree(out); 588 } 589 freeReplyObject(reply); 590 return REDIS_OK; 591 } 592 593 static int cliSendCommand(int argc, char **argv, int repeat) { 594 char *command = argv[0]; 595 size_t *argvlen; 596 int j, output_raw; 597 598 if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { 599 cliOutputHelp(--argc, ++argv); 600 return REDIS_OK; 601 } 602 603 if (context == NULL) return REDIS_ERR; 604 605 output_raw = 0; 606 if (!strcasecmp(command,"info") || 607 (argc == 2 && !strcasecmp(command,"cluster") && 608 (!strcasecmp(argv[1],"nodes") || 609 !strcasecmp(argv[1],"info"))) || 610 (argc == 2 && !strcasecmp(command,"client") && 611 !strcasecmp(argv[1],"list")) || 612 (argc == 3 && !strcasecmp(command,"latency") && 613 !strcasecmp(argv[1],"graph")) || 614 (argc == 2 && !strcasecmp(command,"latency") && 615 !strcasecmp(argv[1],"doctor"))) 616 { 617 output_raw = 1; 618 } 619 620 if (!strcasecmp(command,"shutdown")) config.shutdown = 1; 621 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; 622 if (!strcasecmp(command,"subscribe") || 623 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; 624 if (!strcasecmp(command,"sync") || 625 !strcasecmp(command,"psync")) config.slave_mode = 1; 626 627 /* Setup argument length */ 628 argvlen = malloc(argc*sizeof(size_t)); 629 for (j = 0; j < argc; j++) 630 argvlen[j] = sdslen(argv[j]); 631 632 while(repeat--) { 633 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); 634 while (config.monitor_mode) { 635 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 636 fflush(stdout); 637 } 638 639 if (config.pubsub_mode) { 640 if (config.output != OUTPUT_RAW) 641 printf("Reading messages... (press Ctrl-C to quit)\n"); 642 while (1) { 643 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 644 } 645 } 646 647 if (config.slave_mode) { 648 printf("Entering slave output mode... (press Ctrl-C to quit)\n"); 649 slaveMode(); 650 config.slave_mode = 0; 651 free(argvlen); 652 return REDIS_ERR; /* Error = slaveMode lost connection to master */ 653 } 654 655 if (cliReadReply(output_raw) != REDIS_OK) { 656 free(argvlen); 657 return REDIS_ERR; 658 } else { 659 /* Store database number when SELECT was successfully executed. */ 660 if (!strcasecmp(command,"select") && argc == 2) { 661 config.dbnum = atoi(argv[1]); 662 cliRefreshPrompt(); 663 } else if (!strcasecmp(command,"auth") && argc == 2) { 664 cliSelect(); 665 } 666 } 667 if (config.interval) usleep(config.interval); 668 fflush(stdout); /* Make it grep friendly */ 669 } 670 671 free(argvlen); 672 return REDIS_OK; 673 } 674 675 /* Send the INFO command, reconnecting the link if needed. */ 676 static redisReply *reconnectingInfo(void) { 677 redisContext *c = context; 678 redisReply *reply = NULL; 679 int tries = 0; 680 681 assert(!c->err); 682 while(reply == NULL) { 683 while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) { 684 printf("Reconnecting (%d)...\r", ++tries); 685 fflush(stdout); 686 687 redisFree(c); 688 c = redisConnect(config.hostip,config.hostport); 689 usleep(1000000); 690 } 691 692 reply = redisCommand(c,"INFO"); 693 if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) { 694 fprintf(stderr, "Error: %s\n", c->errstr); 695 exit(1); 696 } else if (tries > 0) { 697 printf("\n"); 698 } 699 } 700 701 context = c; 702 return reply; 703 } 704 705 /*------------------------------------------------------------------------------ 706 * User interface 707 *--------------------------------------------------------------------------- */ 708 709 static int parseOptions(int argc, char **argv) { 710 int i; 711 712 for (i = 1; i < argc; i++) { 713 int lastarg = i==argc-1; 714 715 if (!strcmp(argv[i],"-h") && !lastarg) { 716 sdsfree(config.hostip); 717 config.hostip = sdsnew(argv[++i]); 718 } else if (!strcmp(argv[i],"-h") && lastarg) { 719 usage(); 720 } else if (!strcmp(argv[i],"--help")) { 721 usage(); 722 } else if (!strcmp(argv[i],"-x")) { 723 config.stdinarg = 1; 724 } else if (!strcmp(argv[i],"-p") && !lastarg) { 725 config.hostport = atoi(argv[++i]); 726 } else if (!strcmp(argv[i],"-s") && !lastarg) { 727 config.hostsocket = argv[++i]; 728 } else if (!strcmp(argv[i],"-r") && !lastarg) { 729 config.repeat = strtoll(argv[++i],NULL,10); 730 } else if (!strcmp(argv[i],"-i") && !lastarg) { 731 double seconds = atof(argv[++i]); 732 config.interval = seconds*1000000; 733 } else if (!strcmp(argv[i],"-n") && !lastarg) { 734 config.dbnum = atoi(argv[++i]); 735 } else if (!strcmp(argv[i],"-a") && !lastarg) { 736 config.auth = argv[++i]; 737 } else if (!strcmp(argv[i],"--raw")) { 738 config.output = OUTPUT_RAW; 739 } else if (!strcmp(argv[i],"--no-raw")) { 740 config.output = OUTPUT_STANDARD; 741 } else if (!strcmp(argv[i],"--csv")) { 742 config.output = OUTPUT_CSV; 743 } else if (!strcmp(argv[i],"--latency")) { 744 config.latency_mode = 1; 745 } else if (!strcmp(argv[i],"--latency-history")) { 746 config.latency_mode = 1; 747 config.latency_history = 1; 748 } else if (!strcmp(argv[i],"--slave")) { 749 config.slave_mode = 1; 750 } else if (!strcmp(argv[i],"--stat")) { 751 config.stat_mode = 1; 752 } else if (!strcmp(argv[i],"--scan")) { 753 config.scan_mode = 1; 754 } else if (!strcmp(argv[i],"--pattern") && !lastarg) { 755 config.pattern = argv[++i]; 756 } else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) { 757 config.intrinsic_latency_mode = 1; 758 config.intrinsic_latency_duration = atoi(argv[++i]); 759 } else if (!strcmp(argv[i],"--rdb") && !lastarg) { 760 config.getrdb_mode = 1; 761 config.rdb_filename = argv[++i]; 762 } else if (!strcmp(argv[i],"--pipe")) { 763 config.pipe_mode = 1; 764 } else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) { 765 config.pipe_timeout = atoi(argv[++i]); 766 } else if (!strcmp(argv[i],"--bigkeys")) { 767 config.bigkeys = 1; 768 } else if (!strcmp(argv[i],"--eval") && !lastarg) { 769 config.eval = argv[++i]; 770 } else if (!strcmp(argv[i],"-c")) { 771 config.cluster_mode = 1; 772 } else if (!strcmp(argv[i],"-d") && !lastarg) { 773 sdsfree(config.mb_delim); 774 config.mb_delim = sdsnew(argv[++i]); 775 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { 776 sds version = cliVersion(); 777 printf("redis-cli %s\n", version); 778 sdsfree(version); 779 exit(0); 780 } else { 781 if (argv[i][0] == '-') { 782 fprintf(stderr, 783 "Unrecognized option or bad number of args for: '%s'\n", 784 argv[i]); 785 exit(1); 786 } else { 787 /* Likely the command name, stop here. */ 788 break; 789 } 790 } 791 } 792 return i; 793 } 794 795 static sds readArgFromStdin(void) { 796 char buf[1024]; 797 sds arg = sdsempty(); 798 799 while(1) { 800 int nread = read(fileno(stdin),buf,1024); 801 802 if (nread == 0) break; 803 else if (nread == -1) { 804 perror("Reading from standard input"); 805 exit(1); 806 } 807 arg = sdscatlen(arg,buf,nread); 808 } 809 return arg; 810 } 811 812 static void usage(void) { 813 sds version = cliVersion(); 814 fprintf(stderr, 815 "redis-cli %s\n" 816 "\n" 817 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" 818 " -h <hostname> Server hostname (default: 127.0.0.1).\n" 819 " -p <port> Server port (default: 6379).\n" 820 " -s <socket> Server socket (overrides hostname and port).\n" 821 " -a <password> Password to use when connecting to the server.\n" 822 " -r <repeat> Execute specified command N times.\n" 823 " -i <interval> When -r is used, waits <interval> seconds per command.\n" 824 " It is possible to specify sub-second times like -i 0.1.\n" 825 " -n <db> Database number.\n" 826 " -x Read last argument from STDIN.\n" 827 " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n" 828 " -c Enable cluster mode (follow -ASK and -MOVED redirections).\n" 829 " --raw Use raw formatting for replies (default when STDOUT is\n" 830 " not a tty).\n" 831 " --no-raw Force formatted output even when STDOUT is not a tty.\n" 832 " --csv Output in CSV format.\n" 833 " --stat Print rolling stats about server: mem, clients, ...\n" 834 " --latency Enter a special mode continuously sampling latency.\n" 835 " --latency-history Like --latency but tracking latency changes over time.\n" 836 " Default time interval is 15 sec. Change it using -i.\n" 837 " --slave Simulate a slave showing commands received from the master.\n" 838 " --rdb <filename> Transfer an RDB dump from remote server to local file.\n" 839 " --pipe Transfer raw Redis protocol from stdin to server.\n" 840 " --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n" 841 " no reply is received within <n> seconds.\n" 842 " Default timeout: %d. Use 0 to wait forever.\n" 843 " --bigkeys Sample Redis keys looking for big keys.\n" 844 " --scan List all keys using the SCAN command.\n" 845 " --pattern <pat> Useful with --scan to specify a SCAN pattern.\n" 846 " --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n" 847 " The test will run for the specified amount of seconds.\n" 848 " --eval <file> Send an EVAL command using the Lua script at <file>.\n" 849 " --help Output this help and exit.\n" 850 " --version Output version and exit.\n" 851 "\n" 852 "Examples:\n" 853 " cat /etc/passwd | redis-cli -x set mypasswd\n" 854 " redis-cli get mypasswd\n" 855 " redis-cli -r 100 lpush mylist x\n" 856 " redis-cli -r 100 -i 1 info | grep used_memory_human:\n" 857 " redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n" 858 " redis-cli --scan --pattern '*:12345*'\n" 859 "\n" 860 " (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n" 861 "\n" 862 "When no command is given, redis-cli starts in interactive mode.\n" 863 "Type \"help\" in interactive mode for information on available commands.\n" 864 "\n", 865 version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT); 866 sdsfree(version); 867 exit(1); 868 } 869 870 /* Turn the plain C strings into Sds strings */ 871 static char **convertToSds(int count, char** args) { 872 int j; 873 char **sds = zmalloc(sizeof(char*)*count); 874 875 for(j = 0; j < count; j++) 876 sds[j] = sdsnew(args[j]); 877 878 return sds; 879 } 880 881 static void repl(void) { 882 sds historyfile = NULL; 883 int history = 0; 884 char *line; 885 int argc; 886 sds *argv; 887 888 config.interactive = 1; 889 linenoiseSetMultiLine(1); 890 linenoiseSetCompletionCallback(completionCallback); 891 892 /* Only use history when stdin is a tty. */ 893 if (isatty(fileno(stdin))) { 894 history = 1; 895 896 if (getenv("HOME") != NULL) { 897 historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME")); 898 linenoiseHistoryLoad(historyfile); 899 } 900 } 901 902 cliRefreshPrompt(); 903 while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) { 904 if (line[0] != '\0') { 905 argv = sdssplitargs(line,&argc); 906 if (history) linenoiseHistoryAdd(line); 907 if (historyfile) linenoiseHistorySave(historyfile); 908 909 if (argv == NULL) { 910 printf("Invalid argument(s)\n"); 911 free(line); 912 continue; 913 } else if (argc > 0) { 914 if (strcasecmp(argv[0],"quit") == 0 || 915 strcasecmp(argv[0],"exit") == 0) 916 { 917 exit(0); 918 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { 919 sdsfree(config.hostip); 920 config.hostip = sdsnew(argv[1]); 921 config.hostport = atoi(argv[2]); 922 cliRefreshPrompt(); 923 cliConnect(1); 924 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { 925 linenoiseClearScreen(); 926 } else { 927 long long start_time = mstime(), elapsed; 928 int repeat, skipargs = 0; 929 930 repeat = atoi(argv[0]); 931 if (argc > 1 && repeat) { 932 skipargs = 1; 933 } else { 934 repeat = 1; 935 } 936 937 while (1) { 938 config.cluster_reissue_command = 0; 939 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 940 != REDIS_OK) 941 { 942 cliConnect(1); 943 944 /* If we still cannot send the command print error. 945 * We'll try to reconnect the next time. */ 946 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 947 != REDIS_OK) 948 cliPrintContextError(); 949 } 950 /* Issue the command again if we got redirected in cluster mode */ 951 if (config.cluster_mode && config.cluster_reissue_command) { 952 cliConnect(1); 953 } else { 954 break; 955 } 956 } 957 elapsed = mstime()-start_time; 958 if (elapsed >= 500) { 959 printf("(%.2fs)\n",(double)elapsed/1000); 960 } 961 } 962 } 963 /* Free the argument vector */ 964 sdsfreesplitres(argv,argc); 965 } 966 /* linenoise() returns malloc-ed lines like readline() */ 967 free(line); 968 } 969 exit(0); 970 } 971 972 static int noninteractive(int argc, char **argv) { 973 int retval = 0; 974 if (config.stdinarg) { 975 argv = zrealloc(argv, (argc+1)*sizeof(char*)); 976 argv[argc] = readArgFromStdin(); 977 retval = cliSendCommand(argc+1, argv, config.repeat); 978 } else { 979 /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ 980 retval = cliSendCommand(argc, argv, config.repeat); 981 } 982 return retval; 983 } 984 985 /*------------------------------------------------------------------------------ 986 * Eval mode 987 *--------------------------------------------------------------------------- */ 988 989 static int evalMode(int argc, char **argv) { 990 sds script = sdsempty(); 991 FILE *fp; 992 char buf[1024]; 993 size_t nread; 994 char **argv2; 995 int j, got_comma = 0, keys = 0; 996 997 /* Load the script from the file, as an sds string. */ 998 fp = fopen(config.eval,"r"); 999 if (!fp) { 1000 fprintf(stderr, 1001 "Can't open file '%s': %s\n", config.eval, strerror(errno)); 1002 exit(1); 1003 } 1004 while((nread = fread(buf,1,sizeof(buf),fp)) != 0) { 1005 script = sdscatlen(script,buf,nread); 1006 } 1007 fclose(fp); 1008 1009 /* Create our argument vector */ 1010 argv2 = zmalloc(sizeof(sds)*(argc+3)); 1011 argv2[0] = sdsnew("EVAL"); 1012 argv2[1] = script; 1013 for (j = 0; j < argc; j++) { 1014 if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) { 1015 got_comma = 1; 1016 continue; 1017 } 1018 argv2[j+3-got_comma] = sdsnew(argv[j]); 1019 if (!got_comma) keys++; 1020 } 1021 argv2[2] = sdscatprintf(sdsempty(),"%d",keys); 1022 1023 /* Call it */ 1024 return cliSendCommand(argc+3-got_comma, argv2, config.repeat); 1025 } 1026 1027 /*------------------------------------------------------------------------------ 1028 * Latency and latency history modes 1029 *--------------------------------------------------------------------------- */ 1030 1031 #define LATENCY_SAMPLE_RATE 10 /* milliseconds. */ 1032 #define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */ 1033 static void latencyMode(void) { 1034 redisReply *reply; 1035 long long start, latency, min = 0, max = 0, tot = 0, count = 0; 1036 long long history_interval = 1037 config.interval ? config.interval/1000 : 1038 LATENCY_HISTORY_DEFAULT_INTERVAL; 1039 double avg; 1040 long long history_start = mstime(); 1041 1042 if (!context) exit(1); 1043 while(1) { 1044 start = mstime(); 1045 reply = redisCommand(context,"PING"); 1046 if (reply == NULL) { 1047 fprintf(stderr,"\nI/O error\n"); 1048 exit(1); 1049 } 1050 latency = mstime()-start; 1051 freeReplyObject(reply); 1052 count++; 1053 if (count == 1) { 1054 min = max = tot = latency; 1055 avg = (double) latency; 1056 } else { 1057 if (latency < min) min = latency; 1058 if (latency > max) max = latency; 1059 tot += latency; 1060 avg = (double) tot/count; 1061 } 1062 printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)", 1063 min, max, avg, count); 1064 fflush(stdout); 1065 if (config.latency_history && mstime()-history_start > history_interval) 1066 { 1067 printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000); 1068 history_start = mstime(); 1069 min = max = tot = count = 0; 1070 } 1071 usleep(LATENCY_SAMPLE_RATE * 1000); 1072 } 1073 } 1074 1075 /*------------------------------------------------------------------------------ 1076 * Slave mode 1077 *--------------------------------------------------------------------------- */ 1078 1079 /* Sends SYNC and reads the number of bytes in the payload. Used both by 1080 * slaveMode() and getRDB(). */ 1081 unsigned long long sendSync(int fd) { 1082 /* To start we need to send the SYNC command and return the payload. 1083 * The hiredis client lib does not understand this part of the protocol 1084 * and we don't want to mess with its buffers, so everything is performed 1085 * using direct low-level I/O. */ 1086 char buf[4096], *p; 1087 ssize_t nread; 1088 1089 /* Send the SYNC command. */ 1090 if (write(fd,"SYNC\r\n",6) != 6) { 1091 fprintf(stderr,"Error writing to master\n"); 1092 exit(1); 1093 } 1094 1095 /* Read $<payload>\r\n, making sure to read just up to "\n" */ 1096 p = buf; 1097 while(1) { 1098 nread = read(fd,p,1); 1099 if (nread <= 0) { 1100 fprintf(stderr,"Error reading bulk length while SYNCing\n"); 1101 exit(1); 1102 } 1103 if (*p == '\n' && p != buf) break; 1104 if (*p != '\n') p++; 1105 } 1106 *p = '\0'; 1107 if (buf[0] == '-') { 1108 printf("SYNC with master failed: %s\n", buf); 1109 exit(1); 1110 } 1111 return strtoull(buf+1,NULL,10); 1112 } 1113 1114 static void slaveMode(void) { 1115 int fd = context->fd; 1116 unsigned long long payload = sendSync(fd); 1117 char buf[1024]; 1118 int original_output = config.output; 1119 1120 fprintf(stderr,"SYNC with master, discarding %llu " 1121 "bytes of bulk transfer...\n", payload); 1122 1123 /* Discard the payload. */ 1124 while(payload) { 1125 ssize_t nread; 1126 1127 nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); 1128 if (nread <= 0) { 1129 fprintf(stderr,"Error reading RDB payload while SYNCing\n"); 1130 exit(1); 1131 } 1132 payload -= nread; 1133 } 1134 fprintf(stderr,"SYNC done. Logging commands from master.\n"); 1135 1136 /* Now we can use hiredis to read the incoming protocol. */ 1137 config.output = OUTPUT_CSV; 1138 while (cliReadReply(0) == REDIS_OK); 1139 config.output = original_output; 1140 } 1141 1142 /*------------------------------------------------------------------------------ 1143 * RDB transfer mode 1144 *--------------------------------------------------------------------------- */ 1145 1146 /* This function implements --rdb, so it uses the replication protocol in order 1147 * to fetch the RDB file from a remote server. */ 1148 static void getRDB(void) { 1149 int s = context->fd; 1150 int fd; 1151 unsigned long long payload = sendSync(s); 1152 char buf[4096]; 1153 1154 fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n", 1155 payload, config.rdb_filename); 1156 1157 /* Write to file. */ 1158 if (!strcmp(config.rdb_filename,"-")) { 1159 fd = STDOUT_FILENO; 1160 } else { 1161 fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644); 1162 if (fd == -1) { 1163 fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename, 1164 strerror(errno)); 1165 exit(1); 1166 } 1167 } 1168 1169 while(payload) { 1170 ssize_t nread, nwritten; 1171 1172 nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); 1173 if (nread <= 0) { 1174 fprintf(stderr,"I/O Error reading RDB payload from socket\n"); 1175 exit(1); 1176 } 1177 nwritten = write(fd, buf, nread); 1178 if (nwritten != nread) { 1179 fprintf(stderr,"Error writing data to file: %s\n", 1180 strerror(errno)); 1181 exit(1); 1182 } 1183 payload -= nread; 1184 } 1185 close(s); /* Close the file descriptor ASAP as fsync() may take time. */ 1186 fsync(fd); 1187 fprintf(stderr,"Transfer finished with success.\n"); 1188 exit(0); 1189 } 1190 1191 /*------------------------------------------------------------------------------ 1192 * Bulk import (pipe) mode 1193 *--------------------------------------------------------------------------- */ 1194 1195 static void pipeMode(void) { 1196 int fd = context->fd; 1197 long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0; 1198 char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */ 1199 char aneterr[ANET_ERR_LEN]; 1200 redisReader *reader = redisReaderCreate(); 1201 redisReply *reply; 1202 int eof = 0; /* True once we consumed all the standard input. */ 1203 int done = 0; 1204 char magic[20]; /* Special reply we recognize. */ 1205 time_t last_read_time = time(NULL); 1206 1207 srand(time(NULL)); 1208 1209 /* Use non blocking I/O. */ 1210 if (anetNonBlock(aneterr,fd) == ANET_ERR) { 1211 fprintf(stderr, "Can't set the socket in non blocking mode: %s\n", 1212 aneterr); 1213 exit(1); 1214 } 1215 1216 /* Transfer raw protocol and read replies from the server at the same 1217 * time. */ 1218 while(!done) { 1219 int mask = AE_READABLE; 1220 1221 if (!eof || obuf_len != 0) mask |= AE_WRITABLE; 1222 mask = aeWait(fd,mask,1000); 1223 1224 /* Handle the readable state: we can read replies from the server. */ 1225 if (mask & AE_READABLE) { 1226 ssize_t nread; 1227 1228 /* Read from socket and feed the hiredis reader. */ 1229 do { 1230 nread = read(fd,ibuf,sizeof(ibuf)); 1231 if (nread == -1 && errno != EAGAIN && errno != EINTR) { 1232 fprintf(stderr, "Error reading from the server: %s\n", 1233 strerror(errno)); 1234 exit(1); 1235 } 1236 if (nread > 0) { 1237 redisReaderFeed(reader,ibuf,nread); 1238 last_read_time = time(NULL); 1239 } 1240 } while(nread > 0); 1241 1242 /* Consume replies. */ 1243 do { 1244 if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) { 1245 fprintf(stderr, "Error reading replies from server\n"); 1246 exit(1); 1247 } 1248 if (reply) { 1249 if (reply->type == REDIS_REPLY_ERROR) { 1250 fprintf(stderr,"%s\n", reply->str); 1251 errors++; 1252 } else if (eof && reply->type == REDIS_REPLY_STRING && 1253 reply->len == 20) { 1254 /* Check if this is the reply to our final ECHO 1255 * command. If so everything was received 1256 * from the server. */ 1257 if (memcmp(reply->str,magic,20) == 0) { 1258 printf("Last reply received from server.\n"); 1259 done = 1; 1260 replies--; 1261 } 1262 } 1263 replies++; 1264 freeReplyObject(reply); 1265 } 1266 } while(reply); 1267 } 1268 1269 /* Handle the writable state: we can send protocol to the server. */ 1270 if (mask & AE_WRITABLE) { 1271 while(1) { 1272 /* Transfer current buffer to server. */ 1273 if (obuf_len != 0) { 1274 ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len); 1275 1276 if (nwritten == -1) { 1277 if (errno != EAGAIN && errno != EINTR) { 1278 fprintf(stderr, "Error writing to the server: %s\n", 1279 strerror(errno)); 1280 exit(1); 1281 } else { 1282 nwritten = 0; 1283 } 1284 } 1285 obuf_len -= nwritten; 1286 obuf_pos += nwritten; 1287 if (obuf_len != 0) break; /* Can't accept more data. */ 1288 } 1289 /* If buffer is empty, load from stdin. */ 1290 if (obuf_len == 0 && !eof) { 1291 ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf)); 1292 1293 if (nread == 0) { 1294 /* The ECHO sequence starts with a "\r\n" so that if there 1295 * is garbage in the protocol we read from stdin, the ECHO 1296 * will likely still be properly formatted. 1297 * CRLF is ignored by Redis, so it has no effects. */ 1298 char echo[] = 1299 "\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n"; 1300 int j; 1301 1302 eof = 1; 1303 /* Everything transferred, so we queue a special 1304 * ECHO command that we can match in the replies 1305 * to make sure everything was read from the server. */ 1306 for (j = 0; j < 20; j++) 1307 magic[j] = rand() & 0xff; 1308 memcpy(echo+21,magic,20); 1309 memcpy(obuf,echo,sizeof(echo)-1); 1310 obuf_len = sizeof(echo)-1; 1311 obuf_pos = 0; 1312 printf("All data transferred. Waiting for the last reply...\n"); 1313 } else if (nread == -1) { 1314 fprintf(stderr, "Error reading from stdin: %s\n", 1315 strerror(errno)); 1316 exit(1); 1317 } else { 1318 obuf_len = nread; 1319 obuf_pos = 0; 1320 } 1321 } 1322 if (obuf_len == 0 && eof) break; 1323 } 1324 } 1325 1326 /* Handle timeout, that is, we reached EOF, and we are not getting 1327 * replies from the server for a few seconds, nor the final ECHO is 1328 * received. */ 1329 if (eof && config.pipe_timeout > 0 && 1330 time(NULL)-last_read_time > config.pipe_timeout) 1331 { 1332 fprintf(stderr,"No replies for %d seconds: exiting.\n", 1333 config.pipe_timeout); 1334 errors++; 1335 break; 1336 } 1337 } 1338 redisReaderFree(reader); 1339 printf("errors: %lld, replies: %lld\n", errors, replies); 1340 if (errors) 1341 exit(1); 1342 else 1343 exit(0); 1344 } 1345 1346 /*------------------------------------------------------------------------------ 1347 * Find big keys 1348 *--------------------------------------------------------------------------- */ 1349 1350 #define TYPE_STRING 0 1351 #define TYPE_LIST 1 1352 #define TYPE_SET 2 1353 #define TYPE_HASH 3 1354 #define TYPE_ZSET 4 1355 #define TYPE_NONE 5 1356 1357 static redisReply *sendScan(unsigned long long *it) { 1358 redisReply *reply = redisCommand(context, "SCAN %llu", *it); 1359 1360 /* Handle any error conditions */ 1361 if(reply == NULL) { 1362 fprintf(stderr, "\nI/O error\n"); 1363 exit(1); 1364 } else if(reply->type == REDIS_REPLY_ERROR) { 1365 fprintf(stderr, "SCAN error: %s\n", reply->str); 1366 exit(1); 1367 } else if(reply->type != REDIS_REPLY_ARRAY) { 1368 fprintf(stderr, "Non ARRAY response from SCAN!\n"); 1369 exit(1); 1370 } else if(reply->elements != 2) { 1371 fprintf(stderr, "Invalid element count from SCAN!\n"); 1372 exit(1); 1373 } 1374 1375 /* Validate our types are correct */ 1376 assert(reply->element[0]->type == REDIS_REPLY_STRING); 1377 assert(reply->element[1]->type == REDIS_REPLY_ARRAY); 1378 1379 /* Update iterator */ 1380 *it = atoi(reply->element[0]->str); 1381 1382 return reply; 1383 } 1384 1385 static int getDbSize(void) { 1386 redisReply *reply; 1387 int size; 1388 1389 reply = redisCommand(context, "DBSIZE"); 1390 1391 if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) { 1392 fprintf(stderr, "Couldn't determine DBSIZE!\n"); 1393 exit(1); 1394 } 1395 1396 /* Grab the number of keys and free our reply */ 1397 size = reply->integer; 1398 freeReplyObject(reply); 1399 1400 return size; 1401 } 1402 1403 static int toIntType(char *key, char *type) { 1404 if(!strcmp(type, "string")) { 1405 return TYPE_STRING; 1406 } else if(!strcmp(type, "list")) { 1407 return TYPE_LIST; 1408 } else if(!strcmp(type, "set")) { 1409 return TYPE_SET; 1410 } else if(!strcmp(type, "hash")) { 1411 return TYPE_HASH; 1412 } else if(!strcmp(type, "zset")) { 1413 return TYPE_ZSET; 1414 } else if(!strcmp(type, "none")) { 1415 return TYPE_NONE; 1416 } else { 1417 fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key); 1418 exit(1); 1419 } 1420 } 1421 1422 static void getKeyTypes(redisReply *keys, int *types) { 1423 redisReply *reply; 1424 unsigned int i; 1425 1426 /* Pipeline TYPE commands */ 1427 for(i=0;i<keys->elements;i++) { 1428 redisAppendCommand(context, "TYPE %s", keys->element[i]->str); 1429 } 1430 1431 /* Retrieve types */ 1432 for(i=0;i<keys->elements;i++) { 1433 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) { 1434 fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n", 1435 keys->element[i]->str, context->err, context->errstr); 1436 exit(1); 1437 } else if(reply->type != REDIS_REPLY_STATUS) { 1438 fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n", 1439 reply->type, keys->element[i]->str); 1440 exit(1); 1441 } 1442 1443 types[i] = toIntType(keys->element[i]->str, reply->str); 1444 freeReplyObject(reply); 1445 } 1446 } 1447 1448 static void getKeySizes(redisReply *keys, int *types, 1449 unsigned long long *sizes) 1450 { 1451 redisReply *reply; 1452 char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"}; 1453 unsigned int i; 1454 1455 /* Pipeline size commands */ 1456 for(i=0;i<keys->elements;i++) { 1457 /* Skip keys that were deleted */ 1458 if(types[i]==TYPE_NONE) 1459 continue; 1460 1461 redisAppendCommand(context, "%s %s", sizecmds[types[i]], 1462 keys->element[i]->str); 1463 } 1464 1465 /* Retreive sizes */ 1466 for(i=0;i<keys->elements;i++) { 1467 /* Skip keys that dissapeared between SCAN and TYPE */ 1468 if(types[i] == TYPE_NONE) { 1469 sizes[i] = 0; 1470 continue; 1471 } 1472 1473 /* Retreive size */ 1474 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) { 1475 fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n", 1476 keys->element[i]->str, context->err, context->errstr); 1477 exit(1); 1478 } else if(reply->type != REDIS_REPLY_INTEGER) { 1479 /* Theoretically the key could have been removed and 1480 * added as a different type between TYPE and SIZE */ 1481 fprintf(stderr, 1482 "Warning: %s on '%s' failed (may have changed type)\n", 1483 sizecmds[types[i]], keys->element[i]->str); 1484 sizes[i] = 0; 1485 } else { 1486 sizes[i] = reply->integer; 1487 } 1488 1489 freeReplyObject(reply); 1490 } 1491 } 1492 1493 static void findBigKeys(void) { 1494 unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0}; 1495 unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0; 1496 sds maxkeys[5] = {0}; 1497 char *typename[] = {"string","list","set","hash","zset"}; 1498 char *typeunit[] = {"bytes","items","members","fields","members"}; 1499 redisReply *reply, *keys; 1500 unsigned int arrsize=0, i; 1501 int type, *types=NULL; 1502 double pct; 1503 1504 /* Total keys pre scanning */ 1505 total_keys = getDbSize(); 1506 1507 /* Status message */ 1508 printf("\n# Scanning the entire keyspace to find biggest keys as well as\n"); 1509 printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n"); 1510 printf("# per 100 SCAN commands (not usually needed).\n\n"); 1511 1512 /* New up sds strings to keep track of overall biggest per type */ 1513 for(i=0;i<TYPE_NONE; i++) { 1514 maxkeys[i] = sdsempty(); 1515 if(!maxkeys[i]) { 1516 fprintf(stderr, "Failed to allocate memory for largest key names!\n"); 1517 exit(1); 1518 } 1519 } 1520 1521 /* SCAN loop */ 1522 do { 1523 /* Calculate approximate percentage completion */ 1524 pct = 100 * (double)sampled/total_keys; 1525 1526 /* Grab some keys and point to the keys array */ 1527 reply = sendScan(&it); 1528 keys = reply->element[1]; 1529 1530 /* Reallocate our type and size array if we need to */ 1531 if(keys->elements > arrsize) { 1532 types = zrealloc(types, sizeof(int)*keys->elements); 1533 sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements); 1534 1535 if(!types || !sizes) { 1536 fprintf(stderr, "Failed to allocate storage for keys!\n"); 1537 exit(1); 1538 } 1539 1540 arrsize = keys->elements; 1541 } 1542 1543 /* Retreive types and then sizes */ 1544 getKeyTypes(keys, types); 1545 getKeySizes(keys, types, sizes); 1546 1547 /* Now update our stats */ 1548 for(i=0;i<keys->elements;i++) { 1549 if((type = types[i]) == TYPE_NONE) 1550 continue; 1551 1552 totalsize[type] += sizes[i]; 1553 counts[type]++; 1554 totlen += keys->element[i]->len; 1555 sampled++; 1556 1557 if(biggest[type]<sizes[i]) { 1558 printf( 1559 "[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n", 1560 pct, typename[type], keys->element[i]->str, sizes[i], 1561 typeunit[type]); 1562 1563 /* Keep track of biggest key name for this type */ 1564 maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str); 1565 if(!maxkeys[type]) { 1566 fprintf(stderr, "Failed to allocate memory for key!\n"); 1567 exit(1); 1568 } 1569 1570 /* Keep track of the biggest size for this type */ 1571 biggest[type] = sizes[i]; 1572 } 1573 1574 /* Update overall progress */ 1575 if(sampled % 1000000 == 0) { 1576 printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled); 1577 } 1578 } 1579 1580 /* Sleep if we've been directed to do so */ 1581 if(sampled && (sampled %100) == 0 && config.interval) { 1582 usleep(config.interval); 1583 } 1584 1585 freeReplyObject(reply); 1586 } while(it != 0); 1587 1588 if(types) zfree(types); 1589 if(sizes) zfree(sizes); 1590 1591 /* We're done */ 1592 printf("\n-------- summary -------\n\n"); 1593 1594 printf("Sampled %llu keys in the keyspace!\n", sampled); 1595 printf("Total key length in bytes is %llu (avg len %.2f)\n\n", 1596 totlen, totlen ? (double)totlen/sampled : 0); 1597 1598 /* Output the biggest keys we found, for types we did find */ 1599 for(i=0;i<TYPE_NONE;i++) { 1600 if(sdslen(maxkeys[i])>0) { 1601 printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i], 1602 biggest[i], typeunit[i]); 1603 } 1604 } 1605 1606 printf("\n"); 1607 1608 for(i=0;i<TYPE_NONE;i++) { 1609 printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n", 1610 counts[i], typename[i], totalsize[i], typeunit[i], 1611 sampled ? 100 * (double)counts[i]/sampled : 0, 1612 counts[i] ? (double)totalsize[i]/counts[i] : 0); 1613 } 1614 1615 /* Free sds strings containing max keys */ 1616 for(i=0;i<TYPE_NONE;i++) { 1617 sdsfree(maxkeys[i]); 1618 } 1619 1620 /* Success! */ 1621 exit(0); 1622 } 1623 1624 /*------------------------------------------------------------------------------ 1625 * Stats mode 1626 *--------------------------------------------------------------------------- */ 1627 1628 /* Return the specified INFO field from the INFO command output "info". 1629 * A new buffer is allocated for the result, that needs to be free'd. 1630 * If the field is not found NULL is returned. */ 1631 static char *getInfoField(char *info, char *field) { 1632 char *p = strstr(info,field); 1633 char *n1, *n2; 1634 char *result; 1635 1636 if (!p) return NULL; 1637 p += strlen(field)+1; 1638 n1 = strchr(p,'\r'); 1639 n2 = strchr(p,','); 1640 if (n2 && n2 < n1) n1 = n2; 1641 result = malloc(sizeof(char)*(n1-p)+1); 1642 memcpy(result,p,(n1-p)); 1643 result[n1-p] = '\0'; 1644 return result; 1645 } 1646 1647 /* Like the above function but automatically convert the result into 1648 * a long. On error (missing field) LONG_MIN is returned. */ 1649 static long getLongInfoField(char *info, char *field) { 1650 char *value = getInfoField(info,field); 1651 long l; 1652 1653 if (!value) return LONG_MIN; 1654 l = strtol(value,NULL,10); 1655 free(value); 1656 return l; 1657 } 1658 1659 /* Convert number of bytes into a human readable string of the form: 1660 * 100B, 2G, 100M, 4K, and so forth. */ 1661 void bytesToHuman(char *s, long long n) { 1662 double d; 1663 1664 if (n < 0) { 1665 *s = '-'; 1666 s++; 1667 n = -n; 1668 } 1669 if (n < 1024) { 1670 /* Bytes */ 1671 sprintf(s,"%lluB",n); 1672 return; 1673 } else if (n < (1024*1024)) { 1674 d = (double)n/(1024); 1675 sprintf(s,"%.2fK",d); 1676 } else if (n < (1024LL*1024*1024)) { 1677 d = (double)n/(1024*1024); 1678 sprintf(s,"%.2fM",d); 1679 } else if (n < (1024LL*1024*1024*1024)) { 1680 d = (double)n/(1024LL*1024*1024); 1681 sprintf(s,"%.2fG",d); 1682 } 1683 } 1684 1685 static void statMode(void) { 1686 redisReply *reply; 1687 long aux, requests = 0; 1688 int i = 0; 1689 1690 while(1) { 1691 char buf[64]; 1692 int j; 1693 1694 reply = reconnectingInfo(); 1695 if (reply->type == REDIS_REPLY_ERROR) { 1696 printf("ERROR: %s\n", reply->str); 1697 exit(1); 1698 } 1699 1700 if ((i++ % 20) == 0) { 1701 printf( 1702 "------- data ------ --------------------- load -------------------- - child -\n" 1703 "keys mem clients blocked requests connections \n"); 1704 } 1705 1706 /* Keys */ 1707 aux = 0; 1708 for (j = 0; j < 20; j++) { 1709 long k; 1710 1711 sprintf(buf,"db%d:keys",j); 1712 k = getLongInfoField(reply->str,buf); 1713 if (k == LONG_MIN) continue; 1714 aux += k; 1715 } 1716 sprintf(buf,"%ld",aux); 1717 printf("%-11s",buf); 1718 1719 /* Used memory */ 1720 aux = getLongInfoField(reply->str,"used_memory"); 1721 bytesToHuman(buf,aux); 1722 printf("%-8s",buf); 1723 1724 /* Clients */ 1725 aux = getLongInfoField(reply->str,"connected_clients"); 1726 sprintf(buf,"%ld",aux); 1727 printf(" %-8s",buf); 1728 1729 /* Blocked (BLPOPPING) Clients */ 1730 aux = getLongInfoField(reply->str,"blocked_clients"); 1731 sprintf(buf,"%ld",aux); 1732 printf("%-8s",buf); 1733 1734 /* Requets */ 1735 aux = getLongInfoField(reply->str,"total_commands_processed"); 1736 sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests); 1737 printf("%-19s",buf); 1738 requests = aux; 1739 1740 /* Connections */ 1741 aux = getLongInfoField(reply->str,"total_connections_received"); 1742 sprintf(buf,"%ld",aux); 1743 printf(" %-12s",buf); 1744 1745 /* Children */ 1746 aux = getLongInfoField(reply->str,"bgsave_in_progress"); 1747 aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1; 1748 switch(aux) { 1749 case 0: break; 1750 case 1: 1751 printf("SAVE"); 1752 break; 1753 case 2: 1754 printf("AOF"); 1755 break; 1756 case 3: 1757 printf("SAVE+AOF"); 1758 break; 1759 } 1760 1761 printf("\n"); 1762 freeReplyObject(reply); 1763 usleep(config.interval); 1764 } 1765 } 1766 1767 /*------------------------------------------------------------------------------ 1768 * Scan mode 1769 *--------------------------------------------------------------------------- */ 1770 1771 static void scanMode(void) { 1772 redisReply *reply; 1773 unsigned long long cur = 0; 1774 1775 do { 1776 if (config.pattern) 1777 reply = redisCommand(context,"SCAN %llu MATCH %s", 1778 cur,config.pattern); 1779 else 1780 reply = redisCommand(context,"SCAN %llu",cur); 1781 if (reply == NULL) { 1782 printf("I/O error\n"); 1783 exit(1); 1784 } else if (reply->type == REDIS_REPLY_ERROR) { 1785 printf("ERROR: %s\n", reply->str); 1786 exit(1); 1787 } else { 1788 unsigned int j; 1789 1790 cur = strtoull(reply->element[0]->str,NULL,10); 1791 for (j = 0; j < reply->element[1]->elements; j++) 1792 printf("%s\n", reply->element[1]->element[j]->str); 1793 } 1794 freeReplyObject(reply); 1795 } while(cur != 0); 1796 1797 exit(0); 1798 } 1799 1800 /*------------------------------------------------------------------------------ 1801 * Intrisic latency mode. 1802 * 1803 * Measure max latency of a running process that does not result from 1804 * syscalls. Basically this software should provide an hint about how much 1805 * time the kernel leaves the process without a chance to run. 1806 *--------------------------------------------------------------------------- */ 1807 1808 /* This is just some computation the compiler can't optimize out. 1809 * Should run in less than 100-200 microseconds even using very 1810 * slow hardware. Runs in less than 10 microseconds in modern HW. */ 1811 unsigned long compute_something_fast(void) { 1812 unsigned char s[256], i, j, t; 1813 int count = 1000, k; 1814 unsigned long output = 0; 1815 1816 for (k = 0; k < 256; k++) s[k] = k; 1817 1818 i = 0; 1819 j = 0; 1820 while(count--) { 1821 i++; 1822 j = j + s[i]; 1823 t = s[i]; 1824 s[i] = s[j]; 1825 s[j] = t; 1826 output += s[(s[i]+s[j])&255]; 1827 } 1828 return output; 1829 } 1830 1831 static void intrinsicLatencyModeStop(int s) { 1832 REDIS_NOTUSED(s); 1833 force_cancel_loop = 1; 1834 } 1835 1836 static void intrinsicLatencyMode(void) { 1837 long long test_end, run_time, max_latency = 0, runs = 0; 1838 1839 run_time = config.intrinsic_latency_duration*1000000; 1840 test_end = ustime() + run_time; 1841 signal(SIGINT, intrinsicLatencyModeStop); 1842 1843 while(1) { 1844 long long start, end, latency; 1845 1846 start = ustime(); 1847 compute_something_fast(); 1848 end = ustime(); 1849 latency = end-start; 1850 runs++; 1851 if (latency <= 0) continue; 1852 1853 /* Reporting */ 1854 if (latency > max_latency) { 1855 max_latency = latency; 1856 printf("Max latency so far: %lld microseconds.\n", max_latency); 1857 } 1858 1859 double avg_us = (double)run_time/runs; 1860 double avg_ns = avg_us * 10e3; 1861 if (force_cancel_loop || end > test_end) { 1862 printf("\n%lld total runs " 1863 "(avg latency: " 1864 "%.4f microseconds / %.2f nanoseconds per run).\n", 1865 runs, avg_us, avg_ns); 1866 printf("Worst run took %.0fx longer than the average latency.\n", 1867 max_latency / avg_us); 1868 exit(0); 1869 } 1870 } 1871 } 1872 1873 /*------------------------------------------------------------------------------ 1874 * Program main() 1875 *--------------------------------------------------------------------------- */ 1876 1877 int main(int argc, char **argv) { 1878 int firstarg; 1879 1880 config.hostip = sdsnew("127.0.0.1"); 1881 config.hostport = 6379; 1882 config.hostsocket = NULL; 1883 config.repeat = 1; 1884 config.interval = 0; 1885 config.dbnum = 0; 1886 config.interactive = 0; 1887 config.shutdown = 0; 1888 config.monitor_mode = 0; 1889 config.pubsub_mode = 0; 1890 config.latency_mode = 0; 1891 config.latency_history = 0; 1892 config.cluster_mode = 0; 1893 config.slave_mode = 0; 1894 config.getrdb_mode = 0; 1895 config.stat_mode = 0; 1896 config.scan_mode = 0; 1897 config.intrinsic_latency_mode = 0; 1898 config.pattern = NULL; 1899 config.rdb_filename = NULL; 1900 config.pipe_mode = 0; 1901 config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT; 1902 config.bigkeys = 0; 1903 config.stdinarg = 0; 1904 config.auth = NULL; 1905 config.eval = NULL; 1906 config.last_cmd_type = -1; 1907 1908 if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL)) 1909 config.output = OUTPUT_RAW; 1910 else 1911 config.output = OUTPUT_STANDARD; 1912 config.mb_delim = sdsnew("\n"); 1913 cliInitHelp(); 1914 1915 firstarg = parseOptions(argc,argv); 1916 argc -= firstarg; 1917 argv += firstarg; 1918 1919 /* Latency mode */ 1920 if (config.latency_mode) { 1921 if (cliConnect(0) == REDIS_ERR) exit(1); 1922 latencyMode(); 1923 } 1924 1925 /* Slave mode */ 1926 if (config.slave_mode) { 1927 if (cliConnect(0) == REDIS_ERR) exit(1); 1928 slaveMode(); 1929 } 1930 1931 /* Get RDB mode. */ 1932 if (config.getrdb_mode) { 1933 if (cliConnect(0) == REDIS_ERR) exit(1); 1934 getRDB(); 1935 } 1936 1937 /* Pipe mode */ 1938 if (config.pipe_mode) { 1939 if (cliConnect(0) == REDIS_ERR) exit(1); 1940 pipeMode(); 1941 } 1942 1943 /* Find big keys */ 1944 if (config.bigkeys) { 1945 if (cliConnect(0) == REDIS_ERR) exit(1); 1946 findBigKeys(); 1947 } 1948 1949 /* Stat mode */ 1950 if (config.stat_mode) { 1951 if (cliConnect(0) == REDIS_ERR) exit(1); 1952 if (config.interval == 0) config.interval = 1000000; 1953 statMode(); 1954 } 1955 1956 /* Scan mode */ 1957 if (config.scan_mode) { 1958 if (cliConnect(0) == REDIS_ERR) exit(1); 1959 scanMode(); 1960 } 1961 1962 /* Intrinsic latency mode */ 1963 if (config.intrinsic_latency_mode) intrinsicLatencyMode(); 1964 1965 /* Start interactive mode when no command is provided */ 1966 if (argc == 0 && !config.eval) { 1967 /* Ignore SIGPIPE in interactive mode to force a reconnect */ 1968 signal(SIGPIPE, SIG_IGN); 1969 1970 /* Note that in repl mode we don't abort on connection error. 1971 * A new attempt will be performed for every command send. */ 1972 cliConnect(0); 1973 repl(); 1974 } 1975 1976 /* Otherwise, we have some arguments to execute */ 1977 if (cliConnect(0) != REDIS_OK) exit(1); 1978 if (config.eval) { 1979 return evalMode(argc,argv); 1980 } else { 1981 return noninteractive(argc,convertToSds(argc,argv)); 1982 } 1983 } 1984