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