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 62 static redisContext *context; 63 static struct config { 64 char *hostip; 65 int hostport; 66 char *hostsocket; 67 long repeat; 68 long interval; 69 int dbnum; 70 int interactive; 71 int shutdown; 72 int monitor_mode; 73 int pubsub_mode; 74 int latency_mode; 75 int cluster_mode; 76 int cluster_reissue_command; 77 int slave_mode; 78 int pipe_mode; 79 int getrdb_mode; 80 int stat_mode; 81 char *rdb_filename; 82 int bigkeys; 83 int stdinarg; /* get last arg from stdin. (-x option) */ 84 char *auth; 85 int output; /* output mode, see OUTPUT_* defines */ 86 sds mb_delim; 87 char prompt[128]; 88 char *eval; 89 } config; 90 91 static void usage(); 92 char *redisGitSHA1(void); 93 char *redisGitDirty(void); 94 95 /*------------------------------------------------------------------------------ 96 * Utility functions 97 *--------------------------------------------------------------------------- */ 98 99 static long long mstime(void) { 100 struct timeval tv; 101 long long mst; 102 103 gettimeofday(&tv, NULL); 104 mst = ((long long)tv.tv_sec)*1000; 105 mst += tv.tv_usec/1000; 106 return mst; 107 } 108 109 static void cliRefreshPrompt(void) { 110 int len; 111 112 if (config.hostsocket != NULL) 113 len = snprintf(config.prompt,sizeof(config.prompt),"redis %s", 114 config.hostsocket); 115 else 116 len = snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d", 117 config.hostip, config.hostport); 118 /* Add [dbnum] if needed */ 119 if (config.dbnum != 0) 120 len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]", 121 config.dbnum); 122 snprintf(config.prompt+len,sizeof(config.prompt)-len,"> "); 123 } 124 125 /*------------------------------------------------------------------------------ 126 * Help functions 127 *--------------------------------------------------------------------------- */ 128 129 #define CLI_HELP_COMMAND 1 130 #define CLI_HELP_GROUP 2 131 132 typedef struct { 133 int type; 134 int argc; 135 sds *argv; 136 sds full; 137 138 /* Only used for help on commands */ 139 struct commandHelp *org; 140 } helpEntry; 141 142 static helpEntry *helpEntries; 143 static int helpEntriesLen; 144 145 static sds cliVersion() { 146 sds version; 147 version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); 148 149 /* Add git commit and working tree status when available */ 150 if (strtoll(redisGitSHA1(),NULL,16)) { 151 version = sdscatprintf(version, " (git:%s", redisGitSHA1()); 152 if (strtoll(redisGitDirty(),NULL,10)) 153 version = sdscatprintf(version, "-dirty"); 154 version = sdscat(version, ")"); 155 } 156 return version; 157 } 158 159 static void cliInitHelp() { 160 int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp); 161 int groupslen = sizeof(commandGroups)/sizeof(char*); 162 int i, len, pos = 0; 163 helpEntry tmp; 164 165 helpEntriesLen = len = commandslen+groupslen; 166 helpEntries = malloc(sizeof(helpEntry)*len); 167 168 for (i = 0; i < groupslen; i++) { 169 tmp.argc = 1; 170 tmp.argv = malloc(sizeof(sds)); 171 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]); 172 tmp.full = tmp.argv[0]; 173 tmp.type = CLI_HELP_GROUP; 174 tmp.org = NULL; 175 helpEntries[pos++] = tmp; 176 } 177 178 for (i = 0; i < commandslen; i++) { 179 tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc); 180 tmp.full = sdsnew(commandHelp[i].name); 181 tmp.type = CLI_HELP_COMMAND; 182 tmp.org = &commandHelp[i]; 183 helpEntries[pos++] = tmp; 184 } 185 } 186 187 /* Output command help to stdout. */ 188 static void cliOutputCommandHelp(struct commandHelp *help, int group) { 189 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params); 190 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary); 191 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since); 192 if (group) { 193 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]); 194 } 195 } 196 197 /* Print generic help. */ 198 static void cliOutputGenericHelp() { 199 sds version = cliVersion(); 200 printf( 201 "redis-cli %s\r\n" 202 "Type: \"help @<group>\" to get a list of commands in <group>\r\n" 203 " \"help <command>\" for help on <command>\r\n" 204 " \"help <tab>\" to get a list of possible help topics\r\n" 205 " \"quit\" to exit\r\n", 206 version 207 ); 208 sdsfree(version); 209 } 210 211 /* Output all command help, filtering by group or command name. */ 212 static void cliOutputHelp(int argc, char **argv) { 213 int i, j, len; 214 int group = -1; 215 helpEntry *entry; 216 struct commandHelp *help; 217 218 if (argc == 0) { 219 cliOutputGenericHelp(); 220 return; 221 } else if (argc > 0 && argv[0][0] == '@') { 222 len = sizeof(commandGroups)/sizeof(char*); 223 for (i = 0; i < len; i++) { 224 if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) { 225 group = i; 226 break; 227 } 228 } 229 } 230 231 assert(argc > 0); 232 for (i = 0; i < helpEntriesLen; i++) { 233 entry = &helpEntries[i]; 234 if (entry->type != CLI_HELP_COMMAND) continue; 235 236 help = entry->org; 237 if (group == -1) { 238 /* Compare all arguments */ 239 if (argc == entry->argc) { 240 for (j = 0; j < argc; j++) { 241 if (strcasecmp(argv[j],entry->argv[j]) != 0) break; 242 } 243 if (j == argc) { 244 cliOutputCommandHelp(help,1); 245 } 246 } 247 } else { 248 if (group == help->group) { 249 cliOutputCommandHelp(help,0); 250 } 251 } 252 } 253 printf("\r\n"); 254 } 255 256 static void completionCallback(const char *buf, linenoiseCompletions *lc) { 257 size_t startpos = 0; 258 int mask; 259 int i; 260 size_t matchlen; 261 sds tmp; 262 263 if (strncasecmp(buf,"help ",5) == 0) { 264 startpos = 5; 265 while (isspace(buf[startpos])) startpos++; 266 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP; 267 } else { 268 mask = CLI_HELP_COMMAND; 269 } 270 271 for (i = 0; i < helpEntriesLen; i++) { 272 if (!(helpEntries[i].type & mask)) continue; 273 274 matchlen = strlen(buf+startpos); 275 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) { 276 tmp = sdsnewlen(buf,startpos); 277 tmp = sdscat(tmp,helpEntries[i].full); 278 linenoiseAddCompletion(lc,tmp); 279 sdsfree(tmp); 280 } 281 } 282 } 283 284 /*------------------------------------------------------------------------------ 285 * Networking / parsing 286 *--------------------------------------------------------------------------- */ 287 288 /* Send AUTH command to the server */ 289 static int cliAuth() { 290 redisReply *reply; 291 if (config.auth == NULL) return REDIS_OK; 292 293 reply = redisCommand(context,"AUTH %s",config.auth); 294 if (reply != NULL) { 295 freeReplyObject(reply); 296 return REDIS_OK; 297 } 298 return REDIS_ERR; 299 } 300 301 /* Send SELECT dbnum to the server */ 302 static int cliSelect() { 303 redisReply *reply; 304 if (config.dbnum == 0) return REDIS_OK; 305 306 reply = redisCommand(context,"SELECT %d",config.dbnum); 307 if (reply != NULL) { 308 freeReplyObject(reply); 309 return REDIS_OK; 310 } 311 return REDIS_ERR; 312 } 313 314 /* Connect to the server. If force is not zero the connection is performed 315 * even if there is already a connected socket. */ 316 static int cliConnect(int force) { 317 if (context == NULL || force) { 318 if (context != NULL) 319 redisFree(context); 320 321 if (config.hostsocket == NULL) { 322 context = redisConnect(config.hostip,config.hostport); 323 } else { 324 context = redisConnectUnix(config.hostsocket); 325 } 326 327 if (context->err) { 328 fprintf(stderr,"Could not connect to Redis at "); 329 if (config.hostsocket == NULL) 330 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr); 331 else 332 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr); 333 redisFree(context); 334 context = NULL; 335 return REDIS_ERR; 336 } 337 338 /* Set aggressive KEEP_ALIVE socket option in the Redis context socket 339 * in order to prevent timeouts caused by the execution of long 340 * commands. At the same time this improves the detection of real 341 * errors. */ 342 anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL); 343 344 /* Do AUTH and select the right DB. */ 345 if (cliAuth() != REDIS_OK) 346 return REDIS_ERR; 347 if (cliSelect() != REDIS_OK) 348 return REDIS_ERR; 349 } 350 return REDIS_OK; 351 } 352 353 static void cliPrintContextError() { 354 if (context == NULL) return; 355 fprintf(stderr,"Error: %s\n",context->errstr); 356 } 357 358 static sds cliFormatReplyTTY(redisReply *r, char *prefix) { 359 sds out = sdsempty(); 360 switch (r->type) { 361 case REDIS_REPLY_ERROR: 362 out = sdscatprintf(out,"(error) %s\n", r->str); 363 break; 364 case REDIS_REPLY_STATUS: 365 out = sdscat(out,r->str); 366 out = sdscat(out,"\n"); 367 break; 368 case REDIS_REPLY_INTEGER: 369 out = sdscatprintf(out,"(integer) %lld\n",r->integer); 370 break; 371 case REDIS_REPLY_STRING: 372 /* If you are producing output for the standard output we want 373 * a more interesting output with quoted characters and so forth */ 374 out = sdscatrepr(out,r->str,r->len); 375 out = sdscat(out,"\n"); 376 break; 377 case REDIS_REPLY_NIL: 378 out = sdscat(out,"(nil)\n"); 379 break; 380 case REDIS_REPLY_ARRAY: 381 if (r->elements == 0) { 382 out = sdscat(out,"(empty list or set)\n"); 383 } else { 384 unsigned int i, idxlen = 0; 385 char _prefixlen[16]; 386 char _prefixfmt[16]; 387 sds _prefix; 388 sds tmp; 389 390 /* Calculate chars needed to represent the largest index */ 391 i = r->elements; 392 do { 393 idxlen++; 394 i /= 10; 395 } while(i); 396 397 /* Prefix for nested multi bulks should grow with idxlen+2 spaces */ 398 memset(_prefixlen,' ',idxlen+2); 399 _prefixlen[idxlen+2] = '\0'; 400 _prefix = sdscat(sdsnew(prefix),_prefixlen); 401 402 /* Setup prefix format for every entry */ 403 snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen); 404 405 for (i = 0; i < r->elements; i++) { 406 /* Don't use the prefix for the first element, as the parent 407 * caller already prepended the index number. */ 408 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1); 409 410 /* Format the multi bulk entry */ 411 tmp = cliFormatReplyTTY(r->element[i],_prefix); 412 out = sdscatlen(out,tmp,sdslen(tmp)); 413 sdsfree(tmp); 414 } 415 sdsfree(_prefix); 416 } 417 break; 418 default: 419 fprintf(stderr,"Unknown reply type: %d\n", r->type); 420 exit(1); 421 } 422 return out; 423 } 424 425 static sds cliFormatReplyRaw(redisReply *r) { 426 sds out = sdsempty(), tmp; 427 size_t i; 428 429 switch (r->type) { 430 case REDIS_REPLY_NIL: 431 /* Nothing... */ 432 break; 433 case REDIS_REPLY_ERROR: 434 out = sdscatlen(out,r->str,r->len); 435 out = sdscatlen(out,"\n",1); 436 break; 437 case REDIS_REPLY_STATUS: 438 case REDIS_REPLY_STRING: 439 out = sdscatlen(out,r->str,r->len); 440 break; 441 case REDIS_REPLY_INTEGER: 442 out = sdscatprintf(out,"%lld",r->integer); 443 break; 444 case REDIS_REPLY_ARRAY: 445 for (i = 0; i < r->elements; i++) { 446 if (i > 0) out = sdscat(out,config.mb_delim); 447 tmp = cliFormatReplyRaw(r->element[i]); 448 out = sdscatlen(out,tmp,sdslen(tmp)); 449 sdsfree(tmp); 450 } 451 break; 452 default: 453 fprintf(stderr,"Unknown reply type: %d\n", r->type); 454 exit(1); 455 } 456 return out; 457 } 458 459 static sds cliFormatReplyCSV(redisReply *r) { 460 unsigned int i; 461 462 sds out = sdsempty(); 463 switch (r->type) { 464 case REDIS_REPLY_ERROR: 465 out = sdscat(out,"ERROR,"); 466 out = sdscatrepr(out,r->str,strlen(r->str)); 467 break; 468 case REDIS_REPLY_STATUS: 469 out = sdscatrepr(out,r->str,r->len); 470 break; 471 case REDIS_REPLY_INTEGER: 472 out = sdscatprintf(out,"%lld",r->integer); 473 break; 474 case REDIS_REPLY_STRING: 475 out = sdscatrepr(out,r->str,r->len); 476 break; 477 case REDIS_REPLY_NIL: 478 out = sdscat(out,"NIL\n"); 479 break; 480 case REDIS_REPLY_ARRAY: 481 for (i = 0; i < r->elements; i++) { 482 sds tmp = cliFormatReplyCSV(r->element[i]); 483 out = sdscatlen(out,tmp,sdslen(tmp)); 484 if (i != r->elements-1) out = sdscat(out,","); 485 sdsfree(tmp); 486 } 487 break; 488 default: 489 fprintf(stderr,"Unknown reply type: %d\n", r->type); 490 exit(1); 491 } 492 return out; 493 } 494 495 static int cliReadReply(int output_raw_strings) { 496 void *_reply; 497 redisReply *reply; 498 sds out = NULL; 499 int output = 1; 500 501 if (redisGetReply(context,&_reply) != REDIS_OK) { 502 if (config.shutdown) 503 return REDIS_OK; 504 if (config.interactive) { 505 /* Filter cases where we should reconnect */ 506 if (context->err == REDIS_ERR_IO && errno == ECONNRESET) 507 return REDIS_ERR; 508 if (context->err == REDIS_ERR_EOF) 509 return REDIS_ERR; 510 } 511 cliPrintContextError(); 512 exit(1); 513 return REDIS_ERR; /* avoid compiler warning */ 514 } 515 516 reply = (redisReply*)_reply; 517 518 /* Check if we need to connect to a different node and reissue the 519 * request. */ 520 if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR && 521 (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK"))) 522 { 523 char *p = reply->str, *s; 524 int slot; 525 526 output = 0; 527 /* Comments show the position of the pointer as: 528 * 529 * [S] for pointer 's' 530 * [P] for pointer 'p' 531 */ 532 s = strchr(p,' '); /* MOVED[S]3999 127.0.0.1:6381 */ 533 p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */ 534 *p = '\0'; 535 slot = atoi(s+1); 536 s = strchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */ 537 *s = '\0'; 538 sdsfree(config.hostip); 539 config.hostip = sdsnew(p+1); 540 config.hostport = atoi(s+1); 541 if (config.interactive) 542 printf("-> Redirected to slot [%d] located at %s:%d\n", 543 slot, config.hostip, config.hostport); 544 config.cluster_reissue_command = 1; 545 cliRefreshPrompt(); 546 } 547 548 if (output) { 549 if (output_raw_strings) { 550 out = cliFormatReplyRaw(reply); 551 } else { 552 if (config.output == OUTPUT_RAW) { 553 out = cliFormatReplyRaw(reply); 554 out = sdscat(out,"\n"); 555 } else if (config.output == OUTPUT_STANDARD) { 556 out = cliFormatReplyTTY(reply,""); 557 } else if (config.output == OUTPUT_CSV) { 558 out = cliFormatReplyCSV(reply); 559 out = sdscat(out,"\n"); 560 } 561 } 562 fwrite(out,sdslen(out),1,stdout); 563 sdsfree(out); 564 } 565 freeReplyObject(reply); 566 return REDIS_OK; 567 } 568 569 static int cliSendCommand(int argc, char **argv, int repeat) { 570 char *command = argv[0]; 571 size_t *argvlen; 572 int j, output_raw; 573 574 if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { 575 cliOutputHelp(--argc, ++argv); 576 return REDIS_OK; 577 } 578 579 if (context == NULL) return REDIS_ERR; 580 581 output_raw = 0; 582 if (!strcasecmp(command,"info") || 583 (argc == 2 && !strcasecmp(command,"cluster") && 584 (!strcasecmp(argv[1],"nodes") || 585 !strcasecmp(argv[1],"info"))) || 586 (argc == 2 && !strcasecmp(command,"client") && 587 !strcasecmp(argv[1],"list"))) 588 589 { 590 output_raw = 1; 591 } 592 593 if (!strcasecmp(command,"shutdown")) config.shutdown = 1; 594 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; 595 if (!strcasecmp(command,"subscribe") || 596 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; 597 598 /* Setup argument length */ 599 argvlen = malloc(argc*sizeof(size_t)); 600 for (j = 0; j < argc; j++) 601 argvlen[j] = sdslen(argv[j]); 602 603 while(repeat--) { 604 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); 605 while (config.monitor_mode) { 606 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 607 fflush(stdout); 608 } 609 610 if (config.pubsub_mode) { 611 if (config.output != OUTPUT_RAW) 612 printf("Reading messages... (press Ctrl-C to quit)\n"); 613 while (1) { 614 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 615 } 616 } 617 618 if (cliReadReply(output_raw) != REDIS_OK) { 619 free(argvlen); 620 return REDIS_ERR; 621 } else { 622 /* Store database number when SELECT was successfully executed. */ 623 if (!strcasecmp(command,"select") && argc == 2) { 624 config.dbnum = atoi(argv[1]); 625 cliRefreshPrompt(); 626 } 627 } 628 if (config.interval) usleep(config.interval); 629 fflush(stdout); /* Make it grep friendly */ 630 } 631 632 free(argvlen); 633 return REDIS_OK; 634 } 635 636 /* Send the INFO command, reconnecting the link if needed. */ 637 static redisReply *reconnectingInfo(void) { 638 redisContext *c = context; 639 redisReply *reply = NULL; 640 int tries = 0; 641 642 assert(!c->err); 643 while(reply == NULL) { 644 while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) { 645 printf("Reconnecting (%d)...\r", ++tries); 646 fflush(stdout); 647 648 redisFree(c); 649 c = redisConnect(config.hostip,config.hostport); 650 usleep(1000000); 651 } 652 653 reply = redisCommand(c,"INFO"); 654 if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) { 655 fprintf(stderr, "Error: %s\n", c->errstr); 656 exit(1); 657 } else if (tries > 0) { 658 printf("\n"); 659 } 660 } 661 662 context = c; 663 return reply; 664 } 665 666 /*------------------------------------------------------------------------------ 667 * User interface 668 *--------------------------------------------------------------------------- */ 669 670 static int parseOptions(int argc, char **argv) { 671 int i; 672 673 for (i = 1; i < argc; i++) { 674 int lastarg = i==argc-1; 675 676 if (!strcmp(argv[i],"-h") && !lastarg) { 677 sdsfree(config.hostip); 678 config.hostip = sdsnew(argv[++i]); 679 } else if (!strcmp(argv[i],"-h") && lastarg) { 680 usage(); 681 } else if (!strcmp(argv[i],"--help")) { 682 usage(); 683 } else if (!strcmp(argv[i],"-x")) { 684 config.stdinarg = 1; 685 } else if (!strcmp(argv[i],"-p") && !lastarg) { 686 config.hostport = atoi(argv[++i]); 687 } else if (!strcmp(argv[i],"-s") && !lastarg) { 688 config.hostsocket = argv[++i]; 689 } else if (!strcmp(argv[i],"-r") && !lastarg) { 690 config.repeat = strtoll(argv[++i],NULL,10); 691 } else if (!strcmp(argv[i],"-i") && !lastarg) { 692 double seconds = atof(argv[++i]); 693 config.interval = seconds*1000000; 694 } else if (!strcmp(argv[i],"-n") && !lastarg) { 695 config.dbnum = atoi(argv[++i]); 696 } else if (!strcmp(argv[i],"-a") && !lastarg) { 697 config.auth = argv[++i]; 698 } else if (!strcmp(argv[i],"--raw")) { 699 config.output = OUTPUT_RAW; 700 } else if (!strcmp(argv[i],"--csv")) { 701 config.output = OUTPUT_CSV; 702 } else if (!strcmp(argv[i],"--latency")) { 703 config.latency_mode = 1; 704 } else if (!strcmp(argv[i],"--slave")) { 705 config.slave_mode = 1; 706 } else if (!strcmp(argv[i],"--stat")) { 707 config.stat_mode = 1; 708 } else if (!strcmp(argv[i],"--rdb") && !lastarg) { 709 config.getrdb_mode = 1; 710 config.rdb_filename = argv[++i]; 711 } else if (!strcmp(argv[i],"--pipe")) { 712 config.pipe_mode = 1; 713 } else if (!strcmp(argv[i],"--bigkeys")) { 714 config.bigkeys = 1; 715 } else if (!strcmp(argv[i],"--eval") && !lastarg) { 716 config.eval = argv[++i]; 717 } else if (!strcmp(argv[i],"-c")) { 718 config.cluster_mode = 1; 719 } else if (!strcmp(argv[i],"-d") && !lastarg) { 720 sdsfree(config.mb_delim); 721 config.mb_delim = sdsnew(argv[++i]); 722 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { 723 sds version = cliVersion(); 724 printf("redis-cli %s\n", version); 725 sdsfree(version); 726 exit(0); 727 } else { 728 break; 729 } 730 } 731 return i; 732 } 733 734 static sds readArgFromStdin(void) { 735 char buf[1024]; 736 sds arg = sdsempty(); 737 738 while(1) { 739 int nread = read(fileno(stdin),buf,1024); 740 741 if (nread == 0) break; 742 else if (nread == -1) { 743 perror("Reading from standard input"); 744 exit(1); 745 } 746 arg = sdscatlen(arg,buf,nread); 747 } 748 return arg; 749 } 750 751 static void usage() { 752 sds version = cliVersion(); 753 fprintf(stderr, 754 "redis-cli %s\n" 755 "\n" 756 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" 757 " -h <hostname> Server hostname (default: 127.0.0.1)\n" 758 " -p <port> Server port (default: 6379)\n" 759 " -s <socket> Server socket (overrides hostname and port)\n" 760 " -a <password> Password to use when connecting to the server\n" 761 " -r <repeat> Execute specified command N times\n" 762 " -i <interval> When -r is used, waits <interval> seconds per command.\n" 763 " It is possible to specify sub-second times like -i 0.1\n" 764 " -n <db> Database number\n" 765 " -x Read last argument from STDIN\n" 766 " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n" 767 " -c Enable cluster mode (follow -ASK and -MOVED redirections)\n" 768 " --raw Use raw formatting for replies (default when STDOUT is not a tty)\n" 769 " --latency Enter a special mode continuously sampling latency\n" 770 " --slave Simulate a slave showing commands received from the master\n" 771 " --rdb <filename> Transfer an RDB dump from remote server to local file.\n" 772 " --pipe Transfer raw Redis protocol from stdin to server\n" 773 " --bigkeys Sample Redis keys looking for big keys\n" 774 " --eval <file> Send an EVAL command using the Lua script at <file>\n" 775 " --help Output this help and exit\n" 776 " --version Output version and exit\n" 777 "\n" 778 "Examples:\n" 779 " cat /etc/passwd | redis-cli -x set mypasswd\n" 780 " redis-cli get mypasswd\n" 781 " redis-cli -r 100 lpush mylist x\n" 782 " redis-cli -r 100 -i 1 info | grep used_memory_human:\n" 783 " redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n" 784 " (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n" 785 "\n" 786 "When no command is given, redis-cli starts in interactive mode.\n" 787 "Type \"help\" in interactive mode for information on available commands.\n" 788 "\n", 789 version); 790 sdsfree(version); 791 exit(1); 792 } 793 794 /* Turn the plain C strings into Sds strings */ 795 static char **convertToSds(int count, char** args) { 796 int j; 797 char **sds = zmalloc(sizeof(char*)*count); 798 799 for(j = 0; j < count; j++) 800 sds[j] = sdsnew(args[j]); 801 802 return sds; 803 } 804 805 #define LINE_BUFLEN 4096 806 static void repl() { 807 sds historyfile = NULL; 808 int history = 0; 809 char *line; 810 int argc; 811 sds *argv; 812 813 config.interactive = 1; 814 linenoiseSetCompletionCallback(completionCallback); 815 816 /* Only use history when stdin is a tty. */ 817 if (isatty(fileno(stdin))) { 818 history = 1; 819 820 if (getenv("HOME") != NULL) { 821 historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME")); 822 linenoiseHistoryLoad(historyfile); 823 } 824 } 825 826 cliRefreshPrompt(); 827 while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) { 828 if (line[0] != '\0') { 829 argv = sdssplitargs(line,&argc); 830 if (history) linenoiseHistoryAdd(line); 831 if (historyfile) linenoiseHistorySave(historyfile); 832 833 if (argv == NULL) { 834 printf("Invalid argument(s)\n"); 835 free(line); 836 continue; 837 } else if (argc > 0) { 838 if (strcasecmp(argv[0],"quit") == 0 || 839 strcasecmp(argv[0],"exit") == 0) 840 { 841 exit(0); 842 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { 843 sdsfree(config.hostip); 844 config.hostip = sdsnew(argv[1]); 845 config.hostport = atoi(argv[2]); 846 cliRefreshPrompt(); 847 cliConnect(1); 848 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { 849 linenoiseClearScreen(); 850 } else { 851 long long start_time = mstime(), elapsed; 852 int repeat, skipargs = 0; 853 854 repeat = atoi(argv[0]); 855 if (argc > 1 && repeat) { 856 skipargs = 1; 857 } else { 858 repeat = 1; 859 } 860 861 while (1) { 862 config.cluster_reissue_command = 0; 863 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 864 != REDIS_OK) 865 { 866 cliConnect(1); 867 868 /* If we still cannot send the command print error. 869 * We'll try to reconnect the next time. */ 870 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 871 != REDIS_OK) 872 cliPrintContextError(); 873 } 874 /* Issue the command again if we got redirected in cluster mode */ 875 if (config.cluster_mode && config.cluster_reissue_command) { 876 cliConnect(1); 877 } else { 878 break; 879 } 880 } 881 elapsed = mstime()-start_time; 882 if (elapsed >= 500) { 883 printf("(%.2fs)\n",(double)elapsed/1000); 884 } 885 } 886 } 887 /* Free the argument vector */ 888 sdsfreesplitres(argv,argc); 889 } 890 /* linenoise() returns malloc-ed lines like readline() */ 891 free(line); 892 } 893 exit(0); 894 } 895 896 static int noninteractive(int argc, char **argv) { 897 int retval = 0; 898 if (config.stdinarg) { 899 argv = zrealloc(argv, (argc+1)*sizeof(char*)); 900 argv[argc] = readArgFromStdin(); 901 retval = cliSendCommand(argc+1, argv, config.repeat); 902 } else { 903 /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ 904 retval = cliSendCommand(argc, argv, config.repeat); 905 } 906 return retval; 907 } 908 909 static int evalMode(int argc, char **argv) { 910 sds script = sdsempty(); 911 FILE *fp; 912 char buf[1024]; 913 size_t nread; 914 char **argv2; 915 int j, got_comma = 0, keys = 0; 916 917 /* Load the script from the file, as an sds string. */ 918 fp = fopen(config.eval,"r"); 919 if (!fp) { 920 fprintf(stderr, 921 "Can't open file '%s': %s\n", config.eval, strerror(errno)); 922 exit(1); 923 } 924 while((nread = fread(buf,1,sizeof(buf),fp)) != 0) { 925 script = sdscatlen(script,buf,nread); 926 } 927 fclose(fp); 928 929 /* Create our argument vector */ 930 argv2 = zmalloc(sizeof(sds)*(argc+3)); 931 argv2[0] = sdsnew("EVAL"); 932 argv2[1] = script; 933 for (j = 0; j < argc; j++) { 934 if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) { 935 got_comma = 1; 936 continue; 937 } 938 argv2[j+3-got_comma] = sdsnew(argv[j]); 939 if (!got_comma) keys++; 940 } 941 argv2[2] = sdscatprintf(sdsempty(),"%d",keys); 942 943 /* Call it */ 944 return cliSendCommand(argc+3-got_comma, argv2, config.repeat); 945 } 946 947 static void latencyMode(void) { 948 redisReply *reply; 949 long long start, latency, min = 0, max = 0, tot = 0, count = 0; 950 double avg; 951 952 if (!context) exit(1); 953 while(1) { 954 start = mstime(); 955 reply = redisCommand(context,"PING"); 956 if (reply == NULL) { 957 fprintf(stderr,"\nI/O error\n"); 958 exit(1); 959 } 960 latency = mstime()-start; 961 freeReplyObject(reply); 962 count++; 963 if (count == 1) { 964 min = max = tot = latency; 965 avg = (double) latency; 966 } else { 967 if (latency < min) min = latency; 968 if (latency > max) max = latency; 969 tot += latency; 970 avg = (double) tot/count; 971 } 972 printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)", 973 min, max, avg, count); 974 fflush(stdout); 975 usleep(10000); 976 } 977 } 978 979 /* Sends SYNC and reads the number of bytes in the payload. Used both by 980 * slaveMode() and getRDB(). */ 981 unsigned long long sendSync(int fd) { 982 /* To start we need to send the SYNC command and return the payload. 983 * The hiredis client lib does not understand this part of the protocol 984 * and we don't want to mess with its buffers, so everything is performed 985 * using direct low-level I/O. */ 986 char buf[4096], *p; 987 ssize_t nread; 988 989 /* Send the SYNC command. */ 990 if (write(fd,"SYNC\r\n",6) != 6) { 991 fprintf(stderr,"Error writing to master\n"); 992 exit(1); 993 } 994 995 /* Read $<payload>\r\n, making sure to read just up to "\n" */ 996 p = buf; 997 while(1) { 998 nread = read(fd,p,1); 999 if (nread <= 0) { 1000 fprintf(stderr,"Error reading bulk length while SYNCing\n"); 1001 exit(1); 1002 } 1003 if (*p == '\n' && p != buf) break; 1004 if (*p != '\n') p++; 1005 } 1006 *p = '\0'; 1007 if (buf[0] == '-') { 1008 printf("SYNC with master failed: %s\n", buf); 1009 exit(1); 1010 } 1011 return strtoull(buf+1,NULL,10); 1012 } 1013 1014 static void slaveMode(void) { 1015 int fd = context->fd; 1016 unsigned long long payload = sendSync(fd); 1017 char buf[1024]; 1018 1019 fprintf(stderr,"SYNC with master, discarding %llu " 1020 "bytes of bulk transfer...\n", payload); 1021 1022 /* Discard the payload. */ 1023 while(payload) { 1024 ssize_t nread; 1025 1026 nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); 1027 if (nread <= 0) { 1028 fprintf(stderr,"Error reading RDB payload while SYNCing\n"); 1029 exit(1); 1030 } 1031 payload -= nread; 1032 } 1033 fprintf(stderr,"SYNC done. Logging commands from master.\n"); 1034 1035 /* Now we can use hiredis to read the incoming protocol. */ 1036 config.output = OUTPUT_CSV; 1037 while (cliReadReply(0) == REDIS_OK); 1038 } 1039 1040 /* This function implements --rdb, so it uses the replication protocol in order 1041 * to fetch the RDB file from a remote server. */ 1042 static void getRDB(void) { 1043 int s = context->fd; 1044 int fd; 1045 unsigned long long payload = sendSync(s); 1046 char buf[4096]; 1047 1048 fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n", 1049 payload, config.rdb_filename); 1050 1051 /* Write to file. */ 1052 if (!strcmp(config.rdb_filename,"-")) { 1053 fd = STDOUT_FILENO; 1054 } else { 1055 fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644); 1056 if (fd == -1) { 1057 fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename, 1058 strerror(errno)); 1059 exit(1); 1060 } 1061 } 1062 1063 while(payload) { 1064 ssize_t nread, nwritten; 1065 1066 nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); 1067 if (nread <= 0) { 1068 fprintf(stderr,"I/O Error reading RDB payload from socket\n"); 1069 exit(1); 1070 } 1071 nwritten = write(fd, buf, nread); 1072 if (nwritten != nread) { 1073 fprintf(stderr,"Error writing data to file: %s\n", 1074 strerror(errno)); 1075 exit(1); 1076 } 1077 payload -= nread; 1078 } 1079 close(s); /* Close the file descriptor ASAP as fsync() may take time. */ 1080 fsync(fd); 1081 fprintf(stderr,"Transfer finished with success.\n"); 1082 exit(0); 1083 } 1084 1085 static void pipeMode(void) { 1086 int fd = context->fd; 1087 long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0; 1088 char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */ 1089 char aneterr[ANET_ERR_LEN]; 1090 redisReader *reader = redisReaderCreate(); 1091 redisReply *reply; 1092 int eof = 0; /* True once we consumed all the standard input. */ 1093 int done = 0; 1094 char magic[20]; /* Special reply we recognize. */ 1095 1096 srand(time(NULL)); 1097 1098 /* Use non blocking I/O. */ 1099 if (anetNonBlock(aneterr,fd) == ANET_ERR) { 1100 fprintf(stderr, "Can't set the socket in non blocking mode: %s\n", 1101 aneterr); 1102 exit(1); 1103 } 1104 1105 /* Transfer raw protocol and read replies from the server at the same 1106 * time. */ 1107 while(!done) { 1108 int mask = AE_READABLE; 1109 1110 if (!eof || obuf_len != 0) mask |= AE_WRITABLE; 1111 mask = aeWait(fd,mask,1000); 1112 1113 /* Handle the readable state: we can read replies from the server. */ 1114 if (mask & AE_READABLE) { 1115 ssize_t nread; 1116 1117 /* Read from socket and feed the hiredis reader. */ 1118 do { 1119 nread = read(fd,ibuf,sizeof(ibuf)); 1120 if (nread == -1 && errno != EAGAIN && errno != EINTR) { 1121 fprintf(stderr, "Error reading from the server: %s\n", 1122 strerror(errno)); 1123 exit(1); 1124 } 1125 if (nread > 0) redisReaderFeed(reader,ibuf,nread); 1126 } while(nread > 0); 1127 1128 /* Consume replies. */ 1129 do { 1130 if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) { 1131 fprintf(stderr, "Error reading replies from server\n"); 1132 exit(1); 1133 } 1134 if (reply) { 1135 if (reply->type == REDIS_REPLY_ERROR) { 1136 fprintf(stderr,"%s\n", reply->str); 1137 errors++; 1138 } else if (eof && reply->type == REDIS_REPLY_STRING && 1139 reply->len == 20) { 1140 /* Check if this is the reply to our final ECHO 1141 * command. If so everything was received 1142 * from the server. */ 1143 if (memcmp(reply->str,magic,20) == 0) { 1144 printf("Last reply received from server.\n"); 1145 done = 1; 1146 replies--; 1147 } 1148 } 1149 replies++; 1150 freeReplyObject(reply); 1151 } 1152 } while(reply); 1153 } 1154 1155 /* Handle the writable state: we can send protocol to the server. */ 1156 if (mask & AE_WRITABLE) { 1157 while(1) { 1158 /* Transfer current buffer to server. */ 1159 if (obuf_len != 0) { 1160 ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len); 1161 1162 if (nwritten == -1) { 1163 if (errno != EAGAIN && errno != EINTR) { 1164 fprintf(stderr, "Error writing to the server: %s\n", 1165 strerror(errno)); 1166 exit(1); 1167 } else { 1168 nwritten = 0; 1169 } 1170 } 1171 obuf_len -= nwritten; 1172 obuf_pos += nwritten; 1173 if (obuf_len != 0) break; /* Can't accept more data. */ 1174 } 1175 /* If buffer is empty, load from stdin. */ 1176 if (obuf_len == 0 && !eof) { 1177 ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf)); 1178 1179 if (nread == 0) { 1180 char echo[] = 1181 "*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n"; 1182 int j; 1183 1184 eof = 1; 1185 /* Everything transferred, so we queue a special 1186 * ECHO command that we can match in the replies 1187 * to make sure everything was read from the server. */ 1188 for (j = 0; j < 20; j++) 1189 magic[j] = rand() & 0xff; 1190 memcpy(echo+19,magic,20); 1191 memcpy(obuf,echo,sizeof(echo)-1); 1192 obuf_len = sizeof(echo)-1; 1193 obuf_pos = 0; 1194 printf("All data transferred. Waiting for the last reply...\n"); 1195 } else if (nread == -1) { 1196 fprintf(stderr, "Error reading from stdin: %s\n", 1197 strerror(errno)); 1198 exit(1); 1199 } else { 1200 obuf_len = nread; 1201 obuf_pos = 0; 1202 } 1203 } 1204 if (obuf_len == 0 && eof) break; 1205 } 1206 } 1207 } 1208 redisReaderFree(reader); 1209 printf("errors: %lld, replies: %lld\n", errors, replies); 1210 if (errors) 1211 exit(1); 1212 else 1213 exit(0); 1214 } 1215 1216 #define TYPE_STRING 0 1217 #define TYPE_LIST 1 1218 #define TYPE_SET 2 1219 #define TYPE_HASH 3 1220 #define TYPE_ZSET 4 1221 1222 static void findBigKeys(void) { 1223 unsigned long long biggest[5] = {0,0,0,0,0}; 1224 unsigned long long samples = 0; 1225 redisReply *reply1, *reply2, *reply3 = NULL; 1226 char *sizecmd, *typename[] = {"string","list","set","hash","zset"}; 1227 char *typeunit[] = {"bytes","items","members","fields","members"}; 1228 int type; 1229 1230 printf("\n# Press ctrl+c when you have had enough of it... :)\n"); 1231 printf("# You can use -i 0.1 to sleep 0.1 sec every 100 sampled keys\n"); 1232 printf("# in order to reduce server load (usually not needed).\n\n"); 1233 while(1) { 1234 /* Sample with RANDOMKEY */ 1235 reply1 = redisCommand(context,"RANDOMKEY"); 1236 if (reply1 == NULL) { 1237 fprintf(stderr,"\nI/O error\n"); 1238 exit(1); 1239 } else if (reply1->type == REDIS_REPLY_ERROR) { 1240 fprintf(stderr, "RANDOMKEY error: %s\n", 1241 reply1->str); 1242 exit(1); 1243 } else if (reply1->type == REDIS_REPLY_NIL) { 1244 fprintf(stderr, "It looks like the database is empty!\n"); 1245 exit(1); 1246 } 1247 1248 /* Get the key type */ 1249 reply2 = redisCommand(context,"TYPE %s",reply1->str); 1250 assert(reply2 && reply2->type == REDIS_REPLY_STATUS); 1251 samples++; 1252 1253 /* Get the key "size" */ 1254 if (!strcmp(reply2->str,"string")) { 1255 sizecmd = "STRLEN"; 1256 type = TYPE_STRING; 1257 } else if (!strcmp(reply2->str,"list")) { 1258 sizecmd = "LLEN"; 1259 type = TYPE_LIST; 1260 } else if (!strcmp(reply2->str,"set")) { 1261 sizecmd = "SCARD"; 1262 type = TYPE_SET; 1263 } else if (!strcmp(reply2->str,"hash")) { 1264 sizecmd = "HLEN"; 1265 type = TYPE_HASH; 1266 } else if (!strcmp(reply2->str,"zset")) { 1267 sizecmd = "ZCARD"; 1268 type = TYPE_ZSET; 1269 } else if (!strcmp(reply2->str,"none")) { 1270 freeReplyObject(reply1); 1271 freeReplyObject(reply2); 1272 freeReplyObject(reply3); 1273 continue; 1274 } else { 1275 fprintf(stderr, "Unknown key type '%s' for key '%s'\n", 1276 reply2->str, reply1->str); 1277 exit(1); 1278 } 1279 1280 reply3 = redisCommand(context,"%s %s", sizecmd, reply1->str); 1281 if (reply3 && reply3->type == REDIS_REPLY_INTEGER) { 1282 if (biggest[type] < reply3->integer) { 1283 printf("Biggest %-6s found so far '%s' with %llu %s.\n", 1284 typename[type], reply1->str, 1285 (unsigned long long) reply3->integer, 1286 typeunit[type]); 1287 biggest[type] = reply3->integer; 1288 } 1289 } 1290 1291 if ((samples % 1000000) == 0) 1292 printf("(%llu keys sampled)\n", samples); 1293 1294 if ((samples % 100) == 0 && config.interval) 1295 usleep(config.interval); 1296 1297 freeReplyObject(reply1); 1298 freeReplyObject(reply2); 1299 if (reply3) freeReplyObject(reply3); 1300 } 1301 } 1302 1303 /* Return the specified INFO field from the INFO command output "info". 1304 * A new buffer is allocated for the result, that needs to be free'd. 1305 * If the field is not found NULL is returned. */ 1306 static char *getInfoField(char *info, char *field) { 1307 char *p = strstr(info,field); 1308 char *n1, *n2; 1309 char *result; 1310 1311 if (!p) return NULL; 1312 p += strlen(field)+1; 1313 n1 = strchr(p,'\r'); 1314 n2 = strchr(p,','); 1315 if (n2 && n2 < n1) n1 = n2; 1316 result = malloc(sizeof(char)*(n1-p)+1); 1317 memcpy(result,p,(n1-p)); 1318 result[n1-p] = '\0'; 1319 return result; 1320 } 1321 1322 /* Like the above function but automatically convert the result into 1323 * a long. On error (missing field) LONG_MIN is returned. */ 1324 static long getLongInfoField(char *info, char *field) { 1325 char *value = getInfoField(info,field); 1326 long l; 1327 1328 if (!value) return LONG_MIN; 1329 l = strtol(value,NULL,10); 1330 free(value); 1331 return l; 1332 } 1333 1334 /* Convert number of bytes into a human readable string of the form: 1335 * 100B, 2G, 100M, 4K, and so forth. */ 1336 void bytesToHuman(char *s, long long n) { 1337 double d; 1338 1339 if (n < 0) { 1340 *s = '-'; 1341 s++; 1342 n = -n; 1343 } 1344 if (n < 1024) { 1345 /* Bytes */ 1346 sprintf(s,"%lluB",n); 1347 return; 1348 } else if (n < (1024*1024)) { 1349 d = (double)n/(1024); 1350 sprintf(s,"%.2fK",d); 1351 } else if (n < (1024LL*1024*1024)) { 1352 d = (double)n/(1024*1024); 1353 sprintf(s,"%.2fM",d); 1354 } else if (n < (1024LL*1024*1024*1024)) { 1355 d = (double)n/(1024LL*1024*1024); 1356 sprintf(s,"%.2fG",d); 1357 } 1358 } 1359 1360 static void statMode() { 1361 redisReply *reply; 1362 long aux, requests = 0; 1363 int i = 0; 1364 1365 while(1) { 1366 char buf[64]; 1367 int j; 1368 1369 reply = reconnectingInfo(); 1370 if (reply->type == REDIS_REPLY_ERROR) { 1371 printf("ERROR: %s\n", reply->str); 1372 exit(1); 1373 } 1374 1375 if ((i++ % 20) == 0) { 1376 printf( 1377 "------- data ------ --------------------- load -------------------- - child -\n" 1378 "keys mem clients blocked requests connections \n"); 1379 } 1380 1381 /* Keys */ 1382 aux = 0; 1383 for (j = 0; j < 20; j++) { 1384 long k; 1385 1386 sprintf(buf,"db%d:keys",j); 1387 k = getLongInfoField(reply->str,buf); 1388 if (k == LONG_MIN) continue; 1389 aux += k; 1390 } 1391 sprintf(buf,"%ld",aux); 1392 printf("%-11s",buf); 1393 1394 /* Used memory */ 1395 aux = getLongInfoField(reply->str,"used_memory"); 1396 bytesToHuman(buf,aux); 1397 printf("%-8s",buf); 1398 1399 /* Clients */ 1400 aux = getLongInfoField(reply->str,"connected_clients"); 1401 sprintf(buf,"%ld",aux); 1402 printf(" %-8s",buf); 1403 1404 /* Blocked (BLPOPPING) Clients */ 1405 aux = getLongInfoField(reply->str,"blocked_clients"); 1406 sprintf(buf,"%ld",aux); 1407 printf("%-8s",buf); 1408 1409 /* Requets */ 1410 aux = getLongInfoField(reply->str,"total_commands_processed"); 1411 sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests); 1412 printf("%-19s",buf); 1413 requests = aux; 1414 1415 /* Connections */ 1416 aux = getLongInfoField(reply->str,"total_connections_received"); 1417 sprintf(buf,"%ld",aux); 1418 printf(" %-12s",buf); 1419 1420 /* Children */ 1421 aux = getLongInfoField(reply->str,"bgsave_in_progress"); 1422 aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1; 1423 switch(aux) { 1424 case 0: break; 1425 case 1: 1426 printf("SAVE"); 1427 break; 1428 case 2: 1429 printf("AOF"); 1430 break; 1431 case 3: 1432 printf("SAVE+AOF"); 1433 break; 1434 } 1435 1436 printf("\n"); 1437 freeReplyObject(reply); 1438 usleep(config.interval); 1439 } 1440 } 1441 1442 int main(int argc, char **argv) { 1443 int firstarg; 1444 1445 config.hostip = sdsnew("127.0.0.1"); 1446 config.hostport = 6379; 1447 config.hostsocket = NULL; 1448 config.repeat = 1; 1449 config.interval = 0; 1450 config.dbnum = 0; 1451 config.interactive = 0; 1452 config.shutdown = 0; 1453 config.monitor_mode = 0; 1454 config.pubsub_mode = 0; 1455 config.latency_mode = 0; 1456 config.cluster_mode = 0; 1457 config.slave_mode = 0; 1458 config.getrdb_mode = 0; 1459 config.rdb_filename = NULL; 1460 config.pipe_mode = 0; 1461 config.bigkeys = 0; 1462 config.stdinarg = 0; 1463 config.auth = NULL; 1464 config.eval = NULL; 1465 if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL)) 1466 config.output = OUTPUT_RAW; 1467 else 1468 config.output = OUTPUT_STANDARD; 1469 config.mb_delim = sdsnew("\n"); 1470 cliInitHelp(); 1471 1472 firstarg = parseOptions(argc,argv); 1473 argc -= firstarg; 1474 argv += firstarg; 1475 1476 /* Latency mode */ 1477 if (config.latency_mode) { 1478 if (cliConnect(0) == REDIS_ERR) exit(1); 1479 latencyMode(); 1480 } 1481 1482 /* Slave mode */ 1483 if (config.slave_mode) { 1484 if (cliConnect(0) == REDIS_ERR) exit(1); 1485 slaveMode(); 1486 } 1487 1488 /* Get RDB mode. */ 1489 if (config.getrdb_mode) { 1490 if (cliConnect(0) == REDIS_ERR) exit(1); 1491 getRDB(); 1492 } 1493 1494 /* Pipe mode */ 1495 if (config.pipe_mode) { 1496 if (cliConnect(0) == REDIS_ERR) exit(1); 1497 pipeMode(); 1498 } 1499 1500 /* Find big keys */ 1501 if (config.bigkeys) { 1502 if (cliConnect(0) == REDIS_ERR) exit(1); 1503 findBigKeys(); 1504 } 1505 1506 /* Stat mode */ 1507 if (config.stat_mode) { 1508 if (cliConnect(0) == REDIS_ERR) exit(1); 1509 if (config.interval == 0) config.interval = 1000000; 1510 statMode(); 1511 } 1512 1513 /* Start interactive mode when no command is provided */ 1514 if (argc == 0 && !config.eval) { 1515 /* Note that in repl mode we don't abort on connection error. 1516 * A new attempt will be performed for every command send. */ 1517 cliConnect(0); 1518 repl(); 1519 } 1520 1521 /* Otherwise, we have some arguments to execute */ 1522 if (cliConnect(0) != REDIS_OK) exit(1); 1523 if (config.eval) { 1524 return evalMode(argc,argv); 1525 } else { 1526 return noninteractive(argc,convertToSds(argc,argv)); 1527 } 1528 } 1529