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