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