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