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