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 604 { 605 output_raw = 1; 606 } 607 608 if (!strcasecmp(command,"shutdown")) config.shutdown = 1; 609 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; 610 if (!strcasecmp(command,"subscribe") || 611 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; 612 if (!strcasecmp(command,"sync") || 613 !strcasecmp(command,"psync")) config.slave_mode = 1; 614 615 /* Setup argument length */ 616 argvlen = malloc(argc*sizeof(size_t)); 617 for (j = 0; j < argc; j++) 618 argvlen[j] = sdslen(argv[j]); 619 620 while(repeat--) { 621 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); 622 while (config.monitor_mode) { 623 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 624 fflush(stdout); 625 } 626 627 if (config.pubsub_mode) { 628 if (config.output != OUTPUT_RAW) 629 printf("Reading messages... (press Ctrl-C to quit)\n"); 630 while (1) { 631 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 632 } 633 } 634 635 if (config.slave_mode) { 636 printf("Entering slave output mode... (press Ctrl-C to quit)\n"); 637 slaveMode(); 638 config.slave_mode = 0; 639 return REDIS_ERR; /* Error = slaveMode lost connection to master */ 640 } 641 642 if (cliReadReply(output_raw) != REDIS_OK) { 643 free(argvlen); 644 return REDIS_ERR; 645 } else { 646 /* Store database number when SELECT was successfully executed. */ 647 if (!strcasecmp(command,"select") && argc == 2) { 648 config.dbnum = atoi(argv[1]); 649 cliRefreshPrompt(); 650 } 651 } 652 if (config.interval) usleep(config.interval); 653 fflush(stdout); /* Make it grep friendly */ 654 } 655 656 free(argvlen); 657 return REDIS_OK; 658 } 659 660 /* Send the INFO command, reconnecting the link if needed. */ 661 static redisReply *reconnectingInfo(void) { 662 redisContext *c = context; 663 redisReply *reply = NULL; 664 int tries = 0; 665 666 assert(!c->err); 667 while(reply == NULL) { 668 while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) { 669 printf("Reconnecting (%d)...\r", ++tries); 670 fflush(stdout); 671 672 redisFree(c); 673 c = redisConnect(config.hostip,config.hostport); 674 usleep(1000000); 675 } 676 677 reply = redisCommand(c,"INFO"); 678 if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) { 679 fprintf(stderr, "Error: %s\n", c->errstr); 680 exit(1); 681 } else if (tries > 0) { 682 printf("\n"); 683 } 684 } 685 686 context = c; 687 return reply; 688 } 689 690 /*------------------------------------------------------------------------------ 691 * User interface 692 *--------------------------------------------------------------------------- */ 693 694 static int parseOptions(int argc, char **argv) { 695 int i; 696 697 for (i = 1; i < argc; i++) { 698 int lastarg = i==argc-1; 699 700 if (!strcmp(argv[i],"-h") && !lastarg) { 701 sdsfree(config.hostip); 702 config.hostip = sdsnew(argv[++i]); 703 } else if (!strcmp(argv[i],"-h") && lastarg) { 704 usage(); 705 } else if (!strcmp(argv[i],"--help")) { 706 usage(); 707 } else if (!strcmp(argv[i],"-x")) { 708 config.stdinarg = 1; 709 } else if (!strcmp(argv[i],"-p") && !lastarg) { 710 config.hostport = atoi(argv[++i]); 711 } else if (!strcmp(argv[i],"-s") && !lastarg) { 712 config.hostsocket = argv[++i]; 713 } else if (!strcmp(argv[i],"-r") && !lastarg) { 714 config.repeat = strtoll(argv[++i],NULL,10); 715 } else if (!strcmp(argv[i],"-i") && !lastarg) { 716 double seconds = atof(argv[++i]); 717 config.interval = seconds*1000000; 718 } else if (!strcmp(argv[i],"-n") && !lastarg) { 719 config.dbnum = atoi(argv[++i]); 720 } else if (!strcmp(argv[i],"-a") && !lastarg) { 721 config.auth = argv[++i]; 722 } else if (!strcmp(argv[i],"--raw")) { 723 config.output = OUTPUT_RAW; 724 } else if (!strcmp(argv[i],"--csv")) { 725 config.output = OUTPUT_CSV; 726 } else if (!strcmp(argv[i],"--latency")) { 727 config.latency_mode = 1; 728 } else if (!strcmp(argv[i],"--latency-history")) { 729 config.latency_mode = 1; 730 config.latency_history = 1; 731 } else if (!strcmp(argv[i],"--slave")) { 732 config.slave_mode = 1; 733 } else if (!strcmp(argv[i],"--stat")) { 734 config.stat_mode = 1; 735 } else if (!strcmp(argv[i],"--scan")) { 736 config.scan_mode = 1; 737 } else if (!strcmp(argv[i],"--pattern") && !lastarg) { 738 config.pattern = argv[++i]; 739 } else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) { 740 config.intrinsic_latency_mode = 1; 741 config.intrinsic_latency_duration = atoi(argv[++i]); 742 } else if (!strcmp(argv[i],"--rdb") && !lastarg) { 743 config.getrdb_mode = 1; 744 config.rdb_filename = argv[++i]; 745 } else if (!strcmp(argv[i],"--pipe")) { 746 config.pipe_mode = 1; 747 } else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) { 748 config.pipe_timeout = atoi(argv[++i]); 749 } else if (!strcmp(argv[i],"--bigkeys")) { 750 config.bigkeys = 1; 751 } else if (!strcmp(argv[i],"--eval") && !lastarg) { 752 config.eval = argv[++i]; 753 } else if (!strcmp(argv[i],"-c")) { 754 config.cluster_mode = 1; 755 } else if (!strcmp(argv[i],"-d") && !lastarg) { 756 sdsfree(config.mb_delim); 757 config.mb_delim = sdsnew(argv[++i]); 758 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { 759 sds version = cliVersion(); 760 printf("redis-cli %s\n", version); 761 sdsfree(version); 762 exit(0); 763 } else { 764 if (argv[i][0] == '-') { 765 fprintf(stderr, 766 "Unrecognized option or bad number of args for: '%s'\n", 767 argv[i]); 768 exit(1); 769 } else { 770 /* Likely the command name, stop here. */ 771 break; 772 } 773 } 774 } 775 return i; 776 } 777 778 static sds readArgFromStdin(void) { 779 char buf[1024]; 780 sds arg = sdsempty(); 781 782 while(1) { 783 int nread = read(fileno(stdin),buf,1024); 784 785 if (nread == 0) break; 786 else if (nread == -1) { 787 perror("Reading from standard input"); 788 exit(1); 789 } 790 arg = sdscatlen(arg,buf,nread); 791 } 792 return arg; 793 } 794 795 static void usage() { 796 sds version = cliVersion(); 797 fprintf(stderr, 798 "redis-cli %s\n" 799 "\n" 800 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" 801 " -h <hostname> Server hostname (default: 127.0.0.1).\n" 802 " -p <port> Server port (default: 6379).\n" 803 " -s <socket> Server socket (overrides hostname and port).\n" 804 " -a <password> Password to use when connecting to the server.\n" 805 " -r <repeat> Execute specified command N times.\n" 806 " -i <interval> When -r is used, waits <interval> seconds per command.\n" 807 " It is possible to specify sub-second times like -i 0.1.\n" 808 " -n <db> Database number.\n" 809 " -x Read last argument from STDIN.\n" 810 " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n" 811 " -c Enable cluster mode (follow -ASK and -MOVED redirections).\n" 812 " --raw Use raw formatting for replies (default when STDOUT is\n" 813 " not a tty).\n" 814 " --csv Output in CSV format.\n" 815 " --latency Enter a special mode continuously sampling latency.\n" 816 " --latency-history Like --latency but tracking latency changes over time.\n" 817 " Default time interval is 15 sec. Change it using -i.\n" 818 " --slave Simulate a slave showing commands received from the master.\n" 819 " --rdb <filename> Transfer an RDB dump from remote server to local file.\n" 820 " --pipe Transfer raw Redis protocol from stdin to server.\n" 821 " --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n" 822 " no reply is received within <n> seconds.\n" 823 " Default timeout: %d. Use 0 to wait forever.\n" 824 " --bigkeys Sample Redis keys looking for big keys.\n" 825 " --scan List all keys using the SCAN command.\n" 826 " --pattern <pat> Useful with --scan to specify a SCAN pattern.\n" 827 " --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n" 828 " The test will run for the specified amount of seconds.\n" 829 " --eval <file> Send an EVAL command using the Lua script at <file>.\n" 830 " --help Output this help and exit.\n" 831 " --version Output version and exit.\n" 832 "\n" 833 "Examples:\n" 834 " cat /etc/passwd | redis-cli -x set mypasswd\n" 835 " redis-cli get mypasswd\n" 836 " redis-cli -r 100 lpush mylist x\n" 837 " redis-cli -r 100 -i 1 info | grep used_memory_human:\n" 838 " redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n" 839 " redis-cli --scan --pattern '*:12345*'\n" 840 "\n" 841 " (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n" 842 "\n" 843 "When no command is given, redis-cli starts in interactive mode.\n" 844 "Type \"help\" in interactive mode for information on available commands.\n" 845 "\n", 846 version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT); 847 sdsfree(version); 848 exit(1); 849 } 850 851 /* Turn the plain C strings into Sds strings */ 852 static char **convertToSds(int count, char** args) { 853 int j; 854 char **sds = zmalloc(sizeof(char*)*count); 855 856 for(j = 0; j < count; j++) 857 sds[j] = sdsnew(args[j]); 858 859 return sds; 860 } 861 862 #define LINE_BUFLEN 4096 863 static void repl() { 864 sds historyfile = NULL; 865 int history = 0; 866 char *line; 867 int argc; 868 sds *argv; 869 870 config.interactive = 1; 871 linenoiseSetMultiLine(1); 872 linenoiseSetCompletionCallback(completionCallback); 873 874 /* Only use history when stdin is a tty. */ 875 if (isatty(fileno(stdin))) { 876 history = 1; 877 878 if (getenv("HOME") != NULL) { 879 historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME")); 880 linenoiseHistoryLoad(historyfile); 881 } 882 } 883 884 cliRefreshPrompt(); 885 while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) { 886 if (line[0] != '\0') { 887 argv = sdssplitargs(line,&argc); 888 if (history) linenoiseHistoryAdd(line); 889 if (historyfile) linenoiseHistorySave(historyfile); 890 891 if (argv == NULL) { 892 printf("Invalid argument(s)\n"); 893 free(line); 894 continue; 895 } else if (argc > 0) { 896 if (strcasecmp(argv[0],"quit") == 0 || 897 strcasecmp(argv[0],"exit") == 0) 898 { 899 exit(0); 900 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { 901 sdsfree(config.hostip); 902 config.hostip = sdsnew(argv[1]); 903 config.hostport = atoi(argv[2]); 904 cliRefreshPrompt(); 905 cliConnect(1); 906 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { 907 linenoiseClearScreen(); 908 } else { 909 long long start_time = mstime(), elapsed; 910 int repeat, skipargs = 0; 911 912 repeat = atoi(argv[0]); 913 if (argc > 1 && repeat) { 914 skipargs = 1; 915 } else { 916 repeat = 1; 917 } 918 919 while (1) { 920 config.cluster_reissue_command = 0; 921 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 922 != REDIS_OK) 923 { 924 cliConnect(1); 925 926 /* If we still cannot send the command print error. 927 * We'll try to reconnect the next time. */ 928 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 929 != REDIS_OK) 930 cliPrintContextError(); 931 } 932 /* Issue the command again if we got redirected in cluster mode */ 933 if (config.cluster_mode && config.cluster_reissue_command) { 934 cliConnect(1); 935 } else { 936 break; 937 } 938 } 939 elapsed = mstime()-start_time; 940 if (elapsed >= 500) { 941 printf("(%.2fs)\n",(double)elapsed/1000); 942 } 943 } 944 } 945 /* Free the argument vector */ 946 sdsfreesplitres(argv,argc); 947 } 948 /* linenoise() returns malloc-ed lines like readline() */ 949 free(line); 950 } 951 exit(0); 952 } 953 954 static int noninteractive(int argc, char **argv) { 955 int retval = 0; 956 if (config.stdinarg) { 957 argv = zrealloc(argv, (argc+1)*sizeof(char*)); 958 argv[argc] = readArgFromStdin(); 959 retval = cliSendCommand(argc+1, argv, config.repeat); 960 } else { 961 /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ 962 retval = cliSendCommand(argc, argv, config.repeat); 963 } 964 return retval; 965 } 966 967 /*------------------------------------------------------------------------------ 968 * Eval mode 969 *--------------------------------------------------------------------------- */ 970 971 static int evalMode(int argc, char **argv) { 972 sds script = sdsempty(); 973 FILE *fp; 974 char buf[1024]; 975 size_t nread; 976 char **argv2; 977 int j, got_comma = 0, keys = 0; 978 979 /* Load the script from the file, as an sds string. */ 980 fp = fopen(config.eval,"r"); 981 if (!fp) { 982 fprintf(stderr, 983 "Can't open file '%s': %s\n", config.eval, strerror(errno)); 984 exit(1); 985 } 986 while((nread = fread(buf,1,sizeof(buf),fp)) != 0) { 987 script = sdscatlen(script,buf,nread); 988 } 989 fclose(fp); 990 991 /* Create our argument vector */ 992 argv2 = zmalloc(sizeof(sds)*(argc+3)); 993 argv2[0] = sdsnew("EVAL"); 994 argv2[1] = script; 995 for (j = 0; j < argc; j++) { 996 if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) { 997 got_comma = 1; 998 continue; 999 } 1000 argv2[j+3-got_comma] = sdsnew(argv[j]); 1001 if (!got_comma) keys++; 1002 } 1003 argv2[2] = sdscatprintf(sdsempty(),"%d",keys); 1004 1005 /* Call it */ 1006 return cliSendCommand(argc+3-got_comma, argv2, config.repeat); 1007 } 1008 1009 /*------------------------------------------------------------------------------ 1010 * Latency and latency history modes 1011 *--------------------------------------------------------------------------- */ 1012 1013 #define LATENCY_SAMPLE_RATE 10 /* milliseconds. */ 1014 #define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */ 1015 static void latencyMode(void) { 1016 redisReply *reply; 1017 long long start, latency, min = 0, max = 0, tot = 0, count = 0; 1018 long long history_interval = 1019 config.interval ? config.interval/1000 : 1020 LATENCY_HISTORY_DEFAULT_INTERVAL; 1021 double avg; 1022 long long history_start = mstime(); 1023 1024 if (!context) exit(1); 1025 while(1) { 1026 start = mstime(); 1027 reply = redisCommand(context,"PING"); 1028 if (reply == NULL) { 1029 fprintf(stderr,"\nI/O error\n"); 1030 exit(1); 1031 } 1032 latency = mstime()-start; 1033 freeReplyObject(reply); 1034 count++; 1035 if (count == 1) { 1036 min = max = tot = latency; 1037 avg = (double) latency; 1038 } else { 1039 if (latency < min) min = latency; 1040 if (latency > max) max = latency; 1041 tot += latency; 1042 avg = (double) tot/count; 1043 } 1044 printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)", 1045 min, max, avg, count); 1046 fflush(stdout); 1047 if (config.latency_history && mstime()-history_start > history_interval) 1048 { 1049 printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000); 1050 history_start = mstime(); 1051 min = max = tot = count = 0; 1052 } 1053 usleep(LATENCY_SAMPLE_RATE * 1000); 1054 } 1055 } 1056 1057 /*------------------------------------------------------------------------------ 1058 * Slave mode 1059 *--------------------------------------------------------------------------- */ 1060 1061 /* Sends SYNC and reads the number of bytes in the payload. Used both by 1062 * slaveMode() and getRDB(). */ 1063 unsigned long long sendSync(int fd) { 1064 /* To start we need to send the SYNC command and return the payload. 1065 * The hiredis client lib does not understand this part of the protocol 1066 * and we don't want to mess with its buffers, so everything is performed 1067 * using direct low-level I/O. */ 1068 char buf[4096], *p; 1069 ssize_t nread; 1070 1071 /* Send the SYNC command. */ 1072 if (write(fd,"SYNC\r\n",6) != 6) { 1073 fprintf(stderr,"Error writing to master\n"); 1074 exit(1); 1075 } 1076 1077 /* Read $<payload>\r\n, making sure to read just up to "\n" */ 1078 p = buf; 1079 while(1) { 1080 nread = read(fd,p,1); 1081 if (nread <= 0) { 1082 fprintf(stderr,"Error reading bulk length while SYNCing\n"); 1083 exit(1); 1084 } 1085 if (*p == '\n' && p != buf) break; 1086 if (*p != '\n') p++; 1087 } 1088 *p = '\0'; 1089 if (buf[0] == '-') { 1090 printf("SYNC with master failed: %s\n", buf); 1091 exit(1); 1092 } 1093 return strtoull(buf+1,NULL,10); 1094 } 1095 1096 static void slaveMode(void) { 1097 int fd = context->fd; 1098 unsigned long long payload = sendSync(fd); 1099 char buf[1024]; 1100 int original_output = config.output; 1101 1102 fprintf(stderr,"SYNC with master, discarding %llu " 1103 "bytes of bulk transfer...\n", payload); 1104 1105 /* Discard the payload. */ 1106 while(payload) { 1107 ssize_t nread; 1108 1109 nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); 1110 if (nread <= 0) { 1111 fprintf(stderr,"Error reading RDB payload while SYNCing\n"); 1112 exit(1); 1113 } 1114 payload -= nread; 1115 } 1116 fprintf(stderr,"SYNC done. Logging commands from master.\n"); 1117 1118 /* Now we can use hiredis to read the incoming protocol. */ 1119 config.output = OUTPUT_CSV; 1120 while (cliReadReply(0) == REDIS_OK); 1121 config.output = original_output; 1122 } 1123 1124 /*------------------------------------------------------------------------------ 1125 * RDB transfer mode 1126 *--------------------------------------------------------------------------- */ 1127 1128 /* This function implements --rdb, so it uses the replication protocol in order 1129 * to fetch the RDB file from a remote server. */ 1130 static void getRDB(void) { 1131 int s = context->fd; 1132 int fd; 1133 unsigned long long payload = sendSync(s); 1134 char buf[4096]; 1135 1136 fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n", 1137 payload, config.rdb_filename); 1138 1139 /* Write to file. */ 1140 if (!strcmp(config.rdb_filename,"-")) { 1141 fd = STDOUT_FILENO; 1142 } else { 1143 fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644); 1144 if (fd == -1) { 1145 fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename, 1146 strerror(errno)); 1147 exit(1); 1148 } 1149 } 1150 1151 while(payload) { 1152 ssize_t nread, nwritten; 1153 1154 nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); 1155 if (nread <= 0) { 1156 fprintf(stderr,"I/O Error reading RDB payload from socket\n"); 1157 exit(1); 1158 } 1159 nwritten = write(fd, buf, nread); 1160 if (nwritten != nread) { 1161 fprintf(stderr,"Error writing data to file: %s\n", 1162 strerror(errno)); 1163 exit(1); 1164 } 1165 payload -= nread; 1166 } 1167 close(s); /* Close the file descriptor ASAP as fsync() may take time. */ 1168 fsync(fd); 1169 fprintf(stderr,"Transfer finished with success.\n"); 1170 exit(0); 1171 } 1172 1173 /*------------------------------------------------------------------------------ 1174 * Bulk import (pipe) mode 1175 *--------------------------------------------------------------------------- */ 1176 1177 static void pipeMode(void) { 1178 int fd = context->fd; 1179 long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0; 1180 char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */ 1181 char aneterr[ANET_ERR_LEN]; 1182 redisReader *reader = redisReaderCreate(); 1183 redisReply *reply; 1184 int eof = 0; /* True once we consumed all the standard input. */ 1185 int done = 0; 1186 char magic[20]; /* Special reply we recognize. */ 1187 time_t last_read_time = time(NULL); 1188 1189 srand(time(NULL)); 1190 1191 /* Use non blocking I/O. */ 1192 if (anetNonBlock(aneterr,fd) == ANET_ERR) { 1193 fprintf(stderr, "Can't set the socket in non blocking mode: %s\n", 1194 aneterr); 1195 exit(1); 1196 } 1197 1198 /* Transfer raw protocol and read replies from the server at the same 1199 * time. */ 1200 while(!done) { 1201 int mask = AE_READABLE; 1202 1203 if (!eof || obuf_len != 0) mask |= AE_WRITABLE; 1204 mask = aeWait(fd,mask,1000); 1205 1206 /* Handle the readable state: we can read replies from the server. */ 1207 if (mask & AE_READABLE) { 1208 ssize_t nread; 1209 1210 /* Read from socket and feed the hiredis reader. */ 1211 do { 1212 nread = read(fd,ibuf,sizeof(ibuf)); 1213 if (nread == -1 && errno != EAGAIN && errno != EINTR) { 1214 fprintf(stderr, "Error reading from the server: %s\n", 1215 strerror(errno)); 1216 exit(1); 1217 } 1218 if (nread > 0) { 1219 redisReaderFeed(reader,ibuf,nread); 1220 last_read_time = time(NULL); 1221 } 1222 } while(nread > 0); 1223 1224 /* Consume replies. */ 1225 do { 1226 if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) { 1227 fprintf(stderr, "Error reading replies from server\n"); 1228 exit(1); 1229 } 1230 if (reply) { 1231 if (reply->type == REDIS_REPLY_ERROR) { 1232 fprintf(stderr,"%s\n", reply->str); 1233 errors++; 1234 } else if (eof && reply->type == REDIS_REPLY_STRING && 1235 reply->len == 20) { 1236 /* Check if this is the reply to our final ECHO 1237 * command. If so everything was received 1238 * from the server. */ 1239 if (memcmp(reply->str,magic,20) == 0) { 1240 printf("Last reply received from server.\n"); 1241 done = 1; 1242 replies--; 1243 } 1244 } 1245 replies++; 1246 freeReplyObject(reply); 1247 } 1248 } while(reply); 1249 } 1250 1251 /* Handle the writable state: we can send protocol to the server. */ 1252 if (mask & AE_WRITABLE) { 1253 while(1) { 1254 /* Transfer current buffer to server. */ 1255 if (obuf_len != 0) { 1256 ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len); 1257 1258 if (nwritten == -1) { 1259 if (errno != EAGAIN && errno != EINTR) { 1260 fprintf(stderr, "Error writing to the server: %s\n", 1261 strerror(errno)); 1262 exit(1); 1263 } else { 1264 nwritten = 0; 1265 } 1266 } 1267 obuf_len -= nwritten; 1268 obuf_pos += nwritten; 1269 if (obuf_len != 0) break; /* Can't accept more data. */ 1270 } 1271 /* If buffer is empty, load from stdin. */ 1272 if (obuf_len == 0 && !eof) { 1273 ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf)); 1274 1275 if (nread == 0) { 1276 /* The ECHO sequence starts with a "\r\n" so that if there 1277 * is garbage in the protocol we read from stdin, the ECHO 1278 * will likely still be properly formatted. 1279 * CRLF is ignored by Redis, so it has no effects. */ 1280 char echo[] = 1281 "\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n"; 1282 int j; 1283 1284 eof = 1; 1285 /* Everything transferred, so we queue a special 1286 * ECHO command that we can match in the replies 1287 * to make sure everything was read from the server. */ 1288 for (j = 0; j < 20; j++) 1289 magic[j] = rand() & 0xff; 1290 memcpy(echo+21,magic,20); 1291 memcpy(obuf,echo,sizeof(echo)-1); 1292 obuf_len = sizeof(echo)-1; 1293 obuf_pos = 0; 1294 printf("All data transferred. Waiting for the last reply...\n"); 1295 } else if (nread == -1) { 1296 fprintf(stderr, "Error reading from stdin: %s\n", 1297 strerror(errno)); 1298 exit(1); 1299 } else { 1300 obuf_len = nread; 1301 obuf_pos = 0; 1302 } 1303 } 1304 if (obuf_len == 0 && eof) break; 1305 } 1306 } 1307 1308 /* Handle timeout, that is, we reached EOF, and we are not getting 1309 * replies from the server for a few seconds, nor the final ECHO is 1310 * received. */ 1311 if (eof && config.pipe_timeout > 0 && 1312 time(NULL)-last_read_time > config.pipe_timeout) 1313 { 1314 fprintf(stderr,"No replies for %d seconds: exiting.\n", 1315 config.pipe_timeout); 1316 errors++; 1317 break; 1318 } 1319 } 1320 redisReaderFree(reader); 1321 printf("errors: %lld, replies: %lld\n", errors, replies); 1322 if (errors) 1323 exit(1); 1324 else 1325 exit(0); 1326 } 1327 1328 /*------------------------------------------------------------------------------ 1329 * Find big keys 1330 *--------------------------------------------------------------------------- */ 1331 1332 #define TYPE_STRING 0 1333 #define TYPE_LIST 1 1334 #define TYPE_SET 2 1335 #define TYPE_HASH 3 1336 #define TYPE_ZSET 4 1337 #define TYPE_NONE 5 1338 1339 static redisReply *sendScan(unsigned long long *it) { 1340 redisReply *reply = redisCommand(context, "SCAN %llu", *it); 1341 1342 /* Handle any error conditions */ 1343 if(reply == NULL) { 1344 fprintf(stderr, "\nI/O error\n"); 1345 exit(1); 1346 } else if(reply->type == REDIS_REPLY_ERROR) { 1347 fprintf(stderr, "SCAN error: %s\n", reply->str); 1348 exit(1); 1349 } else if(reply->type != REDIS_REPLY_ARRAY) { 1350 fprintf(stderr, "Non ARRAY response from SCAN!\n"); 1351 exit(1); 1352 } else if(reply->elements != 2) { 1353 fprintf(stderr, "Invalid element count from SCAN!\n"); 1354 exit(1); 1355 } 1356 1357 /* Validate our types are correct */ 1358 assert(reply->element[0]->type == REDIS_REPLY_STRING); 1359 assert(reply->element[1]->type == REDIS_REPLY_ARRAY); 1360 1361 /* Update iterator */ 1362 *it = atoi(reply->element[0]->str); 1363 1364 return reply; 1365 } 1366 1367 static int getDbSize(void) { 1368 redisReply *reply; 1369 int size; 1370 1371 reply = redisCommand(context, "DBSIZE"); 1372 1373 if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) { 1374 fprintf(stderr, "Couldn't determine DBSIZE!\n"); 1375 exit(1); 1376 } 1377 1378 /* Grab the number of keys and free our reply */ 1379 size = reply->integer; 1380 freeReplyObject(reply); 1381 1382 return size; 1383 } 1384 1385 static int toIntType(char *key, char *type) { 1386 if(!strcmp(type, "string")) { 1387 return TYPE_STRING; 1388 } else if(!strcmp(type, "list")) { 1389 return TYPE_LIST; 1390 } else if(!strcmp(type, "set")) { 1391 return TYPE_SET; 1392 } else if(!strcmp(type, "hash")) { 1393 return TYPE_HASH; 1394 } else if(!strcmp(type, "zset")) { 1395 return TYPE_ZSET; 1396 } else if(!strcmp(type, "none")) { 1397 return TYPE_NONE; 1398 } else { 1399 fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key); 1400 exit(1); 1401 } 1402 } 1403 1404 static void getKeyTypes(redisReply *keys, int *types) { 1405 redisReply *reply; 1406 int i; 1407 1408 /* Pipeline TYPE commands */ 1409 for(i=0;i<keys->elements;i++) { 1410 redisAppendCommand(context, "TYPE %s", keys->element[i]->str); 1411 } 1412 1413 /* Retrieve types */ 1414 for(i=0;i<keys->elements;i++) { 1415 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) { 1416 fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n", 1417 keys->element[i]->str, context->err, context->errstr); 1418 exit(1); 1419 } else if(reply->type != REDIS_REPLY_STATUS) { 1420 fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n", 1421 reply->type, keys->element[i]->str); 1422 exit(1); 1423 } 1424 1425 types[i] = toIntType(keys->element[i]->str, reply->str); 1426 freeReplyObject(reply); 1427 } 1428 } 1429 1430 static void getKeySizes(redisReply *keys, int *types, 1431 unsigned long long *sizes) 1432 { 1433 redisReply *reply; 1434 char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"}; 1435 int i; 1436 1437 /* Pipeline size commands */ 1438 for(i=0;i<keys->elements;i++) { 1439 /* Skip keys that were deleted */ 1440 if(types[i]==TYPE_NONE) 1441 continue; 1442 1443 redisAppendCommand(context, "%s %s", sizecmds[types[i]], 1444 keys->element[i]->str); 1445 } 1446 1447 /* Retreive sizes */ 1448 for(i=0;i<keys->elements;i++) { 1449 /* Skip keys that dissapeared between SCAN and TYPE */ 1450 if(types[i] == TYPE_NONE) { 1451 sizes[i] = 0; 1452 continue; 1453 } 1454 1455 /* Retreive size */ 1456 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) { 1457 fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n", 1458 keys->element[i]->str, context->err, context->errstr); 1459 exit(1); 1460 } else if(reply->type != REDIS_REPLY_INTEGER) { 1461 /* Theoretically the key could have been removed and 1462 * added as a different type between TYPE and SIZE */ 1463 fprintf(stderr, 1464 "Warning: %s on '%s' failed (may have changed type)\n", 1465 sizecmds[types[i]], keys->element[i]->str); 1466 sizes[i] = 0; 1467 } else { 1468 sizes[i] = reply->integer; 1469 } 1470 1471 freeReplyObject(reply); 1472 } 1473 } 1474 1475 static void findBigKeys(void) { 1476 unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0}; 1477 unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0; 1478 sds maxkeys[5] = {0}; 1479 char *typename[] = {"string","list","set","hash","zset"}; 1480 char *typeunit[] = {"bytes","items","members","fields","members"}; 1481 redisReply *reply, *keys; 1482 int type, *types=NULL, arrsize=0, i; 1483 double pct; 1484 1485 /* Total keys pre scanning */ 1486 total_keys = getDbSize(); 1487 1488 /* Status message */ 1489 printf("\n# Scanning the entire keyspace to find biggest keys as well as\n"); 1490 printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n"); 1491 printf("# per 100 SCAN commands (not usually needed).\n\n"); 1492 1493 /* New up sds strings to keep track of overall biggest per type */ 1494 for(i=0;i<TYPE_NONE; i++) { 1495 maxkeys[i] = sdsempty(); 1496 if(!maxkeys[i]) { 1497 fprintf(stderr, "Failed to allocate memory for largest key names!"); 1498 exit(1); 1499 } 1500 } 1501 1502 /* SCAN loop */ 1503 do { 1504 /* Calculate approximate percentage completion */ 1505 pct = 100 * (double)sampled/total_keys; 1506 1507 /* Grab some keys and point to the keys array */ 1508 reply = sendScan(&it); 1509 keys = reply->element[1]; 1510 1511 /* Reallocate our type and size array if we need to */ 1512 if(keys->elements > arrsize) { 1513 types = zrealloc(types, sizeof(int)*keys->elements); 1514 sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements); 1515 1516 if(!types || !sizes) { 1517 fprintf(stderr, "Failed to allocate storage for keys!\n"); 1518 exit(1); 1519 } 1520 1521 arrsize = keys->elements; 1522 } 1523 1524 /* Retreive types and then sizes */ 1525 getKeyTypes(keys, types); 1526 getKeySizes(keys, types, sizes); 1527 1528 /* Now update our stats */ 1529 for(i=0;i<keys->elements;i++) { 1530 if((type = types[i]) == TYPE_NONE) 1531 continue; 1532 1533 totalsize[type] += sizes[i]; 1534 counts[type]++; 1535 totlen += keys->element[i]->len; 1536 sampled++; 1537 1538 if(biggest[type]<sizes[i]) { 1539 printf( 1540 "[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n", 1541 pct, typename[type], keys->element[i]->str, sizes[i], 1542 typeunit[type]); 1543 1544 /* Keep track of biggest key name for this type */ 1545 maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str); 1546 if(!maxkeys[type]) { 1547 fprintf(stderr, "Failed to allocate memory for key!\n"); 1548 exit(1); 1549 } 1550 1551 /* Keep track of the biggest size for this type */ 1552 biggest[type] = sizes[i]; 1553 } 1554 1555 /* Update overall progress */ 1556 if(sampled % 1000000 == 0) { 1557 printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled); 1558 } 1559 } 1560 1561 /* Sleep if we've been directed to do so */ 1562 if(sampled && (sampled %100) == 0 && config.interval) { 1563 usleep(config.interval); 1564 } 1565 1566 freeReplyObject(reply); 1567 } while(it != 0); 1568 1569 if(types) zfree(types); 1570 if(sizes) zfree(sizes); 1571 1572 /* We're done */ 1573 printf("\n-------- summary -------\n\n"); 1574 1575 printf("Sampled %llu keys in the keyspace!\n", sampled); 1576 printf("Total key length in bytes is %llu (avg len %.2f)\n\n", 1577 totlen, totlen ? (double)totlen/sampled : 0); 1578 1579 /* Output the biggest keys we found, for types we did find */ 1580 for(i=0;i<TYPE_NONE;i++) { 1581 if(sdslen(maxkeys[i])>0) { 1582 printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i], 1583 biggest[i], typeunit[i]); 1584 } 1585 } 1586 1587 printf("\n"); 1588 1589 for(i=0;i<TYPE_NONE;i++) { 1590 printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n", 1591 counts[i], typename[i], totalsize[i], typeunit[i], 1592 sampled ? 100 * (double)counts[i]/sampled : 0, 1593 counts[i] ? (double)totalsize[i]/counts[i] : 0); 1594 } 1595 1596 /* Free sds strings containing max keys */ 1597 for(i=0;i<TYPE_NONE;i++) { 1598 sdsfree(maxkeys[i]); 1599 } 1600 1601 /* Success! */ 1602 exit(0); 1603 } 1604 1605 /*------------------------------------------------------------------------------ 1606 * Stats mode 1607 *--------------------------------------------------------------------------- */ 1608 1609 /* Return the specified INFO field from the INFO command output "info". 1610 * A new buffer is allocated for the result, that needs to be free'd. 1611 * If the field is not found NULL is returned. */ 1612 static char *getInfoField(char *info, char *field) { 1613 char *p = strstr(info,field); 1614 char *n1, *n2; 1615 char *result; 1616 1617 if (!p) return NULL; 1618 p += strlen(field)+1; 1619 n1 = strchr(p,'\r'); 1620 n2 = strchr(p,','); 1621 if (n2 && n2 < n1) n1 = n2; 1622 result = malloc(sizeof(char)*(n1-p)+1); 1623 memcpy(result,p,(n1-p)); 1624 result[n1-p] = '\0'; 1625 return result; 1626 } 1627 1628 /* Like the above function but automatically convert the result into 1629 * a long. On error (missing field) LONG_MIN is returned. */ 1630 static long getLongInfoField(char *info, char *field) { 1631 char *value = getInfoField(info,field); 1632 long l; 1633 1634 if (!value) return LONG_MIN; 1635 l = strtol(value,NULL,10); 1636 free(value); 1637 return l; 1638 } 1639 1640 /* Convert number of bytes into a human readable string of the form: 1641 * 100B, 2G, 100M, 4K, and so forth. */ 1642 void bytesToHuman(char *s, long long n) { 1643 double d; 1644 1645 if (n < 0) { 1646 *s = '-'; 1647 s++; 1648 n = -n; 1649 } 1650 if (n < 1024) { 1651 /* Bytes */ 1652 sprintf(s,"%lluB",n); 1653 return; 1654 } else if (n < (1024*1024)) { 1655 d = (double)n/(1024); 1656 sprintf(s,"%.2fK",d); 1657 } else if (n < (1024LL*1024*1024)) { 1658 d = (double)n/(1024*1024); 1659 sprintf(s,"%.2fM",d); 1660 } else if (n < (1024LL*1024*1024*1024)) { 1661 d = (double)n/(1024LL*1024*1024); 1662 sprintf(s,"%.2fG",d); 1663 } 1664 } 1665 1666 static void statMode() { 1667 redisReply *reply; 1668 long aux, requests = 0; 1669 int i = 0; 1670 1671 while(1) { 1672 char buf[64]; 1673 int j; 1674 1675 reply = reconnectingInfo(); 1676 if (reply->type == REDIS_REPLY_ERROR) { 1677 printf("ERROR: %s\n", reply->str); 1678 exit(1); 1679 } 1680 1681 if ((i++ % 20) == 0) { 1682 printf( 1683 "------- data ------ --------------------- load -------------------- - child -\n" 1684 "keys mem clients blocked requests connections \n"); 1685 } 1686 1687 /* Keys */ 1688 aux = 0; 1689 for (j = 0; j < 20; j++) { 1690 long k; 1691 1692 sprintf(buf,"db%d:keys",j); 1693 k = getLongInfoField(reply->str,buf); 1694 if (k == LONG_MIN) continue; 1695 aux += k; 1696 } 1697 sprintf(buf,"%ld",aux); 1698 printf("%-11s",buf); 1699 1700 /* Used memory */ 1701 aux = getLongInfoField(reply->str,"used_memory"); 1702 bytesToHuman(buf,aux); 1703 printf("%-8s",buf); 1704 1705 /* Clients */ 1706 aux = getLongInfoField(reply->str,"connected_clients"); 1707 sprintf(buf,"%ld",aux); 1708 printf(" %-8s",buf); 1709 1710 /* Blocked (BLPOPPING) Clients */ 1711 aux = getLongInfoField(reply->str,"blocked_clients"); 1712 sprintf(buf,"%ld",aux); 1713 printf("%-8s",buf); 1714 1715 /* Requets */ 1716 aux = getLongInfoField(reply->str,"total_commands_processed"); 1717 sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests); 1718 printf("%-19s",buf); 1719 requests = aux; 1720 1721 /* Connections */ 1722 aux = getLongInfoField(reply->str,"total_connections_received"); 1723 sprintf(buf,"%ld",aux); 1724 printf(" %-12s",buf); 1725 1726 /* Children */ 1727 aux = getLongInfoField(reply->str,"bgsave_in_progress"); 1728 aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1; 1729 switch(aux) { 1730 case 0: break; 1731 case 1: 1732 printf("SAVE"); 1733 break; 1734 case 2: 1735 printf("AOF"); 1736 break; 1737 case 3: 1738 printf("SAVE+AOF"); 1739 break; 1740 } 1741 1742 printf("\n"); 1743 freeReplyObject(reply); 1744 usleep(config.interval); 1745 } 1746 } 1747 1748 /*------------------------------------------------------------------------------ 1749 * Scan mode 1750 *--------------------------------------------------------------------------- */ 1751 1752 static void scanMode() { 1753 redisReply *reply; 1754 unsigned long long cur = 0; 1755 1756 do { 1757 if (config.pattern) 1758 reply = redisCommand(context,"SCAN %llu MATCH %s", 1759 cur,config.pattern); 1760 else 1761 reply = redisCommand(context,"SCAN %llu",cur); 1762 if (reply == NULL) { 1763 printf("I/O error\n"); 1764 exit(1); 1765 } else if (reply->type == REDIS_REPLY_ERROR) { 1766 printf("ERROR: %s\n", reply->str); 1767 exit(1); 1768 } else { 1769 int j; 1770 1771 cur = strtoull(reply->element[0]->str,NULL,10); 1772 for (j = 0; j < reply->element[1]->elements; j++) 1773 printf("%s\n", reply->element[1]->element[j]->str); 1774 } 1775 freeReplyObject(reply); 1776 } while(cur != 0); 1777 1778 exit(0); 1779 } 1780 1781 /*------------------------------------------------------------------------------ 1782 * Intrisic latency mode. 1783 * 1784 * Measure max latency of a running process that does not result from 1785 * syscalls. Basically this software should provide an hint about how much 1786 * time the kernel leaves the process without a chance to run. 1787 *--------------------------------------------------------------------------- */ 1788 1789 /* This is just some computation the compiler can't optimize out. 1790 * Should run in less than 100-200 microseconds even using very 1791 * slow hardware. Runs in less than 10 microseconds in modern HW. */ 1792 unsigned long compute_something_fast(void) { 1793 unsigned char s[256], i, j, t; 1794 int count = 1000, k; 1795 unsigned long output = 0; 1796 1797 for (k = 0; k < 256; k++) s[k] = k; 1798 1799 i = 0; 1800 j = 0; 1801 while(count--) { 1802 i++; 1803 j = j + s[i]; 1804 t = s[i]; 1805 s[i] = s[j]; 1806 s[j] = t; 1807 output += s[(s[i]+s[j])&255]; 1808 } 1809 return output; 1810 } 1811 1812 static void intrinsicLatencyModeStop(int s) { 1813 REDIS_NOTUSED(s); 1814 force_cancel_loop = 1; 1815 } 1816 1817 static void intrinsicLatencyMode(void) { 1818 long long test_end, run_time, max_latency = 0, runs = 0; 1819 1820 run_time = config.intrinsic_latency_duration*1000000; 1821 test_end = ustime() + run_time; 1822 signal(SIGINT, intrinsicLatencyModeStop); 1823 1824 while(1) { 1825 long long start, end, latency; 1826 1827 start = ustime(); 1828 compute_something_fast(); 1829 end = ustime(); 1830 latency = end-start; 1831 runs++; 1832 if (latency <= 0) continue; 1833 1834 /* Reporting */ 1835 if (latency > max_latency) { 1836 max_latency = latency; 1837 printf("Max latency so far: %lld microseconds.\n", max_latency); 1838 } 1839 1840 if (force_cancel_loop || end > test_end) { 1841 printf("\n%lld total runs (avg %lld microseconds per run).\n", 1842 runs, run_time/runs); 1843 printf("Worst run took %.02fx times the average.\n", 1844 (double) max_latency / (run_time/runs)); 1845 exit(0); 1846 } 1847 } 1848 } 1849 1850 /*------------------------------------------------------------------------------ 1851 * Program main() 1852 *--------------------------------------------------------------------------- */ 1853 1854 int main(int argc, char **argv) { 1855 int firstarg; 1856 1857 config.hostip = sdsnew("127.0.0.1"); 1858 config.hostport = 6379; 1859 config.hostsocket = NULL; 1860 config.repeat = 1; 1861 config.interval = 0; 1862 config.dbnum = 0; 1863 config.interactive = 0; 1864 config.shutdown = 0; 1865 config.monitor_mode = 0; 1866 config.pubsub_mode = 0; 1867 config.latency_mode = 0; 1868 config.latency_history = 0; 1869 config.cluster_mode = 0; 1870 config.slave_mode = 0; 1871 config.getrdb_mode = 0; 1872 config.stat_mode = 0; 1873 config.scan_mode = 0; 1874 config.intrinsic_latency_mode = 0; 1875 config.pattern = NULL; 1876 config.rdb_filename = NULL; 1877 config.pipe_mode = 0; 1878 config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT; 1879 config.bigkeys = 0; 1880 config.stdinarg = 0; 1881 config.auth = NULL; 1882 config.eval = NULL; 1883 if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL)) 1884 config.output = OUTPUT_RAW; 1885 else 1886 config.output = OUTPUT_STANDARD; 1887 config.mb_delim = sdsnew("\n"); 1888 cliInitHelp(); 1889 1890 firstarg = parseOptions(argc,argv); 1891 argc -= firstarg; 1892 argv += firstarg; 1893 1894 /* Latency mode */ 1895 if (config.latency_mode) { 1896 if (cliConnect(0) == REDIS_ERR) exit(1); 1897 latencyMode(); 1898 } 1899 1900 /* Slave mode */ 1901 if (config.slave_mode) { 1902 if (cliConnect(0) == REDIS_ERR) exit(1); 1903 slaveMode(); 1904 } 1905 1906 /* Get RDB mode. */ 1907 if (config.getrdb_mode) { 1908 if (cliConnect(0) == REDIS_ERR) exit(1); 1909 getRDB(); 1910 } 1911 1912 /* Pipe mode */ 1913 if (config.pipe_mode) { 1914 if (cliConnect(0) == REDIS_ERR) exit(1); 1915 pipeMode(); 1916 } 1917 1918 /* Find big keys */ 1919 if (config.bigkeys) { 1920 if (cliConnect(0) == REDIS_ERR) exit(1); 1921 findBigKeys(); 1922 } 1923 1924 /* Stat mode */ 1925 if (config.stat_mode) { 1926 if (cliConnect(0) == REDIS_ERR) exit(1); 1927 if (config.interval == 0) config.interval = 1000000; 1928 statMode(); 1929 } 1930 1931 /* Scan mode */ 1932 if (config.scan_mode) { 1933 if (cliConnect(0) == REDIS_ERR) exit(1); 1934 scanMode(); 1935 } 1936 1937 /* Intrinsic latency mode */ 1938 if (config.intrinsic_latency_mode) intrinsicLatencyMode(); 1939 1940 /* Start interactive mode when no command is provided */ 1941 if (argc == 0 && !config.eval) { 1942 /* Note that in repl mode we don't abort on connection error. 1943 * A new attempt will be performed for every command send. */ 1944 cliConnect(0); 1945 repl(); 1946 } 1947 1948 /* Otherwise, we have some arguments to execute */ 1949 if (cliConnect(0) != REDIS_OK) exit(1); 1950 if (config.eval) { 1951 return evalMode(argc,argv); 1952 } else { 1953 return noninteractive(argc,convertToSds(argc,argv)); 1954 } 1955 } 1956