这份代码是redis的client接口,其和server端的交互使用了deps目录下的hiredis c库,同时,在这部分代码中,应用了linenoise库完成类似history命令查询、自动补全等终端控制功能。
1 #include "fmacros.h" //用于mac下的兼容性处理 2 #include "version.h" //版本信息头文件,当前版本是2.4.10 3 4 #include <stdio.h> 5 #include <string.h> 6 #include <stdlib.h> 7 #include <unistd.h> 8 #include <ctype.h> 9 #include <errno.h> 10 #include <sys/stat.h> 11 #include <sys/time.h> 12 #include <assert.h> 13 14 #include "hiredis.h" //redis 客户端库的头文件 15 #include "sds.h" 16 #include "zmalloc.h" 17 #include "linenoise.h" //终端控制库的头文件 18 #include "help.h" //当前所有的命令文件汇总,用于tab自动补全功能的源数据
/* help entry的结构如下:
struct commandHelp {
20 char *name; //命令名字
21 char *params; //参数格式
22 char *summary; //简单的解释信息
23 int group; //命令类型(当前版本共分10种不同类型的命令)
24 char *since; //从哪个版本开始支持此命令
25 } */
19 20 #define REDIS_NOTUSED(V) ((void) V) 21 22 static redisContext *context; //维护client和server端的连接信息,包括文件描述符,错误信息等,参见deps/hiredis/hiredis.h 23 static struct config { 24 char *hostip; 25 int hostport; 26 char *hostsocket; 27 long repeat; //命令重复执行次数 28 long interval; //命令重复执行间隔 29 int dbnum; // db no. 30 int interactive; //交互模式 or 命令模式 31 int shutdown; 32 int monitor_mode; //监控模式 33 int pubsub_mode; //pub sub模式 34 int latency_mode; //该模式测试cli到server执行ping命令的时间间隔(应用层ping) 35 int stdinarg; /* get last arg from stdin. (-x option) */ 36 char *auth; //需要鉴权时的密码信息 37 int raw_output; /* output mode per command */ //选择该模式,将不会添加类似(interger),参见http://blog.sina.com.cn/s/blog_6262a50e0100zw83.html 38 sds mb_delim; 39 char prompt[128]; 40 } config; 41 42 static void usage(); 43 char *redisGitSHA1(void); 44 char *redisGitDirty(void); 45 46 /*------------------------------------------------------------------------------ 47 * Utility functions 48 *--------------------------------------------------------------------------- */ 49 50 static long long mstime(void) { 51 struct timeval tv; 52 long long mst; 53 54 gettimeofday(&tv, NULL); 55 mst = ((long)tv.tv_sec)*1000; 56 mst += tv.tv_usec/1000; 57 return mst; 58 } 59 60 static void cliRefreshPrompt(void) { 61 int len; 62 63 if (config.hostsocket != NULL) 64 len = snprintf(config.prompt,sizeof(config.prompt),"redis %s", 65 config.hostsocket); 66 else 67 len = snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d", 68 config.hostip, config.hostport); 69 /* Add [dbnum] if needed */ 70 if (config.dbnum != 0) 71 len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]", 72 config.dbnum); 73 snprintf(config.prompt+len,sizeof(config.prompt)-len,"> "); 74 } 75 76 /*------------------------------------------------------------------------------ 77 * Help functions 78 *--------------------------------------------------------------------------- */ 79 80 #define CLI_HELP_COMMAND 1 81 #define CLI_HELP_GROUP 2 82 83 typedef struct { 84 int type; 85 int argc; 86 sds *argv; 87 sds full; 88 89 /* Only used for help on commands */ 90 struct commandHelp *org; 91 } helpEntry; 92 93 static helpEntry *helpEntries; 94 static int helpEntriesLen; 95 96 static sds cliVersion() { 97 sds version; 98 version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); 99 100 /* Add git commit and working tree status when available */ 101 if (strtoll(redisGitSHA1(),NULL,16)) { 102 version = sdscatprintf(version, " (git:%s", redisGitSHA1()); 103 if (strtoll(redisGitDirty(),NULL,10)) 104 version = sdscatprintf(version, "-dirty"); 105 version = sdscat(version, ")"); 106 } 107 return version; 108 } 109 110 static void cliInitHelp() { 111 int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp); 112 int groupslen = sizeof(commandGroups)/sizeof(char*); 113 int i, len, pos = 0; 114 helpEntry tmp; 115 116 helpEntriesLen = len = commandslen+groupslen; 117 helpEntries = malloc(sizeof(helpEntry)*len); 118 119 for (i = 0; i < groupslen; i++) { 120 tmp.argc = 1; 121 tmp.argv = malloc(sizeof(sds)); 122 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]); 123 tmp.full = tmp.argv[0]; 124 tmp.type = CLI_HELP_GROUP; 125 tmp.org = NULL; 126 helpEntries[pos++] = tmp; 127 } 128 129 for (i = 0; i < commandslen; i++) { 130 tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc); 131 tmp.full = sdsnew(commandHelp[i].name); 132 tmp.type = CLI_HELP_COMMAND; 133 tmp.org = &commandHelp[i]; 134 helpEntries[pos++] = tmp; 135 } 136 } 137 138 /* Output command help to stdout. */ 139 static void cliOutputCommandHelp(struct commandHelp *help, int group) { 140 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params); 141 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary); 142 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since); 143 if (group) { 144 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]); 145 } 146 } 147 148 /* Print generic help. */ 149 static void cliOutputGenericHelp() { 150 sds version = cliVersion(); 151 printf( 152 "redis-cli %s\r\n" 153 "Type: \"help @<group>\" to get a list of commands in <group>\r\n" 154 " \"help <command>\" for help on <command>\r\n" 155 " \"help <tab>\" to get a list of possible help topics\r\n" 156 " \"quit\" to exit\r\n", 157 version 158 ); 159 sdsfree(version); 160 } 161 162 /* Output all command help, filtering by group or command name. */ 163 static void cliOutputHelp(int argc, char **argv) { 164 int i, j, len; 165 int group = -1; 166 helpEntry *entry; 167 struct commandHelp *help; 168 169 if (argc == 0) { 170 cliOutputGenericHelp(); 171 return; 172 } else if (argc > 0 && argv[0][0] == '@') { 173 len = sizeof(commandGroups)/sizeof(char*); 174 for (i = 0; i < len; i++) { 175 if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) { 176 group = i; 177 break; 178 } 179 } 180 } 181 182 assert(argc > 0); 183 for (i = 0; i < helpEntriesLen; i++) { 184 entry = &helpEntries[i]; 185 if (entry->type != CLI_HELP_COMMAND) continue; 186 187 help = entry->org; 188 if (group == -1) { 189 /* Compare all arguments */ 190 if (argc == entry->argc) { 191 for (j = 0; j < argc; j++) { 192 if (strcasecmp(argv[j],entry->argv[j]) != 0) break; 193 } 194 if (j == argc) { 195 cliOutputCommandHelp(help,1); 196 } 197 } 198 } else { 199 if (group == help->group) { 200 cliOutputCommandHelp(help,0); 201 } 202 } 203 } 204 printf("\r\n"); 205 } 206 207 static void completionCallback(const char *buf, linenoiseCompletions *lc) { 208 size_t startpos = 0; 209 int mask; 210 int i; 211 size_t matchlen; 212 sds tmp; 213 214 if (strncasecmp(buf,"help ",5) == 0) { 215 startpos = 5; 216 while (isspace(buf[startpos])) startpos++; 217 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP; 218 } else { 219 mask = CLI_HELP_COMMAND; 220 } 221 222 for (i = 0; i < helpEntriesLen; i++) { 223 if (!(helpEntries[i].type & mask)) continue; 224 225 matchlen = strlen(buf+startpos); 226 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) { 227 tmp = sdsnewlen(buf,startpos); 228 tmp = sdscat(tmp,helpEntries[i].full); 229 linenoiseAddCompletion(lc,tmp); 230 sdsfree(tmp); 231 } 232 } 233 } 234 235 /*------------------------------------------------------------------------------ 236 * Networking / parsing 237 *--------------------------------------------------------------------------- */ 238 239 /* Send AUTH command to the server */ 240 static int cliAuth() { 241 redisReply *reply; 242 if (config.auth == NULL) return REDIS_OK; 243 244 reply = redisCommand(context,"AUTH %s",config.auth); 245 if (reply != NULL) { 246 freeReplyObject(reply); 247 return REDIS_OK; 248 } 249 return REDIS_ERR; 250 } 251 252 /* Send SELECT dbnum to the server */ 253 static int cliSelect() { 254 redisReply *reply; 255 if (config.dbnum == 0) return REDIS_OK; 256 257 reply = redisCommand(context,"SELECT %d",config.dbnum); 258 if (reply != NULL) { 259 freeReplyObject(reply); 260 return REDIS_OK; 261 } 262 return REDIS_ERR; 263 } 264 265 /* Connect to the client. If force is not zero the connection is performed 266 * even if there is already a connected socket. */ 267 static int cliConnect(int force) { 268 if (context == NULL || force) { 269 if (context != NULL) 270 redisFree(context); 271 272 if (config.hostsocket == NULL) { 273 context = redisConnect(config.hostip,config.hostport); 274 } else { 275 context = redisConnectUnix(config.hostsocket); 276 } 277 278 if (context->err) { 279 fprintf(stderr,"Could not connect to Redis at "); 280 if (config.hostsocket == NULL) 281 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr); 282 else 283 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr); 284 redisFree(context); 285 context = NULL; 286 return REDIS_ERR; 287 } 288 289 /* Do AUTH and select the right DB. */ 290 if (cliAuth() != REDIS_OK) 291 return REDIS_ERR; 292 if (cliSelect() != REDIS_OK) 293 return REDIS_ERR; 294 } 295 return REDIS_OK; 296 } 297 298 static void cliPrintContextError() { 299 if (context == NULL) return; 300 fprintf(stderr,"Error: %s\n",context->errstr); 301 } 302 303 static sds cliFormatReplyTTY(redisReply *r, char *prefix) { 304 sds out = sdsempty(); 305 switch (r->type) { 306 case REDIS_REPLY_ERROR: 307 out = sdscatprintf(out,"(error) %s\n", r->str); 308 break; 309 case REDIS_REPLY_STATUS: 310 out = sdscat(out,r->str); 311 out = sdscat(out,"\n"); 312 break; 313 case REDIS_REPLY_INTEGER: 314 out = sdscatprintf(out,"(integer) %lld\n",r->integer); 315 break; 316 case REDIS_REPLY_STRING: 317 /* If you are producing output for the standard output we want 318 * a more interesting output with quoted characters and so forth */ 319 out = sdscatrepr(out,r->str,r->len); 320 out = sdscat(out,"\n"); 321 break; 322 case REDIS_REPLY_NIL: 323 out = sdscat(out,"(nil)\n"); 324 break; 325 case REDIS_REPLY_ARRAY: 326 if (r->elements == 0) { 327 out = sdscat(out,"(empty list or set)\n"); 328 } else { 329 unsigned int i, idxlen = 0; 330 char _prefixlen[16]; 331 char _prefixfmt[16]; 332 sds _prefix; 333 sds tmp; 334 335 /* Calculate chars needed to represent the largest index */ 336 i = r->elements; 337 do { 338 idxlen++; 339 i /= 10; 340 } while(i); 341 342 /* Prefix for nested multi bulks should grow with idxlen+2 spaces */ 343 memset(_prefixlen,' ',idxlen+2); 344 _prefixlen[idxlen+2] = '\0'; 345 _prefix = sdscat(sdsnew(prefix),_prefixlen); 346 347 /* Setup prefix format for every entry */ 348 snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen); 349 350 for (i = 0; i < r->elements; i++) { 351 /* Don't use the prefix for the first element, as the parent 352 * caller already prepended the index number. */ 353 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1); 354 355 /* Format the multi bulk entry */ 356 tmp = cliFormatReplyTTY(r->element[i],_prefix); 357 out = sdscatlen(out,tmp,sdslen(tmp)); 358 sdsfree(tmp); 359 } 360 sdsfree(_prefix); 361 } 362 break; 363 default: 364 fprintf(stderr,"Unknown reply type: %d\n", r->type); 365 exit(1); 366 } 367 return out; 368 } 369 370 static sds cliFormatReplyRaw(redisReply *r) { 371 sds out = sdsempty(), tmp; 372 size_t i; 373 374 switch (r->type) { 375 case REDIS_REPLY_NIL: 376 /* Nothing... */ 377 break; 378 case REDIS_REPLY_ERROR: 379 out = sdscatlen(out,r->str,r->len); 380 out = sdscatlen(out,"\n",1); 381 break; 382 case REDIS_REPLY_STATUS: 383 case REDIS_REPLY_STRING: 384 out = sdscatlen(out,r->str,r->len); 385 break; 386 case REDIS_REPLY_INTEGER: 387 out = sdscatprintf(out,"%lld",r->integer); 388 break; 389 case REDIS_REPLY_ARRAY: 390 for (i = 0; i < r->elements; i++) { 391 if (i > 0) out = sdscat(out,config.mb_delim); 392 tmp = cliFormatReplyRaw(r->element[i]); 393 out = sdscatlen(out,tmp,sdslen(tmp)); 394 sdsfree(tmp); 395 } 396 break; 397 default: 398 fprintf(stderr,"Unknown reply type: %d\n", r->type); 399 exit(1); 400 } 401 return out; 402 } 403 404 static int cliReadReply(int output_raw_strings) { 405 void *_reply; 406 redisReply *reply; 407 sds out; 408 409 if (redisGetReply(context,&_reply) != REDIS_OK) { 410 if (config.shutdown) 411 return REDIS_OK; 412 if (config.interactive) { 413 /* Filter cases where we should reconnect */ 414 if (context->err == REDIS_ERR_IO && errno == ECONNRESET) 415 return REDIS_ERR; 416 if (context->err == REDIS_ERR_EOF) 417 return REDIS_ERR; 418 } 419 cliPrintContextError(); 420 exit(1); 421 return REDIS_ERR; /* avoid compiler warning */ 422 } 423 424 reply = (redisReply*)_reply; 425 if (output_raw_strings) { 426 out = cliFormatReplyRaw(reply); 427 } else { 428 if (config.raw_output) { 429 out = cliFormatReplyRaw(reply); 430 out = sdscat(out,"\n"); 431 } else { 432 out = cliFormatReplyTTY(reply,""); 433 } 434 } 435 fwrite(out,sdslen(out),1,stdout); 436 sdsfree(out); 437 freeReplyObject(reply); 438 return REDIS_OK; 439 } 440 441 static int cliSendCommand(int argc, char **argv, int repeat) { 442 char *command = argv[0]; 443 size_t *argvlen; 444 int j, output_raw; 445 446 if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { 447 cliOutputHelp(--argc, ++argv); 448 return REDIS_OK; 449 } 450 451 if (context == NULL) return REDIS_ERR; 452 453 output_raw = 0; 454 if (!strcasecmp(command,"info") || 455 (argc == 2 && !strcasecmp(command,"client") && 456 !strcasecmp(argv[1],"list"))) 457 458 { 459 output_raw = 1; 460 } 461 462 if (!strcasecmp(command,"shutdown")) config.shutdown = 1; 463 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; 464 if (!strcasecmp(command,"subscribe") || 465 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; 466 467 /* Setup argument length */ 468 argvlen = malloc(argc*sizeof(size_t)); 469 for (j = 0; j < argc; j++) 470 argvlen[j] = sdslen(argv[j]); 471 472 while(repeat--) { 473 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); 474 while (config.monitor_mode) { 475 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 476 fflush(stdout); 477 } 478 479 if (config.pubsub_mode) { 480 if (!config.raw_output) 481 printf("Reading messages... (press Ctrl-C to quit)\n"); 482 while (1) { 483 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 484 } 485 } 486 487 if (cliReadReply(output_raw) != REDIS_OK) { 488 free(argvlen); 489 return REDIS_ERR; 490 } else { 491 /* Store database number when SELECT was successfully executed. */ 492 if (!strcasecmp(command,"select") && argc == 2) { 493 config.dbnum = atoi(argv[1]); 494 cliRefreshPrompt(); 495 } 496 } 497 if (config.interval) usleep(config.interval); 498 fflush(stdout); /* Make it grep friendly */ 499 } 500 501 free(argvlen); 502 return REDIS_OK; 503 } 504 505 /*------------------------------------------------------------------------------ 506 * User interface 507 *--------------------------------------------------------------------------- */ 508 509 static int parseOptions(int argc, char **argv) { 510 int i; 511 512 for (i = 1; i < argc; i++) { 513 int lastarg = i==argc-1; 514 515 if (!strcmp(argv[i],"-h") && !lastarg) { 516 sdsfree(config.hostip); 517 config.hostip = sdsnew(argv[i+1]); 518 i++; 519 } else if (!strcmp(argv[i],"-h") && lastarg) { 520 usage(); 521 } else if (!strcmp(argv[i],"--help")) { 522 usage(); 523 } else if (!strcmp(argv[i],"-x")) { 524 config.stdinarg = 1; 525 } else if (!strcmp(argv[i],"-p") && !lastarg) { 526 config.hostport = atoi(argv[i+1]); 527 i++; 528 } else if (!strcmp(argv[i],"-s") && !lastarg) { 529 config.hostsocket = argv[i+1]; 530 i++; 531 } else if (!strcmp(argv[i],"-r") && !lastarg) { 532 config.repeat = strtoll(argv[i+1],NULL,10); 533 i++; 534 } else if (!strcmp(argv[i],"-i") && !lastarg) { 535 double seconds = atof(argv[i+1]); 536 config.interval = seconds*1000000; 537 i++; 538 } else if (!strcmp(argv[i],"-n") && !lastarg) { 539 config.dbnum = atoi(argv[i+1]); 540 i++; 541 } else if (!strcmp(argv[i],"-a") && !lastarg) { 542 config.auth = argv[i+1]; 543 i++; 544 } else if (!strcmp(argv[i],"--raw")) { 545 config.raw_output = 1; 546 } else if (!strcmp(argv[i],"--latency")) { 547 config.latency_mode = 1; 548 } else if (!strcmp(argv[i],"-d") && !lastarg) { 549 sdsfree(config.mb_delim); 550 config.mb_delim = sdsnew(argv[i+1]); 551 i++; 552 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { 553 sds version = cliVersion(); 554 printf("redis-cli %s\n", version); 555 sdsfree(version); 556 exit(0); 557 } else { 558 break; 559 } 560 } 561 return i; 562 } 563 564 static sds readArgFromStdin(void) { 565 char buf[1024]; 566 sds arg = sdsempty(); 567 568 while(1) { 569 int nread = read(fileno(stdin),buf,1024); 570 571 if (nread == 0) break; 572 else if (nread == -1) { 573 perror("Reading from standard input"); 574 exit(1); 575 } 576 arg = sdscatlen(arg,buf,nread); 577 } 578 return arg; 579 } 580 581 static void usage() { 582 sds version = cliVersion(); 583 fprintf(stderr, 584 "redis-cli %s\n" 585 "\n" 586 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" 587 " -h <hostname> Server hostname (default: 127.0.0.1)\n" 588 " -p <port> Server port (default: 6379)\n" 589 " -s <socket> Server socket (overrides hostname and port)\n" 590 " -a <password> Password to use when connecting to the server\n" 591 " -r <repeat> Execute specified command N times\n" 592 " -i <interval> When -r is used, waits <interval> seconds per command.\n" 593 " It is possible to specify sub-second times like -i 0.1.\n" 594 " -n <db> Database number\n" 595 " -x Read last argument from STDIN\n" 596 " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n" 597 " --raw Use raw formatting for replies (default when STDOUT is not a tty)\n" 598 " --latency Enter a special mode continuously sampling latency.\n" 599 " --help Output this help and exit\n" 600 " --version Output version and exit\n" 601 "\n" 602 "Examples:\n" 603 " cat /etc/passwd | redis-cli -x set mypasswd\n" 604 " redis-cli get mypasswd\n" 605 " redis-cli -r 100 lpush mylist x\n" 606 " redis-cli -r 100 -i 1 info | grep used_memory_human:\n" 607 "\n" 608 "When no command is given, redis-cli starts in interactive mode.\n" 609 "Type \"help\" in interactive mode for information on available commands.\n" 610 "\n", 611 version); 612 sdsfree(version); 613 exit(1); 614 } 615 616 /* Turn the plain C strings into Sds strings */ 617 static char **convertToSds(int count, char** args) { 618 int j; 619 char **sds = zmalloc(sizeof(char*)*count); 620 621 for(j = 0; j < count; j++) 622 sds[j] = sdsnew(args[j]); 623 624 return sds; 625 } 626 627 #define LINE_BUFLEN 4096 628 static void repl() { 629 sds historyfile = NULL; 630 int history = 0; 631 char *line; 632 int argc; 633 sds *argv; 634 635 config.interactive = 1; 636 linenoiseSetCompletionCallback(completionCallback); 637 638 /* Only use history when stdin is a tty. */ 639 if (isatty(fileno(stdin))) { 640 history = 1; 641 642 if (getenv("HOME") != NULL) { 643 historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME")); 644 linenoiseHistoryLoad(historyfile); 645 } 646 } 647 648 cliRefreshPrompt(); 649 while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) { 650 if (line[0] != '\0') { 651 argv = sdssplitargs(line,&argc); 652 if (history) linenoiseHistoryAdd(line); 653 if (historyfile) linenoiseHistorySave(historyfile); 654 655 if (argv == NULL) { 656 printf("Invalid argument(s)\n"); 657 free(line); 658 continue; 659 } else if (argc > 0) { 660 if (strcasecmp(argv[0],"quit") == 0 || 661 strcasecmp(argv[0],"exit") == 0) 662 { 663 exit(0); 664 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { 665 sdsfree(config.hostip); 666 config.hostip = sdsnew(argv[1]); 667 config.hostport = atoi(argv[2]); 668 cliConnect(1); 669 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { 670 linenoiseClearScreen(); 671 } else { 672 long long start_time = mstime(), elapsed; 673 int repeat, skipargs = 0; 674 675 repeat = atoi(argv[0]); 676 if (argc > 1 && repeat) { 677 skipargs = 1; 678 } else { 679 repeat = 1; 680 } 681 682 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 683 != REDIS_OK) 684 { 685 cliConnect(1); 686 687 /* If we still cannot send the command print error. 688 * We'll try to reconnect the next time. */ 689 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 690 != REDIS_OK) 691 cliPrintContextError(); 692 } 693 elapsed = mstime()-start_time; 694 if (elapsed >= 500) { 695 printf("(%.2fs)\n",(double)elapsed/1000); 696 } 697 } 698 } 699 /* Free the argument vector */ 700 while(argc--) sdsfree(argv[argc]); 701 zfree(argv); 702 } 703 /* linenoise() returns malloc-ed lines like readline() */ 704 free(line); 705 } 706 exit(0); 707 } 708 709 static int noninteractive(int argc, char **argv) { 710 int retval = 0; 711 if (config.stdinarg) { 712 argv = zrealloc(argv, (argc+1)*sizeof(char*)); 713 argv[argc] = readArgFromStdin(); 714 retval = cliSendCommand(argc+1, argv, config.repeat); 715 } else { 716 /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ 717 retval = cliSendCommand(argc, argv, config.repeat); 718 } 719 return retval; 720 } 721 722 static void latencyMode(void) { 723 redisReply *reply; 724 long long start, latency, min, max, tot, count = 0; 725 double avg; 726 727 if (!context) exit(1); 728 while(1) { 729 start = mstime(); 730 reply = redisCommand(context,"PING"); 731 if (reply == NULL) { 732 fprintf(stderr,"\nI/O error\n"); 733 exit(1); 734 } 735 latency = mstime()-start; 736 freeReplyObject(reply); 737 count++; 738 if (count == 1) { 739 min = max = tot = latency; 740 avg = (double) latency; 741 } else { 742 if (latency < min) min = latency; 743 if (latency > max) max = latency; 744 tot += latency; 745 avg = (double) tot/count; 746 } 747 printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)", 748 min, max, avg, count); 749 fflush(stdout); 750 usleep(10000); 751 } 752 } 753 754 int main(int argc, char **argv) { 755 int firstarg; 756 757 config.hostip = sdsnew("127.0.0.1"); 758 config.hostport = 6379; 759 config.hostsocket = NULL; 760 config.repeat = 1; 761 config.interval = 0; 762 config.dbnum = 0; 763 config.interactive = 0; 764 config.shutdown = 0; 765 config.monitor_mode = 0; 766 config.pubsub_mode = 0; 767 config.latency_mode = 0; 768 config.stdinarg = 0; 769 config.auth = NULL; 770 config.raw_output = !isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL); 771 config.mb_delim = sdsnew("\n"); 772 cliInitHelp(); 773 774 firstarg = parseOptions(argc,argv); 775 argc -= firstarg; 776 argv += firstarg; 777 778 /* Start in latency mode if appropriate */ 779 if (config.latency_mode) { 780 cliConnect(0); 781 latencyMode(); 782 } 783 784 /* Start interactive mode when no command is provided */ 785 if (argc == 0) { 786 /* Note that in repl mode we don't abort on connection error. 787 * A new attempt will be performed for every command send. */ 788 cliConnect(0); 789 repl(); 790 } 791 792 /* Otherwise, we have some arguments to execute */ 793 if (cliConnect(0) != REDIS_OK) exit(1); 794 return noninteractive(argc,convertToSds(argc,argv)); 795 }