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