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