xref: /redis-3.2.3/src/redis-cli.c (revision 0b748e91)
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