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