xref: /redis-3.2.3/src/redis-cli.c (revision 0b748e91)
1e2641e09Santirez /* Redis CLI (command line interface)
2e2641e09Santirez  *
34365e5b2Santirez  * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
4e2641e09Santirez  * All rights reserved.
5e2641e09Santirez  *
6e2641e09Santirez  * Redistribution and use in source and binary forms, with or without
7e2641e09Santirez  * modification, are permitted provided that the following conditions are met:
8e2641e09Santirez  *
9e2641e09Santirez  *   * Redistributions of source code must retain the above copyright notice,
10e2641e09Santirez  *     this list of conditions and the following disclaimer.
11e2641e09Santirez  *   * Redistributions in binary form must reproduce the above copyright
12e2641e09Santirez  *     notice, this list of conditions and the following disclaimer in the
13e2641e09Santirez  *     documentation and/or other materials provided with the distribution.
14e2641e09Santirez  *   * Neither the name of Redis nor the names of its contributors may be used
15e2641e09Santirez  *     to endorse or promote products derived from this software without
16e2641e09Santirez  *     specific prior written permission.
17e2641e09Santirez  *
18e2641e09Santirez  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19e2641e09Santirez  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20e2641e09Santirez  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21e2641e09Santirez  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22e2641e09Santirez  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23e2641e09Santirez  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24e2641e09Santirez  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25e2641e09Santirez  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26e2641e09Santirez  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27e2641e09Santirez  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28e2641e09Santirez  * POSSIBILITY OF SUCH DAMAGE.
29e2641e09Santirez  */
30e2641e09Santirez 
31e2641e09Santirez #include "fmacros.h"
32185cabdaSantirez #include "version.h"
33e2641e09Santirez 
34e2641e09Santirez #include <stdio.h>
35e2641e09Santirez #include <string.h>
36e2641e09Santirez #include <stdlib.h>
379f8dcfe6SAlex Suraci #include <signal.h>
38e2641e09Santirez #include <unistd.h>
39e9f0419cSantirez #include <time.h>
40e2641e09Santirez #include <ctype.h>
41c0b3d423Santirez #include <errno.h>
42b4b62c34SPieter Noordhuis #include <sys/stat.h>
433ce014c7Santirez #include <sys/time.h>
4441945ba6SPieter Noordhuis #include <assert.h>
45a0c24821Santirez #include <fcntl.h>
4609aa55a3Santirez #include <limits.h>
472860cf41Santirez #include <math.h>
48e2641e09Santirez 
49f15df8baSOran Agra #include <hiredis.h>
50f15df8baSOran Agra #include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
51e2641e09Santirez #include "zmalloc.h"
52e2641e09Santirez #include "linenoise.h"
535397f2b5STj Holowaychuk #include "help.h"
54088c508aSantirez #include "anet.h"
55088c508aSantirez #include "ae.h"
56e2641e09Santirez 
5732f80e2fSantirez #define UNUSED(V) ((void) V)
58e2641e09Santirez 
5960893c6cSantirez #define OUTPUT_STANDARD 0
6060893c6cSantirez #define OUTPUT_RAW 1
6160893c6cSantirez #define OUTPUT_CSV 2
623b397441Santirez #define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
63c1d67ea9Santirez #define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
643ab83219SCharles Hooper #define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE"
653ab83219SCharles Hooper #define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
66bbf93108Santirez #define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE"
67bbf93108Santirez #define REDIS_CLI_RCFILE_DEFAULT ".redisclirc"
6860893c6cSantirez 
69f638f045Santirez /* --latency-dist palettes. */
70f638f045Santirez int spectrum_palette_color_size = 19;
71f638f045Santirez int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196};
72f638f045Santirez 
73f638f045Santirez int spectrum_palette_mono_size = 13;
74f638f045Santirez int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253};
75f638f045Santirez 
76f638f045Santirez /* The actual palette in use. */
77f638f045Santirez int *spectrum_palette;
78f638f045Santirez int spectrum_palette_size;
79f638f045Santirez 
807fc4ce13SPieter Noordhuis static redisContext *context;
81e2641e09Santirez static struct config {
82e2641e09Santirez     char *hostip;
83e2641e09Santirez     int hostport;
847e91f971SPieter Noordhuis     char *hostsocket;
85e2641e09Santirez     long repeat;
8618f63d8dSantirez     long interval;
87e2641e09Santirez     int dbnum;
885d15b520SPieter Noordhuis     int interactive;
89e2641e09Santirez     int shutdown;
90e2641e09Santirez     int monitor_mode;
91e2641e09Santirez     int pubsub_mode;
9243071993Santirez     int latency_mode;
932860cf41Santirez     int latency_dist_mode;
940280c2f2Santirez     int latency_history;
95bd128f79Santirez     int lru_test_mode;
96bd128f79Santirez     long long lru_test_sample_size;
97623131d4Santirez     int cluster_mode;
98623131d4Santirez     int cluster_reissue_command;
99b8283ab2Santirez     int slave_mode;
100088c508aSantirez     int pipe_mode;
1011135e9faSantirez     int pipe_timeout;
102a0c24821Santirez     int getrdb_mode;
10309aa55a3Santirez     int stat_mode;
104994c5b26Santirez     int scan_mode;
105c1d67ea9Santirez     int intrinsic_latency_mode;
106c1d67ea9Santirez     int intrinsic_latency_duration;
107994c5b26Santirez     char *pattern;
108a0c24821Santirez     char *rdb_filename;
109f26761aaSantirez     int bigkeys;
110bc63407bSantirez     int stdinarg; /* get last arg from stdin. (-x option) */
111e2641e09Santirez     char *auth;
11260893c6cSantirez     int output; /* output mode, see OUTPUT_* defines */
11365add0a3SPieter Noordhuis     sds mb_delim;
114a5bd0848Santirez     char prompt[128];
115e2f31389Santirez     char *eval;
116def31636Santirez     int eval_ldb;
11775788d6aSantirez     int eval_ldb_sync;  /* Ask for synchronous mode of the Lua debugger. */
11875788d6aSantirez     int eval_ldb_end;   /* Lua debugging session ended. */
1196cbd5596Santirez     int enable_ldb_on_eval; /* Handle manual SCRIPT DEBUG + EVAL commands. */
1200042fb0eSMatt Stancliff     int last_cmd_type;
121e2641e09Santirez } config;
122e2641e09Santirez 
123bbf93108Santirez /* User preferences. */
124bbf93108Santirez static struct pref {
125bbf93108Santirez     int hints;
126bbf93108Santirez } pref;
127bbf93108Santirez 
12820c2a38aSMatt Stancliff static volatile sig_atomic_t force_cancel_loop = 0;
12923f08510Scubicdaiya static void usage(void);
13021648473SMatt Stancliff static void slaveMode(void);
13111fd0c42Santirez char *redisGitSHA1(void);
132c392edf5SPieter Noordhuis char *redisGitDirty(void);
133029dc0d9Santirez static int cliConnect(int force);
134e2641e09Santirez 
1353ce014c7Santirez /*------------------------------------------------------------------------------
1363ce014c7Santirez  * Utility functions
1373ce014c7Santirez  *--------------------------------------------------------------------------- */
1383ce014c7Santirez 
ustime(void)139dcac007bSantirez static long long ustime(void) {
1403ce014c7Santirez     struct timeval tv;
141dcac007bSantirez     long long ust;
1423ce014c7Santirez 
1433ce014c7Santirez     gettimeofday(&tv, NULL);
144dcac007bSantirez     ust = ((long long)tv.tv_sec)*1000000;
145dcac007bSantirez     ust += tv.tv_usec;
146dcac007bSantirez     return ust;
147dcac007bSantirez }
148dcac007bSantirez 
mstime(void)149dcac007bSantirez static long long mstime(void) {
150dcac007bSantirez     return ustime()/1000;
1513ce014c7Santirez }
1523ce014c7Santirez 
cliRefreshPrompt(void)1533f4eef21SPieter Noordhuis static void cliRefreshPrompt(void) {
154a5bd0848Santirez     int len;
155a5bd0848Santirez 
156def31636Santirez     if (config.eval_ldb) return;
157a5bd0848Santirez     if (config.hostsocket != NULL)
158a5bd0848Santirez         len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
159a5bd0848Santirez                        config.hostsocket);
1603f4eef21SPieter Noordhuis     else
161ce269ad3Santirez         len = anetFormatAddr(config.prompt, sizeof(config.prompt),
162a5bd0848Santirez                            config.hostip, config.hostport);
163a5bd0848Santirez     /* Add [dbnum] if needed */
164*0b748e91Santirez     if (config.dbnum != 0)
165a5bd0848Santirez         len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]",
166a5bd0848Santirez             config.dbnum);
167a5bd0848Santirez     snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
1683f4eef21SPieter Noordhuis }
1693f4eef21SPieter Noordhuis 
170bbf93108Santirez /* Return the name of the dotfile for the specified 'dotfilename'.
171bbf93108Santirez  * Normally it just concatenates user $HOME to the file specified
172bbf93108Santirez  * in 'dotfilename'. However if the environment varialbe 'envoverride'
173bbf93108Santirez  * is set, its value is taken as the path.
174bbf93108Santirez  *
175bbf93108Santirez  * The function returns NULL (if the file is /dev/null or cannot be
176bbf93108Santirez  * obtained for some error), or an SDS string that must be freed by
177bbf93108Santirez  * the user. */
getDotfilePath(char * envoverride,char * dotfilename)178bbf93108Santirez static sds getDotfilePath(char *envoverride, char *dotfilename) {
1793ab83219SCharles Hooper     char *path = NULL;
180bbf93108Santirez     sds dotPath = NULL;
1813ab83219SCharles Hooper 
182bbf93108Santirez     /* Check the env for a dotfile override. */
183bbf93108Santirez     path = getenv(envoverride);
1843ab83219SCharles Hooper     if (path != NULL && *path != '\0') {
1853ab83219SCharles Hooper         if (!strcmp("/dev/null", path)) {
1863ab83219SCharles Hooper             return NULL;
1873ab83219SCharles Hooper         }
1883ab83219SCharles Hooper 
189bbf93108Santirez         /* If the env is set, return it. */
190bbf93108Santirez         dotPath = sdsnew(path);
1913ab83219SCharles Hooper     } else {
1923ab83219SCharles Hooper         char *home = getenv("HOME");
1933ab83219SCharles Hooper         if (home != NULL && *home != '\0') {
194bbf93108Santirez             /* If no override is set use $HOME/<dotfilename>. */
195bbf93108Santirez             dotPath = sdscatprintf(sdsempty(), "%s/%s", home, dotfilename);
1963ab83219SCharles Hooper         }
1973ab83219SCharles Hooper     }
198bbf93108Santirez     return dotPath;
1993ab83219SCharles Hooper }
2003ab83219SCharles Hooper 
2013ce014c7Santirez /*------------------------------------------------------------------------------
202a2a69d58SPieter Noordhuis  * Help functions
203a2a69d58SPieter Noordhuis  *--------------------------------------------------------------------------- */
204a2a69d58SPieter Noordhuis 
205b2cc45bfSPieter Noordhuis #define CLI_HELP_COMMAND 1
206b2cc45bfSPieter Noordhuis #define CLI_HELP_GROUP 2
207b2cc45bfSPieter Noordhuis 
208b2cc45bfSPieter Noordhuis typedef struct {
209b2cc45bfSPieter Noordhuis     int type;
210b2cc45bfSPieter Noordhuis     int argc;
211b2cc45bfSPieter Noordhuis     sds *argv;
212b2cc45bfSPieter Noordhuis     sds full;
213b2cc45bfSPieter Noordhuis 
214b2cc45bfSPieter Noordhuis     /* Only used for help on commands */
215b2cc45bfSPieter Noordhuis     struct commandHelp *org;
216b2cc45bfSPieter Noordhuis } helpEntry;
217b2cc45bfSPieter Noordhuis 
218b2cc45bfSPieter Noordhuis static helpEntry *helpEntries;
219b2cc45bfSPieter Noordhuis static int helpEntriesLen;
220b2cc45bfSPieter Noordhuis 
cliVersion(void)22123f08510Scubicdaiya static sds cliVersion(void) {
222c392edf5SPieter Noordhuis     sds version;
223c392edf5SPieter Noordhuis     version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
224c392edf5SPieter Noordhuis 
225c392edf5SPieter Noordhuis     /* Add git commit and working tree status when available */
226c392edf5SPieter Noordhuis     if (strtoll(redisGitSHA1(),NULL,16)) {
227c392edf5SPieter Noordhuis         version = sdscatprintf(version, " (git:%s", redisGitSHA1());
228c392edf5SPieter Noordhuis         if (strtoll(redisGitDirty(),NULL,10))
229c392edf5SPieter Noordhuis             version = sdscatprintf(version, "-dirty");
230c392edf5SPieter Noordhuis         version = sdscat(version, ")");
231c392edf5SPieter Noordhuis     }
232c392edf5SPieter Noordhuis     return version;
233c392edf5SPieter Noordhuis }
234c392edf5SPieter Noordhuis 
cliInitHelp(void)23523f08510Scubicdaiya static void cliInitHelp(void) {
236b2cc45bfSPieter Noordhuis     int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
237b2cc45bfSPieter Noordhuis     int groupslen = sizeof(commandGroups)/sizeof(char*);
238b2cc45bfSPieter Noordhuis     int i, len, pos = 0;
239b2cc45bfSPieter Noordhuis     helpEntry tmp;
240b2cc45bfSPieter Noordhuis 
241b2cc45bfSPieter Noordhuis     helpEntriesLen = len = commandslen+groupslen;
242029dc0d9Santirez     helpEntries = zmalloc(sizeof(helpEntry)*len);
243b2cc45bfSPieter Noordhuis 
244b2cc45bfSPieter Noordhuis     for (i = 0; i < groupslen; i++) {
245b2cc45bfSPieter Noordhuis         tmp.argc = 1;
246029dc0d9Santirez         tmp.argv = zmalloc(sizeof(sds));
247b2cc45bfSPieter Noordhuis         tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
248b2cc45bfSPieter Noordhuis         tmp.full = tmp.argv[0];
249b2cc45bfSPieter Noordhuis         tmp.type = CLI_HELP_GROUP;
250b2cc45bfSPieter Noordhuis         tmp.org = NULL;
251b2cc45bfSPieter Noordhuis         helpEntries[pos++] = tmp;
252b2cc45bfSPieter Noordhuis     }
253b2cc45bfSPieter Noordhuis 
254b2cc45bfSPieter Noordhuis     for (i = 0; i < commandslen; i++) {
255b2cc45bfSPieter Noordhuis         tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
256b2cc45bfSPieter Noordhuis         tmp.full = sdsnew(commandHelp[i].name);
257b2cc45bfSPieter Noordhuis         tmp.type = CLI_HELP_COMMAND;
258b2cc45bfSPieter Noordhuis         tmp.org = &commandHelp[i];
259b2cc45bfSPieter Noordhuis         helpEntries[pos++] = tmp;
260b2cc45bfSPieter Noordhuis     }
261b2cc45bfSPieter Noordhuis }
262b2cc45bfSPieter Noordhuis 
263029dc0d9Santirez /* cliInitHelp() setups the helpEntries array with the command and group
264029dc0d9Santirez  * names from the help.h file. However the Redis instance we are connecting
265029dc0d9Santirez  * to may support more commands, so this function integrates the previous
266029dc0d9Santirez  * entries with additional entries obtained using the COMMAND command
267029dc0d9Santirez  * available in recent versions of Redis. */
cliIntegrateHelp(void)268029dc0d9Santirez static void cliIntegrateHelp(void) {
269029dc0d9Santirez     if (cliConnect(0) == REDIS_ERR) return;
270029dc0d9Santirez 
271029dc0d9Santirez     redisReply *reply = redisCommand(context, "COMMAND");
272029dc0d9Santirez     if(reply == NULL || reply->type != REDIS_REPLY_ARRAY) return;
273029dc0d9Santirez 
274029dc0d9Santirez     /* Scan the array reported by COMMAND and fill only the entries that
275029dc0d9Santirez      * don't already match what we have. */
276029dc0d9Santirez     for (size_t j = 0; j < reply->elements; j++) {
277029dc0d9Santirez         redisReply *entry = reply->element[j];
278029dc0d9Santirez         char *cmdname = entry->element[0]->str;
279029dc0d9Santirez         int i;
280029dc0d9Santirez 
281029dc0d9Santirez         for (i = 0; i < helpEntriesLen; i++) {
282029dc0d9Santirez             helpEntry *he = helpEntries+i;
283029dc0d9Santirez             if (!strcasecmp(he->argv[0],cmdname))
284029dc0d9Santirez                 break;
285029dc0d9Santirez         }
286029dc0d9Santirez         if (i != helpEntriesLen) continue;
287029dc0d9Santirez 
288029dc0d9Santirez         helpEntriesLen++;
289029dc0d9Santirez         helpEntries = zrealloc(helpEntries,sizeof(helpEntry)*helpEntriesLen);
290029dc0d9Santirez         helpEntry *new = helpEntries+(helpEntriesLen-1);
291029dc0d9Santirez 
292029dc0d9Santirez         new->argc = 1;
293029dc0d9Santirez         new->argv = zmalloc(sizeof(sds));
294029dc0d9Santirez         new->argv[0] = sdsnew(cmdname);
295029dc0d9Santirez         new->full = new->argv[0];
296029dc0d9Santirez         new->type = CLI_HELP_COMMAND;
297029dc0d9Santirez         sdstoupper(new->argv[0]);
298029dc0d9Santirez 
299029dc0d9Santirez         struct commandHelp *ch = zmalloc(sizeof(*ch));
300029dc0d9Santirez         ch->name = new->argv[0];
301029dc0d9Santirez         ch->params = sdsempty();
302029dc0d9Santirez         int args = llabs(entry->element[1]->integer);
303029dc0d9Santirez         if (entry->element[3]->integer == 1) {
304029dc0d9Santirez             ch->params = sdscat(ch->params,"key ");
305029dc0d9Santirez             args--;
306029dc0d9Santirez         }
307029dc0d9Santirez         while(args--) ch->params = sdscat(ch->params,"arg ");
308029dc0d9Santirez         if (entry->element[1]->integer < 0)
309029dc0d9Santirez             ch->params = sdscat(ch->params,"...options...");
310029dc0d9Santirez         ch->summary = "Help not available";
311029dc0d9Santirez         ch->group = 0;
312029dc0d9Santirez         ch->since = "not known";
313029dc0d9Santirez         new->org = ch;
314029dc0d9Santirez     }
315029dc0d9Santirez     freeReplyObject(reply);
316029dc0d9Santirez }
317029dc0d9Santirez 
318a2a69d58SPieter Noordhuis /* Output command help to stdout. */
cliOutputCommandHelp(struct commandHelp * help,int group)31941945ba6SPieter Noordhuis static void cliOutputCommandHelp(struct commandHelp *help, int group) {
32041945ba6SPieter Noordhuis     printf("\r\n  \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
32141945ba6SPieter Noordhuis     printf("  \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
32241945ba6SPieter Noordhuis     printf("  \x1b[33msince:\x1b[0m %s\r\n", help->since);
32341945ba6SPieter Noordhuis     if (group) {
32441945ba6SPieter Noordhuis         printf("  \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]);
32541945ba6SPieter Noordhuis     }
326a2a69d58SPieter Noordhuis }
327a2a69d58SPieter Noordhuis 
32841945ba6SPieter Noordhuis /* Print generic help. */
cliOutputGenericHelp(void)32923f08510Scubicdaiya static void cliOutputGenericHelp(void) {
330c392edf5SPieter Noordhuis     sds version = cliVersion();
33141945ba6SPieter Noordhuis     printf(
332bbf93108Santirez         "redis-cli %s\n"
333bbf93108Santirez         "To get help about Redis commands type:\n"
334bbf93108Santirez         "      \"help @<group>\" to get a list of commands in <group>\n"
335bbf93108Santirez         "      \"help <command>\" for help on <command>\n"
336bbf93108Santirez         "      \"help <tab>\" to get a list of possible help topics\n"
337bbf93108Santirez         "      \"quit\" to exit\n"
338bbf93108Santirez         "\n"
339bbf93108Santirez         "To set redis-cli perferences:\n"
340bbf93108Santirez         "      \":set hints\" enable online hints\n"
341bbf93108Santirez         "      \":set nohints\" disable online hints\n"
342bbf93108Santirez         "Set your preferences in ~/.redisclirc\n",
343c392edf5SPieter Noordhuis         version
34441945ba6SPieter Noordhuis     );
345c392edf5SPieter Noordhuis     sdsfree(version);
346a2a69d58SPieter Noordhuis }
347a2a69d58SPieter Noordhuis 
348a2a69d58SPieter Noordhuis /* Output all command help, filtering by group or command name. */
cliOutputHelp(int argc,char ** argv)34941945ba6SPieter Noordhuis static void cliOutputHelp(int argc, char **argv) {
350b2cc45bfSPieter Noordhuis     int i, j, len;
35141945ba6SPieter Noordhuis     int group = -1;
352b2cc45bfSPieter Noordhuis     helpEntry *entry;
353b2cc45bfSPieter Noordhuis     struct commandHelp *help;
354a2a69d58SPieter Noordhuis 
35541945ba6SPieter Noordhuis     if (argc == 0) {
35641945ba6SPieter Noordhuis         cliOutputGenericHelp();
357a2a69d58SPieter Noordhuis         return;
35841945ba6SPieter Noordhuis     } else if (argc > 0 && argv[0][0] == '@') {
35941945ba6SPieter Noordhuis         len = sizeof(commandGroups)/sizeof(char*);
36041945ba6SPieter Noordhuis         for (i = 0; i < len; i++) {
36141945ba6SPieter Noordhuis             if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) {
36241945ba6SPieter Noordhuis                 group = i;
36341945ba6SPieter Noordhuis                 break;
36441945ba6SPieter Noordhuis             }
36541945ba6SPieter Noordhuis         }
366a2a69d58SPieter Noordhuis     }
367a2a69d58SPieter Noordhuis 
36841945ba6SPieter Noordhuis     assert(argc > 0);
369b2cc45bfSPieter Noordhuis     for (i = 0; i < helpEntriesLen; i++) {
370b2cc45bfSPieter Noordhuis         entry = &helpEntries[i];
371b2cc45bfSPieter Noordhuis         if (entry->type != CLI_HELP_COMMAND) continue;
372b2cc45bfSPieter Noordhuis 
373b2cc45bfSPieter Noordhuis         help = entry->org;
374a2a69d58SPieter Noordhuis         if (group == -1) {
375b2cc45bfSPieter Noordhuis             /* Compare all arguments */
376b2cc45bfSPieter Noordhuis             if (argc == entry->argc) {
377b2cc45bfSPieter Noordhuis                 for (j = 0; j < argc; j++) {
378b2cc45bfSPieter Noordhuis                     if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
379b2cc45bfSPieter Noordhuis                 }
380b2cc45bfSPieter Noordhuis                 if (j == argc) {
38141945ba6SPieter Noordhuis                     cliOutputCommandHelp(help,1);
382a2a69d58SPieter Noordhuis                 }
383b2cc45bfSPieter Noordhuis             }
384a2a69d58SPieter Noordhuis         } else {
385a2a69d58SPieter Noordhuis             if (group == help->group) {
38641945ba6SPieter Noordhuis                 cliOutputCommandHelp(help,0);
387a2a69d58SPieter Noordhuis             }
388a2a69d58SPieter Noordhuis         }
389a2a69d58SPieter Noordhuis     }
39041945ba6SPieter Noordhuis     printf("\r\n");
39141945ba6SPieter Noordhuis }
39241945ba6SPieter Noordhuis 
3933fd3fca0Santirez /* Linenoise completion callback. */
completionCallback(const char * buf,linenoiseCompletions * lc)39441945ba6SPieter Noordhuis static void completionCallback(const char *buf, linenoiseCompletions *lc) {
39541945ba6SPieter Noordhuis     size_t startpos = 0;
39641945ba6SPieter Noordhuis     int mask;
39741945ba6SPieter Noordhuis     int i;
39841945ba6SPieter Noordhuis     size_t matchlen;
399b2cc45bfSPieter Noordhuis     sds tmp;
40041945ba6SPieter Noordhuis 
40141945ba6SPieter Noordhuis     if (strncasecmp(buf,"help ",5) == 0) {
40241945ba6SPieter Noordhuis         startpos = 5;
40341945ba6SPieter Noordhuis         while (isspace(buf[startpos])) startpos++;
404b2cc45bfSPieter Noordhuis         mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
40541945ba6SPieter Noordhuis     } else {
406b2cc45bfSPieter Noordhuis         mask = CLI_HELP_COMMAND;
40741945ba6SPieter Noordhuis     }
40841945ba6SPieter Noordhuis 
409b2cc45bfSPieter Noordhuis     for (i = 0; i < helpEntriesLen; i++) {
410b2cc45bfSPieter Noordhuis         if (!(helpEntries[i].type & mask)) continue;
41141945ba6SPieter Noordhuis 
41241945ba6SPieter Noordhuis         matchlen = strlen(buf+startpos);
413b2cc45bfSPieter Noordhuis         if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
414b2cc45bfSPieter Noordhuis             tmp = sdsnewlen(buf,startpos);
415b2cc45bfSPieter Noordhuis             tmp = sdscat(tmp,helpEntries[i].full);
41641945ba6SPieter Noordhuis             linenoiseAddCompletion(lc,tmp);
417b2cc45bfSPieter Noordhuis             sdsfree(tmp);
41841945ba6SPieter Noordhuis         }
41941945ba6SPieter Noordhuis     }
420a2a69d58SPieter Noordhuis }
421a2a69d58SPieter Noordhuis 
4223fd3fca0Santirez /* Linenoise hints callback. */
hintsCallback(const char * buf,int * color,int * bold)4233fd3fca0Santirez static char *hintsCallback(const char *buf, int *color, int *bold) {
424bbf93108Santirez     if (!pref.hints) return NULL;
425bbf93108Santirez 
4263fd3fca0Santirez     int i, argc, buflen = strlen(buf);
4273fd3fca0Santirez     sds *argv = sdssplitargs(buf,&argc);
4283fd3fca0Santirez     int endspace = buflen && isspace(buf[buflen-1]);
4293fd3fca0Santirez 
4303fd3fca0Santirez     /* Check if the argument list is empty and return ASAP. */
4313fd3fca0Santirez     if (argc == 0) {
4323fd3fca0Santirez         sdsfreesplitres(argv,argc);
4333fd3fca0Santirez         return NULL;
4343fd3fca0Santirez     }
4353fd3fca0Santirez 
4363fd3fca0Santirez     for (i = 0; i < helpEntriesLen; i++) {
4373fd3fca0Santirez         if (!(helpEntries[i].type & CLI_HELP_COMMAND)) continue;
4383fd3fca0Santirez 
4393fd3fca0Santirez         if (strcasecmp(argv[0],helpEntries[i].full) == 0)
4403fd3fca0Santirez         {
4413fd3fca0Santirez             *color = 90;
4423fd3fca0Santirez             *bold = 0;
4433fd3fca0Santirez             sds hint = sdsnew(helpEntries[i].org->params);
4443fd3fca0Santirez 
4453fd3fca0Santirez             /* Remove arguments from the returned hint to show only the
4463fd3fca0Santirez              * ones the user did not yet typed. */
4473fd3fca0Santirez             int toremove = argc-1;
4483fd3fca0Santirez             while(toremove > 0 && sdslen(hint)) {
4493fd3fca0Santirez                 if (hint[0] == '[') break;
4503fd3fca0Santirez                 if (hint[0] == ' ') toremove--;
4513fd3fca0Santirez                 sdsrange(hint,1,-1);
4523fd3fca0Santirez             }
4533fd3fca0Santirez 
4543fd3fca0Santirez             /* Add an initial space if needed. */
4553fd3fca0Santirez             if (!endspace) {
4563fd3fca0Santirez                 sds newhint = sdsnewlen(" ",1);
4573fd3fca0Santirez                 newhint = sdscatsds(newhint,hint);
4583fd3fca0Santirez                 sdsfree(hint);
4593fd3fca0Santirez                 hint = newhint;
4603fd3fca0Santirez             }
4613fd3fca0Santirez 
4623fd3fca0Santirez             sdsfreesplitres(argv,argc);
4633fd3fca0Santirez             return hint;
4643fd3fca0Santirez         }
4653fd3fca0Santirez     }
4663fd3fca0Santirez     sdsfreesplitres(argv,argc);
4673fd3fca0Santirez     return NULL;
4683fd3fca0Santirez }
4693fd3fca0Santirez 
freeHintsCallback(void * ptr)4703fd3fca0Santirez static void freeHintsCallback(void *ptr) {
4713fd3fca0Santirez     sdsfree(ptr);
4723fd3fca0Santirez }
4733fd3fca0Santirez 
474a2a69d58SPieter Noordhuis /*------------------------------------------------------------------------------
4753ce014c7Santirez  * Networking / parsing
4763ce014c7Santirez  *--------------------------------------------------------------------------- */
4773ce014c7Santirez 
4787fc4ce13SPieter Noordhuis /* Send AUTH command to the server */
cliAuth(void)47925407363SNan Xiao static int cliAuth(void) {
4807fc4ce13SPieter Noordhuis     redisReply *reply;
4817fc4ce13SPieter Noordhuis     if (config.auth == NULL) return REDIS_OK;
4827fc4ce13SPieter Noordhuis 
4837fc4ce13SPieter Noordhuis     reply = redisCommand(context,"AUTH %s",config.auth);
4847fc4ce13SPieter Noordhuis     if (reply != NULL) {
4857fc4ce13SPieter Noordhuis         freeReplyObject(reply);
4867fc4ce13SPieter Noordhuis         return REDIS_OK;
4877fc4ce13SPieter Noordhuis     }
4887fc4ce13SPieter Noordhuis     return REDIS_ERR;
4897fc4ce13SPieter Noordhuis }
4907fc4ce13SPieter Noordhuis 
4917fc4ce13SPieter Noordhuis /* Send SELECT dbnum to the server */
cliSelect(void)49225407363SNan Xiao static int cliSelect(void) {
4937fc4ce13SPieter Noordhuis     redisReply *reply;
4947fc4ce13SPieter Noordhuis     if (config.dbnum == 0) return REDIS_OK;
4957fc4ce13SPieter Noordhuis 
49696e34b3cSPieter Noordhuis     reply = redisCommand(context,"SELECT %d",config.dbnum);
4977fc4ce13SPieter Noordhuis     if (reply != NULL) {
498bbc1cd0bSMatt Stancliff         int result = REDIS_OK;
499bbc1cd0bSMatt Stancliff         if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
5007fc4ce13SPieter Noordhuis         freeReplyObject(reply);
501bbc1cd0bSMatt Stancliff         return result;
5027fc4ce13SPieter Noordhuis     }
5037fc4ce13SPieter Noordhuis     return REDIS_ERR;
5047fc4ce13SPieter Noordhuis }
5057fc4ce13SPieter Noordhuis 
5069d09ce39Sguiquanz /* Connect to the server. If force is not zero the connection is performed
507c0b3d423Santirez  * even if there is already a connected socket. */
cliConnect(int force)508c0b3d423Santirez static int cliConnect(int force) {
5097fc4ce13SPieter Noordhuis     if (context == NULL || force) {
51002de5d99Santirez         if (context != NULL) {
5117fc4ce13SPieter Noordhuis             redisFree(context);
51202de5d99Santirez         }
513e2641e09Santirez 
5147e91f971SPieter Noordhuis         if (config.hostsocket == NULL) {
5157fc4ce13SPieter Noordhuis             context = redisConnect(config.hostip,config.hostport);
5167e91f971SPieter Noordhuis         } else {
5177fc4ce13SPieter Noordhuis             context = redisConnectUnix(config.hostsocket);
5187e91f971SPieter Noordhuis         }
5197fc4ce13SPieter Noordhuis 
5207fc4ce13SPieter Noordhuis         if (context->err) {
5217e91f971SPieter Noordhuis             fprintf(stderr,"Could not connect to Redis at ");
5227e91f971SPieter Noordhuis             if (config.hostsocket == NULL)
5237fc4ce13SPieter Noordhuis                 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
5247e91f971SPieter Noordhuis             else
5257fc4ce13SPieter Noordhuis                 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
5267fc4ce13SPieter Noordhuis             redisFree(context);
5277fc4ce13SPieter Noordhuis             context = NULL;
5287fc4ce13SPieter Noordhuis             return REDIS_ERR;
529e2641e09Santirez         }
530e2641e09Santirez 
5313b397441Santirez         /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
5323b397441Santirez          * in order to prevent timeouts caused by the execution of long
5333b397441Santirez          * commands. At the same time this improves the detection of real
5343b397441Santirez          * errors. */
5353b397441Santirez         anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
5363b397441Santirez 
5377fc4ce13SPieter Noordhuis         /* Do AUTH and select the right DB. */
5387fc4ce13SPieter Noordhuis         if (cliAuth() != REDIS_OK)
5397fc4ce13SPieter Noordhuis             return REDIS_ERR;
5407fc4ce13SPieter Noordhuis         if (cliSelect() != REDIS_OK)
5417fc4ce13SPieter Noordhuis             return REDIS_ERR;
5427fc4ce13SPieter Noordhuis     }
5437fc4ce13SPieter Noordhuis     return REDIS_OK;
5447fc4ce13SPieter Noordhuis }
545e2641e09Santirez 
cliPrintContextError(void)54623f08510Scubicdaiya static void cliPrintContextError(void) {
5477fc4ce13SPieter Noordhuis     if (context == NULL) return;
5487fc4ce13SPieter Noordhuis     fprintf(stderr,"Error: %s\n",context->errstr);
5497fc4ce13SPieter Noordhuis }
550e2641e09Santirez 
cliFormatReplyTTY(redisReply * r,char * prefix)55165add0a3SPieter Noordhuis static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
5527fc4ce13SPieter Noordhuis     sds out = sdsempty();
5537fc4ce13SPieter Noordhuis     switch (r->type) {
5547fc4ce13SPieter Noordhuis     case REDIS_REPLY_ERROR:
55565add0a3SPieter Noordhuis         out = sdscatprintf(out,"(error) %s\n", r->str);
556e2641e09Santirez     break;
5577fc4ce13SPieter Noordhuis     case REDIS_REPLY_STATUS:
5587fc4ce13SPieter Noordhuis         out = sdscat(out,r->str);
5597fc4ce13SPieter Noordhuis         out = sdscat(out,"\n");
5607fc4ce13SPieter Noordhuis     break;
5617fc4ce13SPieter Noordhuis     case REDIS_REPLY_INTEGER:
56265add0a3SPieter Noordhuis         out = sdscatprintf(out,"(integer) %lld\n",r->integer);
5637fc4ce13SPieter Noordhuis     break;
5647fc4ce13SPieter Noordhuis     case REDIS_REPLY_STRING:
565e2641e09Santirez         /* If you are producing output for the standard output we want
566e2641e09Santirez         * a more interesting output with quoted characters and so forth */
5677fc4ce13SPieter Noordhuis         out = sdscatrepr(out,r->str,r->len);
5687fc4ce13SPieter Noordhuis         out = sdscat(out,"\n");
5697fc4ce13SPieter Noordhuis     break;
5707fc4ce13SPieter Noordhuis     case REDIS_REPLY_NIL:
5717fc4ce13SPieter Noordhuis         out = sdscat(out,"(nil)\n");
5727fc4ce13SPieter Noordhuis     break;
5737fc4ce13SPieter Noordhuis     case REDIS_REPLY_ARRAY:
5747fc4ce13SPieter Noordhuis         if (r->elements == 0) {
5757fc4ce13SPieter Noordhuis             out = sdscat(out,"(empty list or set)\n");
576c0b3d423Santirez         } else {
577cfcd5d6dSPieter Noordhuis             unsigned int i, idxlen = 0;
578cfcd5d6dSPieter Noordhuis             char _prefixlen[16];
579cfcd5d6dSPieter Noordhuis             char _prefixfmt[16];
580cfcd5d6dSPieter Noordhuis             sds _prefix;
5817fc4ce13SPieter Noordhuis             sds tmp;
5827fc4ce13SPieter Noordhuis 
583cfcd5d6dSPieter Noordhuis             /* Calculate chars needed to represent the largest index */
584cfcd5d6dSPieter Noordhuis             i = r->elements;
585cfcd5d6dSPieter Noordhuis             do {
586cfcd5d6dSPieter Noordhuis                 idxlen++;
587cfcd5d6dSPieter Noordhuis                 i /= 10;
588cfcd5d6dSPieter Noordhuis             } while(i);
589cfcd5d6dSPieter Noordhuis 
590cfcd5d6dSPieter Noordhuis             /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
591cfcd5d6dSPieter Noordhuis             memset(_prefixlen,' ',idxlen+2);
592cfcd5d6dSPieter Noordhuis             _prefixlen[idxlen+2] = '\0';
593cfcd5d6dSPieter Noordhuis             _prefix = sdscat(sdsnew(prefix),_prefixlen);
594cfcd5d6dSPieter Noordhuis 
595cfcd5d6dSPieter Noordhuis             /* Setup prefix format for every entry */
5964916d205Santirez             snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud) ",idxlen);
597cfcd5d6dSPieter Noordhuis 
5987fc4ce13SPieter Noordhuis             for (i = 0; i < r->elements; i++) {
599cfcd5d6dSPieter Noordhuis                 /* Don't use the prefix for the first element, as the parent
600cfcd5d6dSPieter Noordhuis                  * caller already prepended the index number. */
601cfcd5d6dSPieter Noordhuis                 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
602cfcd5d6dSPieter Noordhuis 
603cfcd5d6dSPieter Noordhuis                 /* Format the multi bulk entry */
60465add0a3SPieter Noordhuis                 tmp = cliFormatReplyTTY(r->element[i],_prefix);
6057fc4ce13SPieter Noordhuis                 out = sdscatlen(out,tmp,sdslen(tmp));
6067fc4ce13SPieter Noordhuis                 sdsfree(tmp);
6077fc4ce13SPieter Noordhuis             }
608cfcd5d6dSPieter Noordhuis             sdsfree(_prefix);
6097fc4ce13SPieter Noordhuis         }
6107fc4ce13SPieter Noordhuis     break;
6117fc4ce13SPieter Noordhuis     default:
6127fc4ce13SPieter Noordhuis         fprintf(stderr,"Unknown reply type: %d\n", r->type);
613e2641e09Santirez         exit(1);
614e2641e09Santirez     }
6157fc4ce13SPieter Noordhuis     return out;
616e2641e09Santirez }
617e2641e09Santirez 
isColorTerm(void)6180d43a421Santirez int isColorTerm(void) {
6190d43a421Santirez     char *t = getenv("TERM");
6200d43a421Santirez     return t != NULL && strstr(t,"xterm") != NULL;
6210d43a421Santirez }
6220d43a421Santirez 
6230d43a421Santirez /* Helpe  function for sdsCatColorizedLdbReply() appending colorize strings
6240d43a421Santirez  * to an SDS string. */
sdscatcolor(sds o,char * s,size_t len,char * color)6250d43a421Santirez sds sdscatcolor(sds o, char *s, size_t len, char *color) {
6260d43a421Santirez     if (!isColorTerm()) return sdscatlen(o,s,len);
6270d43a421Santirez 
6280d43a421Santirez     int bold = strstr(color,"bold") != NULL;
6290d43a421Santirez     int ccode = 37; /* Defaults to white. */
6300d43a421Santirez     if (strstr(color,"red")) ccode = 31;
6310d43a421Santirez     else if (strstr(color,"red")) ccode = 31;
6320d43a421Santirez     else if (strstr(color,"green")) ccode = 32;
6330d43a421Santirez     else if (strstr(color,"yellow")) ccode = 33;
6340d43a421Santirez     else if (strstr(color,"blue")) ccode = 34;
6350d43a421Santirez     else if (strstr(color,"magenta")) ccode = 35;
6360d43a421Santirez     else if (strstr(color,"cyan")) ccode = 36;
6370d43a421Santirez     else if (strstr(color,"white")) ccode = 37;
6380d43a421Santirez 
6390d43a421Santirez     o = sdscatfmt(o,"\033[%i;%i;49m",bold,ccode);
6400d43a421Santirez     o = sdscatlen(o,s,len);
6410d43a421Santirez     o = sdscat(o,"\033[0m");
6420d43a421Santirez     return o;
6430d43a421Santirez }
6440d43a421Santirez 
6450d43a421Santirez /* Colorize Lua debugger status replies according to the prefix they
6460d43a421Santirez  * have. */
sdsCatColorizedLdbReply(sds o,char * s,size_t len)6470d43a421Santirez sds sdsCatColorizedLdbReply(sds o, char *s, size_t len) {
6480d43a421Santirez     char *color = "white";
6490d43a421Santirez 
6508f8c6b3bSantirez     if (strstr(s,"<debug>")) color = "bold";
6510d43a421Santirez     if (strstr(s,"<redis>")) color = "green";
6520d43a421Santirez     if (strstr(s,"<reply>")) color = "cyan";
6530d43a421Santirez     if (strstr(s,"<error>")) color = "red";
65479c6e689Santirez     if (strstr(s,"<hint>")) color = "bold";
655878725deSantirez     if (strstr(s,"<value>") || strstr(s,"<retval>")) color = "magenta";
6568a0020f1Santirez     if (len > 4 && isdigit(s[3])) {
6578a0020f1Santirez         if (s[1] == '>') color = "yellow"; /* Current line. */
6588a0020f1Santirez         else if (s[2] == '#') color = "bold"; /* Break point. */
6590d43a421Santirez     }
6600d43a421Santirez     return sdscatcolor(o,s,len,color);
6610d43a421Santirez }
6620d43a421Santirez 
cliFormatReplyRaw(redisReply * r)66365add0a3SPieter Noordhuis static sds cliFormatReplyRaw(redisReply *r) {
66465add0a3SPieter Noordhuis     sds out = sdsempty(), tmp;
66565add0a3SPieter Noordhuis     size_t i;
66665add0a3SPieter Noordhuis 
66765add0a3SPieter Noordhuis     switch (r->type) {
66865add0a3SPieter Noordhuis     case REDIS_REPLY_NIL:
66965add0a3SPieter Noordhuis         /* Nothing... */
67065add0a3SPieter Noordhuis         break;
67165add0a3SPieter Noordhuis     case REDIS_REPLY_ERROR:
672ecc91094Santirez         out = sdscatlen(out,r->str,r->len);
673ecc91094Santirez         out = sdscatlen(out,"\n",1);
674ecc91094Santirez         break;
67565add0a3SPieter Noordhuis     case REDIS_REPLY_STATUS:
67665add0a3SPieter Noordhuis     case REDIS_REPLY_STRING:
6770d43a421Santirez         if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
6780d43a421Santirez             /* The Lua debugger replies with arrays of simple (status)
6790d43a421Santirez              * strings. We colorize the output for more fun if this
6800d43a421Santirez              * is a debugging session. */
681629acd61Santirez 
682629acd61Santirez             /* Detect the end of a debugging session. */
683629acd61Santirez             if (strstr(r->str,"<endsession>") == r->str) {
6846cbd5596Santirez                 config.enable_ldb_on_eval = 0;
685629acd61Santirez                 config.eval_ldb = 0;
686629acd61Santirez                 config.eval_ldb_end = 1; /* Signal the caller session ended. */
687629acd61Santirez                 config.output = OUTPUT_STANDARD;
688629acd61Santirez                 cliRefreshPrompt();
689629acd61Santirez             } else {
6900d43a421Santirez                 out = sdsCatColorizedLdbReply(out,r->str,r->len);
691629acd61Santirez             }
6920d43a421Santirez         } else {
69365add0a3SPieter Noordhuis             out = sdscatlen(out,r->str,r->len);
6940d43a421Santirez         }
69565add0a3SPieter Noordhuis         break;
69665add0a3SPieter Noordhuis     case REDIS_REPLY_INTEGER:
69765add0a3SPieter Noordhuis         out = sdscatprintf(out,"%lld",r->integer);
69865add0a3SPieter Noordhuis         break;
69965add0a3SPieter Noordhuis     case REDIS_REPLY_ARRAY:
70065add0a3SPieter Noordhuis         for (i = 0; i < r->elements; i++) {
70165add0a3SPieter Noordhuis             if (i > 0) out = sdscat(out,config.mb_delim);
70265add0a3SPieter Noordhuis             tmp = cliFormatReplyRaw(r->element[i]);
70365add0a3SPieter Noordhuis             out = sdscatlen(out,tmp,sdslen(tmp));
70465add0a3SPieter Noordhuis             sdsfree(tmp);
70565add0a3SPieter Noordhuis         }
70665add0a3SPieter Noordhuis         break;
70765add0a3SPieter Noordhuis     default:
70865add0a3SPieter Noordhuis         fprintf(stderr,"Unknown reply type: %d\n", r->type);
70965add0a3SPieter Noordhuis         exit(1);
71065add0a3SPieter Noordhuis     }
71165add0a3SPieter Noordhuis     return out;
71265add0a3SPieter Noordhuis }
71365add0a3SPieter Noordhuis 
cliFormatReplyCSV(redisReply * r)71460893c6cSantirez static sds cliFormatReplyCSV(redisReply *r) {
71560893c6cSantirez     unsigned int i;
71660893c6cSantirez 
71760893c6cSantirez     sds out = sdsempty();
71860893c6cSantirez     switch (r->type) {
71960893c6cSantirez     case REDIS_REPLY_ERROR:
72060893c6cSantirez         out = sdscat(out,"ERROR,");
72160893c6cSantirez         out = sdscatrepr(out,r->str,strlen(r->str));
72260893c6cSantirez     break;
72360893c6cSantirez     case REDIS_REPLY_STATUS:
72460893c6cSantirez         out = sdscatrepr(out,r->str,r->len);
72560893c6cSantirez     break;
72660893c6cSantirez     case REDIS_REPLY_INTEGER:
72760893c6cSantirez         out = sdscatprintf(out,"%lld",r->integer);
72860893c6cSantirez     break;
72960893c6cSantirez     case REDIS_REPLY_STRING:
73060893c6cSantirez         out = sdscatrepr(out,r->str,r->len);
73160893c6cSantirez     break;
73260893c6cSantirez     case REDIS_REPLY_NIL:
7336ec5f1f7Smattcollier         out = sdscat(out,"NIL");
73460893c6cSantirez     break;
73560893c6cSantirez     case REDIS_REPLY_ARRAY:
73660893c6cSantirez         for (i = 0; i < r->elements; i++) {
73760893c6cSantirez             sds tmp = cliFormatReplyCSV(r->element[i]);
73860893c6cSantirez             out = sdscatlen(out,tmp,sdslen(tmp));
73960893c6cSantirez             if (i != r->elements-1) out = sdscat(out,",");
74060893c6cSantirez             sdsfree(tmp);
74160893c6cSantirez         }
74260893c6cSantirez     break;
74360893c6cSantirez     default:
74460893c6cSantirez         fprintf(stderr,"Unknown reply type: %d\n", r->type);
74560893c6cSantirez         exit(1);
74660893c6cSantirez     }
74760893c6cSantirez     return out;
74860893c6cSantirez }
74960893c6cSantirez 
cliReadReply(int output_raw_strings)75065add0a3SPieter Noordhuis static int cliReadReply(int output_raw_strings) {
7518ce39260SPieter Noordhuis     void *_reply;
7527fc4ce13SPieter Noordhuis     redisReply *reply;
7530d44d507Santirez     sds out = NULL;
754623131d4Santirez     int output = 1;
755e2641e09Santirez 
7568ce39260SPieter Noordhuis     if (redisGetReply(context,&_reply) != REDIS_OK) {
757233d24a7SDov Murik         if (config.shutdown) {
758233d24a7SDov Murik             redisFree(context);
759233d24a7SDov Murik             context = NULL;
7607fc4ce13SPieter Noordhuis             return REDIS_OK;
761233d24a7SDov Murik         }
7627fc4ce13SPieter Noordhuis         if (config.interactive) {
7637fc4ce13SPieter Noordhuis             /* Filter cases where we should reconnect */
764e10c5444SMatt Stancliff             if (context->err == REDIS_ERR_IO &&
765e10c5444SMatt Stancliff                 (errno == ECONNRESET || errno == EPIPE))
7667fc4ce13SPieter Noordhuis                 return REDIS_ERR;
7677fc4ce13SPieter Noordhuis             if (context->err == REDIS_ERR_EOF)
7687fc4ce13SPieter Noordhuis                 return REDIS_ERR;
769e2641e09Santirez         }
770a45f9a1aSantirez         cliPrintContextError();
771a45f9a1aSantirez         exit(1);
7727fc4ce13SPieter Noordhuis         return REDIS_ERR; /* avoid compiler warning */
7737fc4ce13SPieter Noordhuis     }
7747fc4ce13SPieter Noordhuis 
7758ce39260SPieter Noordhuis     reply = (redisReply*)_reply;
776623131d4Santirez 
7770042fb0eSMatt Stancliff     config.last_cmd_type = reply->type;
7780042fb0eSMatt Stancliff 
7790d44d507Santirez     /* Check if we need to connect to a different node and reissue the
7800d44d507Santirez      * request. */
781623131d4Santirez     if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
782623131d4Santirez         (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK")))
783623131d4Santirez     {
784623131d4Santirez         char *p = reply->str, *s;
785623131d4Santirez         int slot;
786623131d4Santirez 
787623131d4Santirez         output = 0;
788623131d4Santirez         /* Comments show the position of the pointer as:
789623131d4Santirez          *
790623131d4Santirez          * [S] for pointer 's'
791623131d4Santirez          * [P] for pointer 'p'
792623131d4Santirez          */
793623131d4Santirez         s = strchr(p,' ');      /* MOVED[S]3999 127.0.0.1:6381 */
794623131d4Santirez         p = strchr(s+1,' ');    /* MOVED[S]3999[P]127.0.0.1:6381 */
795623131d4Santirez         *p = '\0';
796623131d4Santirez         slot = atoi(s+1);
797787d5ab9SDavid Cavar         s = strrchr(p+1,':');    /* MOVED 3999[P]127.0.0.1[S]6381 */
798623131d4Santirez         *s = '\0';
799623131d4Santirez         sdsfree(config.hostip);
800623131d4Santirez         config.hostip = sdsnew(p+1);
801623131d4Santirez         config.hostport = atoi(s+1);
802623131d4Santirez         if (config.interactive)
803623131d4Santirez             printf("-> Redirected to slot [%d] located at %s:%d\n",
804623131d4Santirez                 slot, config.hostip, config.hostport);
805623131d4Santirez         config.cluster_reissue_command = 1;
8066b641f3aSantirez         cliRefreshPrompt();
807623131d4Santirez     }
808623131d4Santirez 
809623131d4Santirez     if (output) {
81065add0a3SPieter Noordhuis         if (output_raw_strings) {
81165add0a3SPieter Noordhuis             out = cliFormatReplyRaw(reply);
81265add0a3SPieter Noordhuis         } else {
81360893c6cSantirez             if (config.output == OUTPUT_RAW) {
81465add0a3SPieter Noordhuis                 out = cliFormatReplyRaw(reply);
81565add0a3SPieter Noordhuis                 out = sdscat(out,"\n");
81660893c6cSantirez             } else if (config.output == OUTPUT_STANDARD) {
81765add0a3SPieter Noordhuis                 out = cliFormatReplyTTY(reply,"");
81860893c6cSantirez             } else if (config.output == OUTPUT_CSV) {
81960893c6cSantirez                 out = cliFormatReplyCSV(reply);
82060893c6cSantirez                 out = sdscat(out,"\n");
82165add0a3SPieter Noordhuis             }
82265add0a3SPieter Noordhuis         }
8237fc4ce13SPieter Noordhuis         fwrite(out,sdslen(out),1,stdout);
8247fc4ce13SPieter Noordhuis         sdsfree(out);
825623131d4Santirez     }
82665add0a3SPieter Noordhuis     freeReplyObject(reply);
8277fc4ce13SPieter Noordhuis     return REDIS_OK;
828e2641e09Santirez }
829e2641e09Santirez 
cliSendCommand(int argc,char ** argv,int repeat)830e2641e09Santirez static int cliSendCommand(int argc, char **argv, int repeat) {
831e2641e09Santirez     char *command = argv[0];
8327fc4ce13SPieter Noordhuis     size_t *argvlen;
83365add0a3SPieter Noordhuis     int j, output_raw;
834e2641e09Santirez 
83502de5d99Santirez     if (!config.eval_ldb && /* In debugging mode, let's pass "help" to Redis. */
83602de5d99Santirez         (!strcasecmp(command,"help") || !strcasecmp(command,"?"))) {
8374eb3b3e9Santirez         cliOutputHelp(--argc, ++argv);
8384eb3b3e9Santirez         return REDIS_OK;
8394eb3b3e9Santirez     }
8404eb3b3e9Santirez 
841a45f9a1aSantirez     if (context == NULL) return REDIS_ERR;
842efcf948cSantirez 
843ecc91094Santirez     output_raw = 0;
844ecc91094Santirez     if (!strcasecmp(command,"info") ||
8450f64080dSantirez         (argc >= 2 && !strcasecmp(command,"debug") &&
8463ede6c7aSOran Agra                       ((!strcasecmp(argv[1],"jemalloc") && !strcasecmp(argv[2],"info")) ||
8470f64080dSantirez                        !strcasecmp(argv[1],"htstats"))) ||
848ecc91094Santirez         (argc == 2 && !strcasecmp(command,"cluster") &&
849ecc91094Santirez                       (!strcasecmp(argv[1],"nodes") ||
8503cd12b56Santirez                        !strcasecmp(argv[1],"info"))) ||
8513cd12b56Santirez         (argc == 2 && !strcasecmp(command,"client") &&
852aa16f87bSantirez                        !strcasecmp(argv[1],"list")) ||
853aa16f87bSantirez         (argc == 3 && !strcasecmp(command,"latency") &&
8542a232dfaSantirez                        !strcasecmp(argv[1],"graph")) ||
8552a232dfaSantirez         (argc == 2 && !strcasecmp(command,"latency") &&
8562a232dfaSantirez                        !strcasecmp(argv[1],"doctor")))
857ecc91094Santirez     {
858ecc91094Santirez         output_raw = 1;
859ecc91094Santirez     }
860ecc91094Santirez 
861e2641e09Santirez     if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
862e2641e09Santirez     if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
863e2641e09Santirez     if (!strcasecmp(command,"subscribe") ||
864e2641e09Santirez         !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
86521648473SMatt Stancliff     if (!strcasecmp(command,"sync") ||
86621648473SMatt Stancliff         !strcasecmp(command,"psync")) config.slave_mode = 1;
867e2641e09Santirez 
8686cbd5596Santirez     /* When the user manually calls SCRIPT DEBUG, setup the activation of
8696cbd5596Santirez      * debugging mode on the next eval if needed. */
8706cbd5596Santirez     if (argc == 3 && !strcasecmp(argv[0],"script") &&
8716cbd5596Santirez                      !strcasecmp(argv[1],"debug"))
8726cbd5596Santirez     {
8736cbd5596Santirez         if (!strcasecmp(argv[2],"yes") || !strcasecmp(argv[2],"sync")) {
8746cbd5596Santirez             config.enable_ldb_on_eval = 1;
8756cbd5596Santirez         } else {
8766cbd5596Santirez             config.enable_ldb_on_eval = 0;
8776cbd5596Santirez         }
8786cbd5596Santirez     }
8796cbd5596Santirez 
8806cbd5596Santirez     /* Actually activate LDB on EVAL if needed. */
8816cbd5596Santirez     if (!strcasecmp(command,"eval") && config.enable_ldb_on_eval) {
8826cbd5596Santirez         config.eval_ldb = 1;
8836cbd5596Santirez         config.output = OUTPUT_RAW;
8846cbd5596Santirez     }
8856cbd5596Santirez 
8867fc4ce13SPieter Noordhuis     /* Setup argument length */
887029dc0d9Santirez     argvlen = zmalloc(argc*sizeof(size_t));
8887fc4ce13SPieter Noordhuis     for (j = 0; j < argc; j++)
8897fc4ce13SPieter Noordhuis         argvlen[j] = sdslen(argv[j]);
890e2641e09Santirez 
891e2641e09Santirez     while(repeat--) {
8927fc4ce13SPieter Noordhuis         redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
893e2641e09Santirez         while (config.monitor_mode) {
89465add0a3SPieter Noordhuis             if (cliReadReply(output_raw) != REDIS_OK) exit(1);
895d9d8ccabSantirez             fflush(stdout);
896e2641e09Santirez         }
897e2641e09Santirez 
898e2641e09Santirez         if (config.pubsub_mode) {
89960893c6cSantirez             if (config.output != OUTPUT_RAW)
9007fc4ce13SPieter Noordhuis                 printf("Reading messages... (press Ctrl-C to quit)\n");
901e2641e09Santirez             while (1) {
90265add0a3SPieter Noordhuis                 if (cliReadReply(output_raw) != REDIS_OK) exit(1);
903e2641e09Santirez             }
904e2641e09Santirez         }
905e2641e09Santirez 
90621648473SMatt Stancliff         if (config.slave_mode) {
90721648473SMatt Stancliff             printf("Entering slave output mode...  (press Ctrl-C to quit)\n");
90821648473SMatt Stancliff             slaveMode();
90921648473SMatt Stancliff             config.slave_mode = 0;
910029dc0d9Santirez             zfree(argvlen);
91121648473SMatt Stancliff             return REDIS_ERR;  /* Error = slaveMode lost connection to master */
91221648473SMatt Stancliff         }
91321648473SMatt Stancliff 
91433753a73SPieter Noordhuis         if (cliReadReply(output_raw) != REDIS_OK) {
915029dc0d9Santirez             zfree(argvlen);
9167fc4ce13SPieter Noordhuis             return REDIS_ERR;
91796e34b3cSPieter Noordhuis         } else {
91896e34b3cSPieter Noordhuis             /* Store database number when SELECT was successfully executed. */
9191158386bSsskorgal             if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) {
92096e34b3cSPieter Noordhuis                 config.dbnum = atoi(argv[1]);
9213f4eef21SPieter Noordhuis                 cliRefreshPrompt();
922bbc1cd0bSMatt Stancliff             } else if (!strcasecmp(command,"auth") && argc == 2) {
923bbc1cd0bSMatt Stancliff                 cliSelect();
9243f4eef21SPieter Noordhuis             }
925e2641e09Santirez         }
92618f63d8dSantirez         if (config.interval) usleep(config.interval);
92718f63d8dSantirez         fflush(stdout); /* Make it grep friendly */
92833753a73SPieter Noordhuis     }
92933753a73SPieter Noordhuis 
930029dc0d9Santirez     zfree(argvlen);
9317fc4ce13SPieter Noordhuis     return REDIS_OK;
932e2641e09Santirez }
933e2641e09Santirez 
934ca23b2a6Santirez /* Send a command reconnecting the link if needed. */
reconnectingRedisCommand(redisContext * c,const char * fmt,...)935ca23b2a6Santirez static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ...) {
93609aa55a3Santirez     redisReply *reply = NULL;
93709aa55a3Santirez     int tries = 0;
938ca23b2a6Santirez     va_list ap;
93909aa55a3Santirez 
94009aa55a3Santirez     assert(!c->err);
94109aa55a3Santirez     while(reply == NULL) {
94209aa55a3Santirez         while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
943ca23b2a6Santirez             printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
944ca23b2a6Santirez             printf("Reconnecting... %d\r", ++tries);
94509aa55a3Santirez             fflush(stdout);
94609aa55a3Santirez 
94709aa55a3Santirez             redisFree(c);
94809aa55a3Santirez             c = redisConnect(config.hostip,config.hostport);
94909aa55a3Santirez             usleep(1000000);
95009aa55a3Santirez         }
95109aa55a3Santirez 
952ca23b2a6Santirez         va_start(ap,fmt);
953ca23b2a6Santirez         reply = redisvCommand(c,fmt,ap);
954ca23b2a6Santirez         va_end(ap);
955ca23b2a6Santirez 
95609aa55a3Santirez         if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
95709aa55a3Santirez             fprintf(stderr, "Error: %s\n", c->errstr);
95809aa55a3Santirez             exit(1);
95909aa55a3Santirez         } else if (tries > 0) {
960ca23b2a6Santirez             printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
96109aa55a3Santirez         }
96209aa55a3Santirez     }
96309aa55a3Santirez 
96409aa55a3Santirez     context = c;
96509aa55a3Santirez     return reply;
96609aa55a3Santirez }
96709aa55a3Santirez 
9683ce014c7Santirez /*------------------------------------------------------------------------------
9693ce014c7Santirez  * User interface
9703ce014c7Santirez  *--------------------------------------------------------------------------- */
9713ce014c7Santirez 
parseOptions(int argc,char ** argv)972e2641e09Santirez static int parseOptions(int argc, char **argv) {
973e2641e09Santirez     int i;
974e2641e09Santirez 
975e2641e09Santirez     for (i = 1; i < argc; i++) {
976e2641e09Santirez         int lastarg = i==argc-1;
977e2641e09Santirez 
978e2641e09Santirez         if (!strcmp(argv[i],"-h") && !lastarg) {
979efcf948cSantirez             sdsfree(config.hostip);
980e2f31389Santirez             config.hostip = sdsnew(argv[++i]);
981e2641e09Santirez         } else if (!strcmp(argv[i],"-h") && lastarg) {
982e2641e09Santirez             usage();
983f18e059eSPieter Noordhuis         } else if (!strcmp(argv[i],"--help")) {
984f18e059eSPieter Noordhuis             usage();
985bc63407bSantirez         } else if (!strcmp(argv[i],"-x")) {
986bc63407bSantirez             config.stdinarg = 1;
987e2641e09Santirez         } else if (!strcmp(argv[i],"-p") && !lastarg) {
988e2f31389Santirez             config.hostport = atoi(argv[++i]);
9897e91f971SPieter Noordhuis         } else if (!strcmp(argv[i],"-s") && !lastarg) {
990e2f31389Santirez             config.hostsocket = argv[++i];
991e2641e09Santirez         } else if (!strcmp(argv[i],"-r") && !lastarg) {
992e2f31389Santirez             config.repeat = strtoll(argv[++i],NULL,10);
99318f63d8dSantirez         } else if (!strcmp(argv[i],"-i") && !lastarg) {
994e2f31389Santirez             double seconds = atof(argv[++i]);
99518f63d8dSantirez             config.interval = seconds*1000000;
996e2641e09Santirez         } else if (!strcmp(argv[i],"-n") && !lastarg) {
997e2f31389Santirez             config.dbnum = atoi(argv[++i]);
998e2641e09Santirez         } else if (!strcmp(argv[i],"-a") && !lastarg) {
999e2f31389Santirez             config.auth = argv[++i];
100065add0a3SPieter Noordhuis         } else if (!strcmp(argv[i],"--raw")) {
100160893c6cSantirez             config.output = OUTPUT_RAW;
100242655316SMatt Stancliff         } else if (!strcmp(argv[i],"--no-raw")) {
100342655316SMatt Stancliff             config.output = OUTPUT_STANDARD;
100460893c6cSantirez         } else if (!strcmp(argv[i],"--csv")) {
100560893c6cSantirez             config.output = OUTPUT_CSV;
100643071993Santirez         } else if (!strcmp(argv[i],"--latency")) {
100743071993Santirez             config.latency_mode = 1;
10082860cf41Santirez         } else if (!strcmp(argv[i],"--latency-dist")) {
10092860cf41Santirez             config.latency_dist_mode = 1;
1010f638f045Santirez         } else if (!strcmp(argv[i],"--mono")) {
1011f638f045Santirez             spectrum_palette = spectrum_palette_mono;
1012f638f045Santirez             spectrum_palette_size = spectrum_palette_mono_size;
10130280c2f2Santirez         } else if (!strcmp(argv[i],"--latency-history")) {
10140280c2f2Santirez             config.latency_mode = 1;
10150280c2f2Santirez             config.latency_history = 1;
1016bd128f79Santirez         } else if (!strcmp(argv[i],"--lru-test") && !lastarg) {
1017bd128f79Santirez             config.lru_test_mode = 1;
1018bd128f79Santirez             config.lru_test_sample_size = strtoll(argv[++i],NULL,10);
1019b8283ab2Santirez         } else if (!strcmp(argv[i],"--slave")) {
1020b8283ab2Santirez             config.slave_mode = 1;
102109aa55a3Santirez         } else if (!strcmp(argv[i],"--stat")) {
102209aa55a3Santirez             config.stat_mode = 1;
1023994c5b26Santirez         } else if (!strcmp(argv[i],"--scan")) {
1024994c5b26Santirez             config.scan_mode = 1;
10255580350aSantirez         } else if (!strcmp(argv[i],"--pattern") && !lastarg) {
1026994c5b26Santirez             config.pattern = argv[++i];
1027c1d67ea9Santirez         } else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
1028c1d67ea9Santirez             config.intrinsic_latency_mode = 1;
1029c1d67ea9Santirez             config.intrinsic_latency_duration = atoi(argv[++i]);
1030a0c24821Santirez         } else if (!strcmp(argv[i],"--rdb") && !lastarg) {
1031a0c24821Santirez             config.getrdb_mode = 1;
1032a0c24821Santirez             config.rdb_filename = argv[++i];
1033088c508aSantirez         } else if (!strcmp(argv[i],"--pipe")) {
1034088c508aSantirez             config.pipe_mode = 1;
10351135e9faSantirez         } else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) {
10361135e9faSantirez             config.pipe_timeout = atoi(argv[++i]);
1037f26761aaSantirez         } else if (!strcmp(argv[i],"--bigkeys")) {
1038f26761aaSantirez             config.bigkeys = 1;
1039e2f31389Santirez         } else if (!strcmp(argv[i],"--eval") && !lastarg) {
1040e2f31389Santirez             config.eval = argv[++i];
1041def31636Santirez         } else if (!strcmp(argv[i],"--ldb")) {
1042def31636Santirez             config.eval_ldb = 1;
104389bf9696Santirez             config.output = OUTPUT_RAW;
104475788d6aSantirez         } else if (!strcmp(argv[i],"--ldb-sync-mode")) {
104575788d6aSantirez             config.eval_ldb = 1;
104675788d6aSantirez             config.eval_ldb_sync = 1;
104775788d6aSantirez             config.output = OUTPUT_RAW;
1048623131d4Santirez         } else if (!strcmp(argv[i],"-c")) {
1049623131d4Santirez             config.cluster_mode = 1;
105028c07c7bSPieter Noordhuis         } else if (!strcmp(argv[i],"-d") && !lastarg) {
105128c07c7bSPieter Noordhuis             sdsfree(config.mb_delim);
1052e2f31389Santirez             config.mb_delim = sdsnew(argv[++i]);
1053f18e059eSPieter Noordhuis         } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
1054f18e059eSPieter Noordhuis             sds version = cliVersion();
1055f18e059eSPieter Noordhuis             printf("redis-cli %s\n", version);
1056f18e059eSPieter Noordhuis             sdsfree(version);
1057185cabdaSantirez             exit(0);
1058e2641e09Santirez         } else {
1059f8ae70cfSantirez             if (argv[i][0] == '-') {
1060f8ae70cfSantirez                 fprintf(stderr,
1061f8ae70cfSantirez                     "Unrecognized option or bad number of args for: '%s'\n",
1062f8ae70cfSantirez                     argv[i]);
1063f8ae70cfSantirez                 exit(1);
1064f8ae70cfSantirez             } else {
1065f8ae70cfSantirez                 /* Likely the command name, stop here. */
1066e2641e09Santirez                 break;
1067e2641e09Santirez             }
1068e2641e09Santirez         }
1069f8ae70cfSantirez     }
107057d38d54Santirez 
107157d38d54Santirez     /* --ldb requires --eval. */
107257d38d54Santirez     if (config.eval_ldb && config.eval == NULL) {
107357d38d54Santirez         fprintf(stderr,"Options --ldb and --ldb-sync-mode require --eval.\n");
107457d38d54Santirez         fprintf(stderr,"Try %s --help for more information.\n", argv[0]);
107557d38d54Santirez         exit(1);
107657d38d54Santirez     }
1077e2641e09Santirez     return i;
1078e2641e09Santirez }
1079e2641e09Santirez 
readArgFromStdin(void)1080e2641e09Santirez static sds readArgFromStdin(void) {
1081e2641e09Santirez     char buf[1024];
1082e2641e09Santirez     sds arg = sdsempty();
1083e2641e09Santirez 
1084e2641e09Santirez     while(1) {
1085e2641e09Santirez         int nread = read(fileno(stdin),buf,1024);
1086e2641e09Santirez 
1087e2641e09Santirez         if (nread == 0) break;
1088e2641e09Santirez         else if (nread == -1) {
1089e2641e09Santirez             perror("Reading from standard input");
1090e2641e09Santirez             exit(1);
1091e2641e09Santirez         }
1092e2641e09Santirez         arg = sdscatlen(arg,buf,nread);
1093e2641e09Santirez     }
1094e2641e09Santirez     return arg;
1095e2641e09Santirez }
1096e2641e09Santirez 
usage(void)109723f08510Scubicdaiya static void usage(void) {
1098f18e059eSPieter Noordhuis     sds version = cliVersion();
1099f18e059eSPieter Noordhuis     fprintf(stderr,
1100f18e059eSPieter Noordhuis "redis-cli %s\n"
1101f18e059eSPieter Noordhuis "\n"
1102f18e059eSPieter Noordhuis "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
11031cf532dcSantirez "  -h <hostname>      Server hostname (default: 127.0.0.1).\n"
11041cf532dcSantirez "  -p <port>          Server port (default: 6379).\n"
11051cf532dcSantirez "  -s <socket>        Server socket (overrides hostname and port).\n"
11061cf532dcSantirez "  -a <password>      Password to use when connecting to the server.\n"
11071cf532dcSantirez "  -r <repeat>        Execute specified command N times.\n"
110818f63d8dSantirez "  -i <interval>      When -r is used, waits <interval> seconds per command.\n"
11091cf532dcSantirez "                     It is possible to specify sub-second times like -i 0.1.\n"
11101cf532dcSantirez "  -n <db>            Database number.\n"
11111cf532dcSantirez "  -x                 Read last argument from STDIN.\n"
11121cf532dcSantirez "  -d <delimiter>     Multi-bulk delimiter in for raw formatting (default: \\n).\n"
11131cf532dcSantirez "  -c                 Enable cluster mode (follow -ASK and -MOVED redirections).\n"
11140280c2f2Santirez "  --raw              Use raw formatting for replies (default when STDOUT is\n"
11151cf532dcSantirez "                     not a tty).\n"
111642655316SMatt Stancliff "  --no-raw           Force formatted output even when STDOUT is not a tty.\n"
11171cf532dcSantirez "  --csv              Output in CSV format.\n"
1118e039791eSantirez "  --stat             Print rolling stats about server: mem, clients, ...\n"
11191cf532dcSantirez "  --latency          Enter a special mode continuously sampling latency.\n"
11200280c2f2Santirez "  --latency-history  Like --latency but tracking latency changes over time.\n"
11210280c2f2Santirez "                     Default time interval is 15 sec. Change it using -i.\n"
11222860cf41Santirez "  --latency-dist     Shows latency as a spectrum, requires xterm 256 colors.\n"
11232860cf41Santirez "                     Default time interval is 1 sec. Change it using -i.\n"
1124bd128f79Santirez "  --lru-test <keys>  Simulate a cache workload with an 80-20 distribution.\n"
11251cf532dcSantirez "  --slave            Simulate a slave showing commands received from the master.\n"
1126a0c24821Santirez "  --rdb <filename>   Transfer an RDB dump from remote server to local file.\n"
11271cf532dcSantirez "  --pipe             Transfer raw Redis protocol from stdin to server.\n"
11281cf532dcSantirez "  --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
11291135e9faSantirez "                     no reply is received within <n> seconds.\n"
11301135e9faSantirez "                     Default timeout: %d. Use 0 to wait forever.\n"
11311cf532dcSantirez "  --bigkeys          Sample Redis keys looking for big keys.\n"
11321cf532dcSantirez "  --scan             List all keys using the SCAN command.\n"
11331cf532dcSantirez "  --pattern <pat>    Useful with --scan to specify a SCAN pattern.\n"
1134c1d67ea9Santirez "  --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
1135c1d67ea9Santirez "                     The test will run for the specified amount of seconds.\n"
11361cf532dcSantirez "  --eval <file>      Send an EVAL command using the Lua script at <file>.\n"
1137def31636Santirez "  --ldb              Used with --eval enable the Redis Lua debugger.\n"
113875788d6aSantirez "  --ldb-sync-mode    Like --ldb but uses the synchronous Lua debugger, in\n"
113975788d6aSantirez "                     this mode the server is blocked and script changes are\n"
114075788d6aSantirez "                     are not rolled back from the server memory.\n"
11411cf532dcSantirez "  --help             Output this help and exit.\n"
11421cf532dcSantirez "  --version          Output version and exit.\n"
1143f18e059eSPieter Noordhuis "\n"
1144f18e059eSPieter Noordhuis "Examples:\n"
1145f18e059eSPieter Noordhuis "  cat /etc/passwd | redis-cli -x set mypasswd\n"
1146f18e059eSPieter Noordhuis "  redis-cli get mypasswd\n"
1147f18e059eSPieter Noordhuis "  redis-cli -r 100 lpush mylist x\n"
114818f63d8dSantirez "  redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
1149e2f31389Santirez "  redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
11501cf532dcSantirez "  redis-cli --scan --pattern '*:12345*'\n"
11511cf532dcSantirez "\n"
1152e2f31389Santirez "  (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
1153f18e059eSPieter Noordhuis "\n"
1154f18e059eSPieter Noordhuis "When no command is given, redis-cli starts in interactive mode.\n"
1155b8a63635Santirez "Type \"help\" in interactive mode for information on available commands\n"
1156b8a63635Santirez "and settings.\n"
1157f18e059eSPieter Noordhuis "\n",
1158c1d67ea9Santirez         version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
1159f18e059eSPieter Noordhuis     sdsfree(version);
1160e2641e09Santirez     exit(1);
1161e2641e09Santirez }
1162e2641e09Santirez 
1163e2641e09Santirez /* Turn the plain C strings into Sds strings */
convertToSds(int count,char ** args)1164e2641e09Santirez static char **convertToSds(int count, char** args) {
1165e2641e09Santirez   int j;
1166e2641e09Santirez   char **sds = zmalloc(sizeof(char*)*count);
1167e2641e09Santirez 
1168e2641e09Santirez   for(j = 0; j < count; j++)
1169e2641e09Santirez     sds[j] = sdsnew(args[j]);
1170e2641e09Santirez 
1171e2641e09Santirez   return sds;
1172e2641e09Santirez }
1173e2641e09Santirez 
issueCommandRepeat(int argc,char ** argv,long repeat)11747fcfbea0Sh0x91b static int issueCommandRepeat(int argc, char **argv, long repeat) {
11757fcfbea0Sh0x91b     while (1) {
11767fcfbea0Sh0x91b         config.cluster_reissue_command = 0;
11777fcfbea0Sh0x91b         if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
11787fcfbea0Sh0x91b             cliConnect(1);
11797fcfbea0Sh0x91b 
11807fcfbea0Sh0x91b             /* If we still cannot send the command print error.
11817fcfbea0Sh0x91b              * We'll try to reconnect the next time. */
11827fcfbea0Sh0x91b             if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
11837fcfbea0Sh0x91b                 cliPrintContextError();
11847fcfbea0Sh0x91b                 return REDIS_ERR;
11857fcfbea0Sh0x91b             }
11867fcfbea0Sh0x91b          }
11877fcfbea0Sh0x91b          /* Issue the command again if we got redirected in cluster mode */
11887fcfbea0Sh0x91b          if (config.cluster_mode && config.cluster_reissue_command) {
11897fcfbea0Sh0x91b             cliConnect(1);
11907fcfbea0Sh0x91b          } else {
11917fcfbea0Sh0x91b              break;
11927fcfbea0Sh0x91b         }
11937fcfbea0Sh0x91b     }
11947fcfbea0Sh0x91b     return REDIS_OK;
11957fcfbea0Sh0x91b }
11967fcfbea0Sh0x91b 
issueCommand(int argc,char ** argv)11977fcfbea0Sh0x91b static int issueCommand(int argc, char **argv) {
11987fcfbea0Sh0x91b     return issueCommandRepeat(argc, argv, config.repeat);
11997fcfbea0Sh0x91b }
12007fcfbea0Sh0x91b 
12018a0020f1Santirez /* Split the user provided command into multiple SDS arguments.
12028a0020f1Santirez  * This function normally uses sdssplitargs() from sds.c which is able
12038a0020f1Santirez  * to understand "quoted strings", escapes and so forth. However when
12048a0020f1Santirez  * we are in Lua debugging mode and the "eval" command is used, we want
12058a0020f1Santirez  * the remaining Lua script (after "e " or "eval ") to be passed verbatim
12068a0020f1Santirez  * as a single big argument. */
cliSplitArgs(char * line,int * argc)12078a0020f1Santirez static sds *cliSplitArgs(char *line, int *argc) {
12088a0020f1Santirez     if (config.eval_ldb && (strstr(line,"eval ") == line ||
12098a0020f1Santirez                             strstr(line,"e ") == line))
12108a0020f1Santirez     {
1211b9429fd8Santirez         sds *argv = sds_malloc(sizeof(sds)*2);
12128a0020f1Santirez         *argc = 2;
12138a0020f1Santirez         int len = strlen(line);
12148a0020f1Santirez         int elen = line[1] == ' ' ? 2 : 5; /* "e " or "eval "? */
12158a0020f1Santirez         argv[0] = sdsnewlen(line,elen-1);
12168a0020f1Santirez         argv[1] = sdsnewlen(line+elen,len-elen);
12178a0020f1Santirez         return argv;
12188a0020f1Santirez     } else {
12198a0020f1Santirez         return sdssplitargs(line,argc);
12208a0020f1Santirez     }
12218a0020f1Santirez }
12228a0020f1Santirez 
1223bbf93108Santirez /* Set the CLI perferences. This function is invoked when an interactive
1224bbf93108Santirez  * ":command" is called, or when reading ~/.redisclirc file, in order to
1225bbf93108Santirez  * set user preferences. */
cliSetPreferences(char ** argv,int argc,int interactive)1226bbf93108Santirez void cliSetPreferences(char **argv, int argc, int interactive) {
1227bbf93108Santirez     if (!strcasecmp(argv[0],":set") && argc >= 2) {
1228bbf93108Santirez         if (!strcasecmp(argv[1],"hints")) pref.hints = 1;
1229bbf93108Santirez         else if (!strcasecmp(argv[1],"nohints")) pref.hints = 0;
1230bbf93108Santirez         else {
1231bbf93108Santirez             printf("%sunknown redis-cli preference '%s'\n",
1232bbf93108Santirez                 interactive ? "" : ".redisclirc: ",
1233bbf93108Santirez                 argv[1]);
1234bbf93108Santirez         }
1235bbf93108Santirez     } else {
1236bbf93108Santirez         printf("%sunknown redis-cli internal command '%s'\n",
1237bbf93108Santirez             interactive ? "" : ".redisclirc: ",
1238bbf93108Santirez             argv[0]);
1239bbf93108Santirez     }
1240bbf93108Santirez }
1241bbf93108Santirez 
1242bbf93108Santirez /* Load the ~/.redisclirc file if any. */
cliLoadPreferences(void)1243bbf93108Santirez void cliLoadPreferences(void) {
1244bbf93108Santirez     sds rcfile = getDotfilePath(REDIS_CLI_RCFILE_ENV,REDIS_CLI_RCFILE_DEFAULT);
1245bbf93108Santirez     if (rcfile == NULL) return;
1246bbf93108Santirez     FILE *fp = fopen(rcfile,"r");
1247bbf93108Santirez     char buf[1024];
1248bbf93108Santirez 
1249bbf93108Santirez     if (fp) {
1250bbf93108Santirez         while(fgets(buf,sizeof(buf),fp) != NULL) {
1251bbf93108Santirez             sds *argv;
1252bbf93108Santirez             int argc;
1253bbf93108Santirez 
1254bbf93108Santirez             argv = sdssplitargs(buf,&argc);
1255bbf93108Santirez             if (argc > 0) cliSetPreferences(argv,argc,0);
1256bbf93108Santirez             sdsfreesplitres(argv,argc);
1257bbf93108Santirez         }
1258bbf93108Santirez     }
1259bbf93108Santirez     sdsfree(rcfile);
1260bbf93108Santirez }
1261bbf93108Santirez 
repl(void)126223f08510Scubicdaiya static void repl(void) {
1263ca36b4abSPieter Noordhuis     sds historyfile = NULL;
1264ca36b4abSPieter Noordhuis     int history = 0;
1265cbce5171Santirez     char *line;
1266ca36b4abSPieter Noordhuis     int argc;
1267cbce5171Santirez     sds *argv;
1268e2641e09Santirez 
12695d15b520SPieter Noordhuis     config.interactive = 1;
1270429aff4eSantirez     linenoiseSetMultiLine(1);
127141945ba6SPieter Noordhuis     linenoiseSetCompletionCallback(completionCallback);
12723fd3fca0Santirez     linenoiseSetHintsCallback(hintsCallback);
12733fd3fca0Santirez     linenoiseSetFreeHintsCallback(freeHintsCallback);
1274ce260f73Santirez 
1275bbf93108Santirez     /* Only use history and load the rc file when stdin is a tty. */
1276ca36b4abSPieter Noordhuis     if (isatty(fileno(stdin))) {
1277bbf93108Santirez         historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV,REDIS_CLI_HISTFILE_DEFAULT);
12783ab83219SCharles Hooper         if (historyfile != NULL) {
1279ca36b4abSPieter Noordhuis             history = 1;
1280ca36b4abSPieter Noordhuis             linenoiseHistoryLoad(historyfile);
1281ca36b4abSPieter Noordhuis         }
1282bbf93108Santirez         cliLoadPreferences();
1283ca36b4abSPieter Noordhuis     }
1284ca36b4abSPieter Noordhuis 
12853f4eef21SPieter Noordhuis     cliRefreshPrompt();
12863f4eef21SPieter Noordhuis     while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
1287e2641e09Santirez         if (line[0] != '\0') {
12888a0020f1Santirez             argv = cliSplitArgs(line,&argc);
1289ca36b4abSPieter Noordhuis             if (history) linenoiseHistoryAdd(line);
1290ca36b4abSPieter Noordhuis             if (historyfile) linenoiseHistorySave(historyfile);
1291ca36b4abSPieter Noordhuis 
12920439d792SPieter Noordhuis             if (argv == NULL) {
12930439d792SPieter Noordhuis                 printf("Invalid argument(s)\n");
1294029dc0d9Santirez                 linenoiseFree(line);
12950439d792SPieter Noordhuis                 continue;
12960439d792SPieter Noordhuis             } else if (argc > 0) {
1297e2641e09Santirez                 if (strcasecmp(argv[0],"quit") == 0 ||
1298e2641e09Santirez                     strcasecmp(argv[0],"exit") == 0)
1299c0b3d423Santirez                 {
1300e2641e09Santirez                     exit(0);
1301bbf93108Santirez                 } else if (argv[0][0] == ':') {
1302bbf93108Santirez                     cliSetPreferences(argv,argc,1);
1303bbf93108Santirez                     continue;
13045535784bSantirez                 } else if (strcasecmp(argv[0],"restart") == 0) {
1305848c92a3Santirez                     if (config.eval) {
1306848c92a3Santirez                         config.eval_ldb = 1;
1307848c92a3Santirez                         config.output = OUTPUT_RAW;
13085535784bSantirez                         return; /* Return to evalMode to restart the session. */
13095535784bSantirez                     } else {
13105535784bSantirez                         printf("Use 'restart' only in Lua debugging mode.");
13115535784bSantirez                     }
1312efcf948cSantirez                 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
1313efcf948cSantirez                     sdsfree(config.hostip);
1314efcf948cSantirez                     config.hostip = sdsnew(argv[1]);
1315efcf948cSantirez                     config.hostport = atoi(argv[2]);
1316e3962366Scharsyam                     cliRefreshPrompt();
1317efcf948cSantirez                     cliConnect(1);
1318bbac56c2Santirez                 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
1319bbac56c2Santirez                     linenoiseClearScreen();
1320c0b3d423Santirez                 } else {
13213ce014c7Santirez                     long long start_time = mstime(), elapsed;
13224d19e344Santirez                     int repeat, skipargs = 0;
1323c0b3d423Santirez 
13244d19e344Santirez                     repeat = atoi(argv[0]);
1325aee7f997SJuri M. Vainonen                     if (argc > 1 && repeat) {
13264d19e344Santirez                         skipargs = 1;
13274d19e344Santirez                     } else {
13284d19e344Santirez                         repeat = 1;
13294d19e344Santirez                     }
13304d19e344Santirez 
13317fcfbea0Sh0x91b                     issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);
13327fc4ce13SPieter Noordhuis 
1333629acd61Santirez                     /* If our debugging session ended, show the EVAL final
1334629acd61Santirez                      * reply. */
1335629acd61Santirez                     if (config.eval_ldb_end) {
1336629acd61Santirez                         config.eval_ldb_end = 0;
1337629acd61Santirez                         cliReadReply(0);
133875788d6aSantirez                         printf("\n(Lua debugging session ended%s)\n\n",
133975788d6aSantirez                             config.eval_ldb_sync ? "" :
134075788d6aSantirez                             " -- dataset changes rolled back");
1341629acd61Santirez                     }
1342629acd61Santirez 
13433ce014c7Santirez                     elapsed = mstime()-start_time;
1344339b9dc2SPieter Noordhuis                     if (elapsed >= 500) {
1345339b9dc2SPieter Noordhuis                         printf("(%.2fs)\n",(double)elapsed/1000);
1346339b9dc2SPieter Noordhuis                     }
1347c0b3d423Santirez                 }
1348c0b3d423Santirez             }
1349e2641e09Santirez             /* Free the argument vector */
13508c193af6Santirez             sdsfreesplitres(argv,argc);
1351e2641e09Santirez         }
1352e2641e09Santirez         /* linenoise() returns malloc-ed lines like readline() */
1353029dc0d9Santirez         linenoiseFree(line);
1354e2641e09Santirez     }
1355e2641e09Santirez     exit(0);
1356e2641e09Santirez }
1357e2641e09Santirez 
noninteractive(int argc,char ** argv)1358b4b62c34SPieter Noordhuis static int noninteractive(int argc, char **argv) {
1359b4b62c34SPieter Noordhuis     int retval = 0;
1360bc63407bSantirez     if (config.stdinarg) {
1361b4b62c34SPieter Noordhuis         argv = zrealloc(argv, (argc+1)*sizeof(char*));
1362b4b62c34SPieter Noordhuis         argv[argc] = readArgFromStdin();
13637fcfbea0Sh0x91b         retval = issueCommand(argc+1, argv);
1364b4b62c34SPieter Noordhuis     } else {
13657fcfbea0Sh0x91b         retval = issueCommand(argc, argv);
1366b4b62c34SPieter Noordhuis     }
1367b4b62c34SPieter Noordhuis     return retval;
1368b4b62c34SPieter Noordhuis }
1369b4b62c34SPieter Noordhuis 
1370dcac007bSantirez /*------------------------------------------------------------------------------
1371dcac007bSantirez  * Eval mode
1372dcac007bSantirez  *--------------------------------------------------------------------------- */
1373dcac007bSantirez 
evalMode(int argc,char ** argv)1374e2f31389Santirez static int evalMode(int argc, char **argv) {
13755535784bSantirez     sds script = NULL;
1376e2f31389Santirez     FILE *fp;
1377e2f31389Santirez     char buf[1024];
1378e2f31389Santirez     size_t nread;
1379e2f31389Santirez     char **argv2;
13805535784bSantirez     int j, got_comma, keys;
13815535784bSantirez     int retval = REDIS_OK;
13825535784bSantirez 
13835535784bSantirez     while(1) {
13845535784bSantirez         if (config.eval_ldb) {
13855535784bSantirez             printf(
13865535784bSantirez             "Lua debugging session started, please use:\n"
13875535784bSantirez             "quit    -- End the session.\n"
13885535784bSantirez             "restart -- Restart the script in debug mode again.\n"
13895535784bSantirez             "help    -- Show Lua script debugging commands.\n\n"
13905535784bSantirez             );
13915535784bSantirez         }
13925535784bSantirez 
13935535784bSantirez         sdsfree(script);
13945535784bSantirez         script = sdsempty();
13955535784bSantirez         got_comma = 0;
13965535784bSantirez         keys = 0;
1397e2f31389Santirez 
1398e2f31389Santirez         /* Load the script from the file, as an sds string. */
1399e2f31389Santirez         fp = fopen(config.eval,"r");
1400e2f31389Santirez         if (!fp) {
1401e2f31389Santirez             fprintf(stderr,
1402e2f31389Santirez                 "Can't open file '%s': %s\n", config.eval, strerror(errno));
1403e2f31389Santirez             exit(1);
1404e2f31389Santirez         }
1405e2f31389Santirez         while((nread = fread(buf,1,sizeof(buf),fp)) != 0) {
1406e2f31389Santirez             script = sdscatlen(script,buf,nread);
1407e2f31389Santirez         }
1408e2f31389Santirez         fclose(fp);
1409e2f31389Santirez 
1410def31636Santirez         /* If we are debugging a script, enable the Lua debugger. */
1411def31636Santirez         if (config.eval_ldb) {
141275788d6aSantirez             redisReply *reply = redisCommand(context,
14135535784bSantirez                     config.eval_ldb_sync ?
14145535784bSantirez                     "SCRIPT DEBUG sync": "SCRIPT DEBUG yes");
1415def31636Santirez             if (reply) freeReplyObject(reply);
1416def31636Santirez         }
1417def31636Santirez 
1418e2f31389Santirez         /* Create our argument vector */
1419e2f31389Santirez         argv2 = zmalloc(sizeof(sds)*(argc+3));
1420e2f31389Santirez         argv2[0] = sdsnew("EVAL");
1421e2f31389Santirez         argv2[1] = script;
1422e2f31389Santirez         for (j = 0; j < argc; j++) {
1423e2f31389Santirez             if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) {
1424e2f31389Santirez                 got_comma = 1;
1425e2f31389Santirez                 continue;
1426e2f31389Santirez             }
1427e2f31389Santirez             argv2[j+3-got_comma] = sdsnew(argv[j]);
1428e2f31389Santirez             if (!got_comma) keys++;
1429e2f31389Santirez         }
1430e2f31389Santirez         argv2[2] = sdscatprintf(sdsempty(),"%d",keys);
1431e2f31389Santirez 
1432e2f31389Santirez         /* Call it */
1433267ebb67Santirez         int eval_ldb = config.eval_ldb; /* Save it, may be reverteed. */
14345535784bSantirez         retval = issueCommand(argc+3-got_comma, argv2);
1435267ebb67Santirez         if (eval_ldb) {
1436267ebb67Santirez             if (!config.eval_ldb) {
1437267ebb67Santirez                 /* If the debugging session ended immediately, there was an
1438267ebb67Santirez                  * error compiling the script. Show it and don't enter
1439267ebb67Santirez                  * the REPL at all. */
1440267ebb67Santirez                 printf("Eval debugging session can't start:\n");
1441267ebb67Santirez                 cliReadReply(0);
14425535784bSantirez                 break; /* Return to the caller. */
1443267ebb67Santirez             } else {
1444def31636Santirez                 strncpy(config.prompt,"lua debugger> ",sizeof(config.prompt));
1445def31636Santirez                 repl();
14465535784bSantirez                 /* Restart the session if repl() returned. */
14475535784bSantirez                 cliConnect(1);
14485535784bSantirez                 printf("\n");
14495535784bSantirez             }
14505535784bSantirez         } else {
14515535784bSantirez             break; /* Return to the caller. */
1452def31636Santirez         }
1453267ebb67Santirez     }
1454def31636Santirez     return retval;
1455e2f31389Santirez }
1456e2f31389Santirez 
1457dcac007bSantirez /*------------------------------------------------------------------------------
1458dcac007bSantirez  * Latency and latency history modes
1459dcac007bSantirez  *--------------------------------------------------------------------------- */
1460dcac007bSantirez 
14610280c2f2Santirez #define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
14620280c2f2Santirez #define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
latencyMode(void)146343071993Santirez static void latencyMode(void) {
146443071993Santirez     redisReply *reply;
1465e54fe9a7Santirez     long long start, latency, min = 0, max = 0, tot = 0, count = 0;
14660280c2f2Santirez     long long history_interval =
14670280c2f2Santirez         config.interval ? config.interval/1000 :
14680280c2f2Santirez                           LATENCY_HISTORY_DEFAULT_INTERVAL;
146943071993Santirez     double avg;
14700280c2f2Santirez     long long history_start = mstime();
147143071993Santirez 
147243071993Santirez     if (!context) exit(1);
147343071993Santirez     while(1) {
147443071993Santirez         start = mstime();
1475ca23b2a6Santirez         reply = reconnectingRedisCommand(context,"PING");
147643071993Santirez         if (reply == NULL) {
147743071993Santirez             fprintf(stderr,"\nI/O error\n");
147843071993Santirez             exit(1);
147943071993Santirez         }
148043071993Santirez         latency = mstime()-start;
148143071993Santirez         freeReplyObject(reply);
148243071993Santirez         count++;
148343071993Santirez         if (count == 1) {
148443071993Santirez             min = max = tot = latency;
148543071993Santirez             avg = (double) latency;
148643071993Santirez         } else {
148743071993Santirez             if (latency < min) min = latency;
148843071993Santirez             if (latency > max) max = latency;
148996674b6dSantirez             tot += latency;
149043071993Santirez             avg = (double) tot/count;
149143071993Santirez         }
149243071993Santirez         printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)",
149343071993Santirez             min, max, avg, count);
149443071993Santirez         fflush(stdout);
14950280c2f2Santirez         if (config.latency_history && mstime()-history_start > history_interval)
14960280c2f2Santirez         {
14970280c2f2Santirez             printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000);
14980280c2f2Santirez             history_start = mstime();
14990280c2f2Santirez             min = max = tot = count = 0;
15000280c2f2Santirez         }
15010280c2f2Santirez         usleep(LATENCY_SAMPLE_RATE * 1000);
150243071993Santirez     }
150343071993Santirez }
150443071993Santirez 
1505dcac007bSantirez /*------------------------------------------------------------------------------
15062860cf41Santirez  * Latency distribution mode -- requires 256 colors xterm
15072860cf41Santirez  *--------------------------------------------------------------------------- */
15082860cf41Santirez 
15092860cf41Santirez #define LATENCY_DIST_DEFAULT_INTERVAL 1000 /* milliseconds. */
15102723412bSantirez 
15112860cf41Santirez /* Structure to store samples distribution. */
15122860cf41Santirez struct distsamples {
15132860cf41Santirez     long long max;   /* Max latency to fit into this interval (usec). */
15142860cf41Santirez     long long count; /* Number of samples in this interval. */
15152860cf41Santirez     int character;   /* Associated character in visualization. */
15162860cf41Santirez };
15172860cf41Santirez 
15182860cf41Santirez /* Helper function for latencyDistMode(). Performs the spectrum visualization
15192860cf41Santirez  * of the collected samples targeting an xterm 256 terminal.
15202860cf41Santirez  *
15212860cf41Santirez  * Takes an array of distsamples structures, ordered from smaller to bigger
15222860cf41Santirez  * 'max' value. Last sample max must be 0, to mean that it olds all the
15232860cf41Santirez  * samples greater than the previous one, and is also the stop sentinel.
15242860cf41Santirez  *
15252860cf41Santirez  * "tot' is the total number of samples in the different buckets, so it
15262860cf41Santirez  * is the SUM(samples[i].conut) for i to 0 up to the max sample.
15272860cf41Santirez  *
15282860cf41Santirez  * As a side effect the function sets all the buckets count to 0. */
showLatencyDistSamples(struct distsamples * samples,long long tot)15292860cf41Santirez void showLatencyDistSamples(struct distsamples *samples, long long tot) {
15302860cf41Santirez     int j;
15312860cf41Santirez 
15322723412bSantirez      /* We convert samples into a index inside the palette
15332860cf41Santirez      * proportional to the percentage a given bucket represents.
15342860cf41Santirez      * This way intensity of the different parts of the spectrum
15352860cf41Santirez      * don't change relative to the number of requests, which avoids to
15362860cf41Santirez      * pollute the visualization with non-latency related info. */
15372860cf41Santirez     printf("\033[38;5;0m"); /* Set foreground color to black. */
15382860cf41Santirez     for (j = 0; ; j++) {
15392723412bSantirez         int coloridx =
15402723412bSantirez             ceil((float) samples[j].count / tot * (spectrum_palette_size-1));
15412723412bSantirez         int color = spectrum_palette[coloridx];
15422860cf41Santirez         printf("\033[48;5;%dm%c", (int)color, samples[j].character);
15432860cf41Santirez         samples[j].count = 0;
15442860cf41Santirez         if (samples[j].max == 0) break; /* Last sample. */
15452860cf41Santirez     }
15462860cf41Santirez     printf("\033[0m\n");
15472860cf41Santirez     fflush(stdout);
15482860cf41Santirez }
15492860cf41Santirez 
15502860cf41Santirez /* Show the legend: different buckets values and colors meaning, so
15512860cf41Santirez  * that the spectrum is more easily readable. */
showLatencyDistLegend(void)15522860cf41Santirez void showLatencyDistLegend(void) {
1553cfe21852Santirez     int j;
1554cfe21852Santirez 
1555ace1acc5Santirez     printf("---------------------------------------------\n");
1556414df143Santirez     printf(". - * #          .01 .125 .25 .5 milliseconds\n");
15572860cf41Santirez     printf("1,2,3,...,9      from 1 to 9     milliseconds\n");
15582860cf41Santirez     printf("A,B,C,D,E        10,20,30,40,50  milliseconds\n");
15592860cf41Santirez     printf("F,G,H,I,J        .1,.2,.3,.4,.5       seconds\n");
15602860cf41Santirez     printf("K,L,M,N,O,P,Q,?  1,2,4,8,16,30,60,>60 seconds\n");
1561cfe21852Santirez     printf("From 0 to 100%%: ");
1562cfe21852Santirez     for (j = 0; j < spectrum_palette_size; j++) {
1563cfe21852Santirez         printf("\033[48;5;%dm ", spectrum_palette[j]);
1564cfe21852Santirez     }
1565cfe21852Santirez     printf("\033[0m\n");
15662860cf41Santirez     printf("---------------------------------------------\n");
15672860cf41Santirez }
15682860cf41Santirez 
latencyDistMode(void)15692860cf41Santirez static void latencyDistMode(void) {
15702860cf41Santirez     redisReply *reply;
15712860cf41Santirez     long long start, latency, count = 0;
15722860cf41Santirez     long long history_interval =
15732860cf41Santirez         config.interval ? config.interval/1000 :
15742860cf41Santirez                           LATENCY_DIST_DEFAULT_INTERVAL;
15752860cf41Santirez     long long history_start = ustime();
15762860cf41Santirez     int j, outputs = 0;
15772860cf41Santirez 
15782860cf41Santirez     struct distsamples samples[] = {
15792860cf41Santirez         /* We use a mostly logarithmic scale, with certain linear intervals
15802860cf41Santirez          * which are more interesting than others, like 1-10 milliseconds
15812860cf41Santirez          * range. */
15822860cf41Santirez         {10,0,'.'},         /* 0.01 ms */
15832860cf41Santirez         {125,0,'-'},        /* 0.125 ms */
15842860cf41Santirez         {250,0,'*'},        /* 0.25 ms */
15852860cf41Santirez         {500,0,'#'},        /* 0.5 ms */
15862860cf41Santirez         {1000,0,'1'},       /* 1 ms */
15872860cf41Santirez         {2000,0,'2'},       /* 2 ms */
15882860cf41Santirez         {3000,0,'3'},       /* 3 ms */
15892860cf41Santirez         {4000,0,'4'},       /* 4 ms */
15902860cf41Santirez         {5000,0,'5'},       /* 5 ms */
15912860cf41Santirez         {6000,0,'6'},       /* 6 ms */
15922860cf41Santirez         {7000,0,'7'},       /* 7 ms */
15932860cf41Santirez         {8000,0,'8'},       /* 8 ms */
15942860cf41Santirez         {9000,0,'9'},       /* 9 ms */
15952860cf41Santirez         {10000,0,'A'},      /* 10 ms */
15962860cf41Santirez         {20000,0,'B'},      /* 20 ms */
15972860cf41Santirez         {30000,0,'C'},      /* 30 ms */
15982860cf41Santirez         {40000,0,'D'},      /* 40 ms */
15992860cf41Santirez         {50000,0,'E'},      /* 50 ms */
16002860cf41Santirez         {100000,0,'F'},     /* 0.1 s */
16012860cf41Santirez         {200000,0,'G'},     /* 0.2 s */
16022860cf41Santirez         {300000,0,'H'},     /* 0.3 s */
16032860cf41Santirez         {400000,0,'I'},     /* 0.4 s */
16042860cf41Santirez         {500000,0,'J'},     /* 0.5 s */
16052860cf41Santirez         {1000000,0,'K'},    /* 1 s */
16062860cf41Santirez         {2000000,0,'L'},    /* 2 s */
16072860cf41Santirez         {4000000,0,'M'},    /* 4 s */
16082860cf41Santirez         {8000000,0,'N'},    /* 8 s */
16092860cf41Santirez         {16000000,0,'O'},   /* 16 s */
16102860cf41Santirez         {30000000,0,'P'},   /* 30 s */
16112860cf41Santirez         {60000000,0,'Q'},   /* 1 minute */
16122860cf41Santirez         {0,0,'?'},          /* > 1 minute */
16132860cf41Santirez     };
16142860cf41Santirez 
16152860cf41Santirez     if (!context) exit(1);
16162860cf41Santirez     while(1) {
16172860cf41Santirez         start = ustime();
1618ca23b2a6Santirez         reply = reconnectingRedisCommand(context,"PING");
16192860cf41Santirez         if (reply == NULL) {
16202860cf41Santirez             fprintf(stderr,"\nI/O error\n");
16212860cf41Santirez             exit(1);
16222860cf41Santirez         }
16232860cf41Santirez         latency = ustime()-start;
16242860cf41Santirez         freeReplyObject(reply);
16252860cf41Santirez         count++;
16262860cf41Santirez 
16272860cf41Santirez         /* Populate the relevant bucket. */
16282860cf41Santirez         for (j = 0; ; j++) {
16292860cf41Santirez             if (samples[j].max == 0 || latency <= samples[j].max) {
16302860cf41Santirez                 samples[j].count++;
16312860cf41Santirez                 break;
16322860cf41Santirez             }
16332860cf41Santirez         }
16342860cf41Santirez 
16352860cf41Santirez         /* From time to time show the spectrum. */
16362860cf41Santirez         if (count && (ustime()-history_start)/1000 > history_interval) {
16372860cf41Santirez             if ((outputs++ % 20) == 0)
16382860cf41Santirez                 showLatencyDistLegend();
16392860cf41Santirez             showLatencyDistSamples(samples,count);
16402860cf41Santirez             history_start = ustime();
16412860cf41Santirez             count = 0;
16422860cf41Santirez         }
16432860cf41Santirez         usleep(LATENCY_SAMPLE_RATE * 1000);
16442860cf41Santirez     }
16452860cf41Santirez }
16462860cf41Santirez 
16472860cf41Santirez /*------------------------------------------------------------------------------
1648dcac007bSantirez  * Slave mode
1649dcac007bSantirez  *--------------------------------------------------------------------------- */
1650dcac007bSantirez 
1651a0c24821Santirez /* Sends SYNC and reads the number of bytes in the payload. Used both by
1652a0c24821Santirez  * slaveMode() and getRDB(). */
sendSync(int fd)1653a0c24821Santirez unsigned long long sendSync(int fd) {
1654b8283ab2Santirez     /* To start we need to send the SYNC command and return the payload.
1655b8283ab2Santirez      * The hiredis client lib does not understand this part of the protocol
1656b8283ab2Santirez      * and we don't want to mess with its buffers, so everything is performed
1657b8283ab2Santirez      * using direct low-level I/O. */
1658a0c24821Santirez     char buf[4096], *p;
1659b8283ab2Santirez     ssize_t nread;
1660b8283ab2Santirez 
1661b8283ab2Santirez     /* Send the SYNC command. */
16620d44d507Santirez     if (write(fd,"SYNC\r\n",6) != 6) {
16630d44d507Santirez         fprintf(stderr,"Error writing to master\n");
16640d44d507Santirez         exit(1);
16650d44d507Santirez     }
1666b8283ab2Santirez 
1667b8283ab2Santirez     /* Read $<payload>\r\n, making sure to read just up to "\n" */
1668b8283ab2Santirez     p = buf;
1669b8283ab2Santirez     while(1) {
1670b8283ab2Santirez         nread = read(fd,p,1);
1671b8283ab2Santirez         if (nread <= 0) {
1672b8283ab2Santirez             fprintf(stderr,"Error reading bulk length while SYNCing\n");
1673b8283ab2Santirez             exit(1);
1674b8283ab2Santirez         }
16751920cab3SNathan Parry         if (*p == '\n' && p != buf) break;
16761920cab3SNathan Parry         if (*p != '\n') p++;
1677b8283ab2Santirez     }
1678b8283ab2Santirez     *p = '\0';
1679a0c24821Santirez     if (buf[0] == '-') {
1680a0c24821Santirez         printf("SYNC with master failed: %s\n", buf);
1681a0c24821Santirez         exit(1);
1682a0c24821Santirez     }
1683a0c24821Santirez     return strtoull(buf+1,NULL,10);
1684a0c24821Santirez }
1685a0c24821Santirez 
slaveMode(void)1686a0c24821Santirez static void slaveMode(void) {
1687a0c24821Santirez     int fd = context->fd;
1688a0c24821Santirez     unsigned long long payload = sendSync(fd);
1689a0c24821Santirez     char buf[1024];
169021648473SMatt Stancliff     int original_output = config.output;
1691a0c24821Santirez 
1692a0c24821Santirez     fprintf(stderr,"SYNC with master, discarding %llu "
16939d09ce39Sguiquanz                    "bytes of bulk transfer...\n", payload);
1694b8283ab2Santirez 
1695b8283ab2Santirez     /* Discard the payload. */
1696b8283ab2Santirez     while(payload) {
1697a0c24821Santirez         ssize_t nread;
1698a0c24821Santirez 
1699b8283ab2Santirez         nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
1700b8283ab2Santirez         if (nread <= 0) {
1701b8283ab2Santirez             fprintf(stderr,"Error reading RDB payload while SYNCing\n");
1702b8283ab2Santirez             exit(1);
1703b8283ab2Santirez         }
1704b8283ab2Santirez         payload -= nread;
1705b8283ab2Santirez     }
170660893c6cSantirez     fprintf(stderr,"SYNC done. Logging commands from master.\n");
1707b8283ab2Santirez 
1708a0c24821Santirez     /* Now we can use hiredis to read the incoming protocol. */
170960893c6cSantirez     config.output = OUTPUT_CSV;
171060893c6cSantirez     while (cliReadReply(0) == REDIS_OK);
171121648473SMatt Stancliff     config.output = original_output;
1712b8283ab2Santirez }
1713b8283ab2Santirez 
1714dcac007bSantirez /*------------------------------------------------------------------------------
1715dcac007bSantirez  * RDB transfer mode
1716dcac007bSantirez  *--------------------------------------------------------------------------- */
1717dcac007bSantirez 
1718a0c24821Santirez /* This function implements --rdb, so it uses the replication protocol in order
1719a0c24821Santirez  * to fetch the RDB file from a remote server. */
getRDB(void)1720a0c24821Santirez static void getRDB(void) {
1721a0c24821Santirez     int s = context->fd;
1722a0c24821Santirez     int fd;
1723a0c24821Santirez     unsigned long long payload = sendSync(s);
1724a0c24821Santirez     char buf[4096];
1725a0c24821Santirez 
1726a0c24821Santirez     fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n",
1727a0c24821Santirez         payload, config.rdb_filename);
1728a0c24821Santirez 
1729a0c24821Santirez     /* Write to file. */
1730a0c24821Santirez     if (!strcmp(config.rdb_filename,"-")) {
1731a0c24821Santirez         fd = STDOUT_FILENO;
1732a0c24821Santirez     } else {
1733a0c24821Santirez         fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644);
1734a0c24821Santirez         if (fd == -1) {
1735a0c24821Santirez             fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename,
1736a0c24821Santirez                 strerror(errno));
1737a0c24821Santirez             exit(1);
1738a0c24821Santirez         }
1739a0c24821Santirez     }
1740a0c24821Santirez 
1741a0c24821Santirez     while(payload) {
1742a0c24821Santirez         ssize_t nread, nwritten;
1743a0c24821Santirez 
1744a0c24821Santirez         nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
1745a0c24821Santirez         if (nread <= 0) {
1746a0c24821Santirez             fprintf(stderr,"I/O Error reading RDB payload from socket\n");
1747a0c24821Santirez             exit(1);
1748a0c24821Santirez         }
1749a0c24821Santirez         nwritten = write(fd, buf, nread);
1750a0c24821Santirez         if (nwritten != nread) {
1751a0c24821Santirez             fprintf(stderr,"Error writing data to file: %s\n",
1752a0c24821Santirez                 strerror(errno));
1753a0c24821Santirez             exit(1);
1754a0c24821Santirez         }
1755a0c24821Santirez         payload -= nread;
1756a0c24821Santirez     }
1757a0c24821Santirez     close(s); /* Close the file descriptor ASAP as fsync() may take time. */
1758a0c24821Santirez     fsync(fd);
1759a0c24821Santirez     fprintf(stderr,"Transfer finished with success.\n");
1760a0c24821Santirez     exit(0);
1761a0c24821Santirez }
1762a0c24821Santirez 
1763dcac007bSantirez /*------------------------------------------------------------------------------
1764dcac007bSantirez  * Bulk import (pipe) mode
1765dcac007bSantirez  *--------------------------------------------------------------------------- */
1766dcac007bSantirez 
17670f81f83eSantirez #define PIPEMODE_WRITE_LOOP_MAX_BYTES (128*1024)
pipeMode(void)1768088c508aSantirez static void pipeMode(void) {
1769088c508aSantirez     int fd = context->fd;
1770088c508aSantirez     long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
1771088c508aSantirez     char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */
1772088c508aSantirez     char aneterr[ANET_ERR_LEN];
1773088c508aSantirez     redisReader *reader = redisReaderCreate();
1774088c508aSantirez     redisReply *reply;
1775088c508aSantirez     int eof = 0; /* True once we consumed all the standard input. */
1776088c508aSantirez     int done = 0;
1777088c508aSantirez     char magic[20]; /* Special reply we recognize. */
17781135e9faSantirez     time_t last_read_time = time(NULL);
1779088c508aSantirez 
1780088c508aSantirez     srand(time(NULL));
1781088c508aSantirez 
1782088c508aSantirez     /* Use non blocking I/O. */
1783088c508aSantirez     if (anetNonBlock(aneterr,fd) == ANET_ERR) {
1784088c508aSantirez         fprintf(stderr, "Can't set the socket in non blocking mode: %s\n",
1785088c508aSantirez             aneterr);
1786088c508aSantirez         exit(1);
1787088c508aSantirez     }
1788088c508aSantirez 
1789088c508aSantirez     /* Transfer raw protocol and read replies from the server at the same
1790088c508aSantirez      * time. */
1791088c508aSantirez     while(!done) {
1792088c508aSantirez         int mask = AE_READABLE;
1793088c508aSantirez 
1794088c508aSantirez         if (!eof || obuf_len != 0) mask |= AE_WRITABLE;
1795088c508aSantirez         mask = aeWait(fd,mask,1000);
1796088c508aSantirez 
1797088c508aSantirez         /* Handle the readable state: we can read replies from the server. */
1798088c508aSantirez         if (mask & AE_READABLE) {
1799088c508aSantirez             ssize_t nread;
1800088c508aSantirez 
1801088c508aSantirez             /* Read from socket and feed the hiredis reader. */
1802088c508aSantirez             do {
1803088c508aSantirez                 nread = read(fd,ibuf,sizeof(ibuf));
1804ea66be60Santirez                 if (nread == -1 && errno != EAGAIN && errno != EINTR) {
1805088c508aSantirez                     fprintf(stderr, "Error reading from the server: %s\n",
1806088c508aSantirez                         strerror(errno));
1807088c508aSantirez                     exit(1);
1808088c508aSantirez                 }
18091135e9faSantirez                 if (nread > 0) {
18101135e9faSantirez                     redisReaderFeed(reader,ibuf,nread);
18111135e9faSantirez                     last_read_time = time(NULL);
18121135e9faSantirez                 }
1813088c508aSantirez             } while(nread > 0);
1814088c508aSantirez 
1815088c508aSantirez             /* Consume replies. */
1816088c508aSantirez             do {
1817088c508aSantirez                 if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) {
1818088c508aSantirez                     fprintf(stderr, "Error reading replies from server\n");
1819088c508aSantirez                     exit(1);
1820088c508aSantirez                 }
1821088c508aSantirez                 if (reply) {
1822088c508aSantirez                     if (reply->type == REDIS_REPLY_ERROR) {
1823088c508aSantirez                         fprintf(stderr,"%s\n", reply->str);
1824088c508aSantirez                         errors++;
1825088c508aSantirez                     } else if (eof && reply->type == REDIS_REPLY_STRING &&
1826088c508aSantirez                                       reply->len == 20) {
1827088c508aSantirez                         /* Check if this is the reply to our final ECHO
1828088c508aSantirez                          * command. If so everything was received
1829088c508aSantirez                          * from the server. */
1830088c508aSantirez                         if (memcmp(reply->str,magic,20) == 0) {
1831088c508aSantirez                             printf("Last reply received from server.\n");
1832088c508aSantirez                             done = 1;
1833088c508aSantirez                             replies--;
1834088c508aSantirez                         }
1835088c508aSantirez                     }
1836088c508aSantirez                     replies++;
1837088c508aSantirez                     freeReplyObject(reply);
1838088c508aSantirez                 }
1839088c508aSantirez             } while(reply);
1840088c508aSantirez         }
1841088c508aSantirez 
1842088c508aSantirez         /* Handle the writable state: we can send protocol to the server. */
1843088c508aSantirez         if (mask & AE_WRITABLE) {
18440f81f83eSantirez             ssize_t loop_nwritten = 0;
18450f81f83eSantirez 
1846088c508aSantirez             while(1) {
1847088c508aSantirez                 /* Transfer current buffer to server. */
1848088c508aSantirez                 if (obuf_len != 0) {
1849088c508aSantirez                     ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len);
1850088c508aSantirez 
1851088c508aSantirez                     if (nwritten == -1) {
1852ea66be60Santirez                         if (errno != EAGAIN && errno != EINTR) {
1853088c508aSantirez                             fprintf(stderr, "Error writing to the server: %s\n",
1854088c508aSantirez                                 strerror(errno));
1855088c508aSantirez                             exit(1);
1856f6bd9122Santirez                         } else {
1857f6bd9122Santirez                             nwritten = 0;
1858f6bd9122Santirez                         }
1859088c508aSantirez                     }
1860088c508aSantirez                     obuf_len -= nwritten;
1861088c508aSantirez                     obuf_pos += nwritten;
18620f81f83eSantirez                     loop_nwritten += nwritten;
1863088c508aSantirez                     if (obuf_len != 0) break; /* Can't accept more data. */
1864088c508aSantirez                 }
1865088c508aSantirez                 /* If buffer is empty, load from stdin. */
1866088c508aSantirez                 if (obuf_len == 0 && !eof) {
1867088c508aSantirez                     ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf));
1868088c508aSantirez 
1869088c508aSantirez                     if (nread == 0) {
1870fbb97c6bSantirez                         /* The ECHO sequence starts with a "\r\n" so that if there
1871fbb97c6bSantirez                          * is garbage in the protocol we read from stdin, the ECHO
1872fbb97c6bSantirez                          * will likely still be properly formatted.
1873fbb97c6bSantirez                          * CRLF is ignored by Redis, so it has no effects. */
1874088c508aSantirez                         char echo[] =
1875fbb97c6bSantirez                         "\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
1876088c508aSantirez                         int j;
1877088c508aSantirez 
1878088c508aSantirez                         eof = 1;
18799d09ce39Sguiquanz                         /* Everything transferred, so we queue a special
1880088c508aSantirez                          * ECHO command that we can match in the replies
1881088c508aSantirez                          * to make sure everything was read from the server. */
1882088c508aSantirez                         for (j = 0; j < 20; j++)
1883088c508aSantirez                             magic[j] = rand() & 0xff;
1884fbb97c6bSantirez                         memcpy(echo+21,magic,20);
1885088c508aSantirez                         memcpy(obuf,echo,sizeof(echo)-1);
1886088c508aSantirez                         obuf_len = sizeof(echo)-1;
1887088c508aSantirez                         obuf_pos = 0;
1888088c508aSantirez                         printf("All data transferred. Waiting for the last reply...\n");
1889088c508aSantirez                     } else if (nread == -1) {
1890088c508aSantirez                         fprintf(stderr, "Error reading from stdin: %s\n",
1891088c508aSantirez                             strerror(errno));
1892088c508aSantirez                         exit(1);
1893088c508aSantirez                     } else {
1894088c508aSantirez                         obuf_len = nread;
1895088c508aSantirez                         obuf_pos = 0;
1896088c508aSantirez                     }
1897088c508aSantirez                 }
18980f81f83eSantirez                 if ((obuf_len == 0 && eof) ||
18990f81f83eSantirez                     loop_nwritten > PIPEMODE_WRITE_LOOP_MAX_BYTES) break;
1900088c508aSantirez             }
1901088c508aSantirez         }
19021135e9faSantirez 
19031135e9faSantirez         /* Handle timeout, that is, we reached EOF, and we are not getting
19041135e9faSantirez          * replies from the server for a few seconds, nor the final ECHO is
19051135e9faSantirez          * received. */
19061135e9faSantirez         if (eof && config.pipe_timeout > 0 &&
19071135e9faSantirez             time(NULL)-last_read_time > config.pipe_timeout)
19081135e9faSantirez         {
19091135e9faSantirez             fprintf(stderr,"No replies for %d seconds: exiting.\n",
19101135e9faSantirez                 config.pipe_timeout);
19111135e9faSantirez             errors++;
19121135e9faSantirez             break;
19131135e9faSantirez         }
1914088c508aSantirez     }
1915088c508aSantirez     redisReaderFree(reader);
1916088c508aSantirez     printf("errors: %lld, replies: %lld\n", errors, replies);
1917088c508aSantirez     if (errors)
1918088c508aSantirez         exit(1);
1919088c508aSantirez     else
1920088c508aSantirez         exit(0);
1921088c508aSantirez }
1922088c508aSantirez 
1923dcac007bSantirez /*------------------------------------------------------------------------------
1924dcac007bSantirez  * Find big keys
1925dcac007bSantirez  *--------------------------------------------------------------------------- */
1926dcac007bSantirez 
1927f26761aaSantirez #define TYPE_STRING 0
1928f26761aaSantirez #define TYPE_LIST   1
1929f26761aaSantirez #define TYPE_SET    2
1930f26761aaSantirez #define TYPE_HASH   3
1931f26761aaSantirez #define TYPE_ZSET   4
1932806788d0Smichael-grunder #define TYPE_NONE   5
1933f26761aaSantirez 
sendScan(unsigned long long * it)1934806788d0Smichael-grunder static redisReply *sendScan(unsigned long long *it) {
1935806788d0Smichael-grunder     redisReply *reply = redisCommand(context, "SCAN %llu", *it);
1936f26761aaSantirez 
1937806788d0Smichael-grunder     /* Handle any error conditions */
1938806788d0Smichael-grunder     if(reply == NULL) {
1939f26761aaSantirez         fprintf(stderr, "\nI/O error\n");
1940f26761aaSantirez         exit(1);
1941806788d0Smichael-grunder     } else if(reply->type == REDIS_REPLY_ERROR) {
1942806788d0Smichael-grunder         fprintf(stderr, "SCAN error: %s\n", reply->str);
1943f26761aaSantirez         exit(1);
1944806788d0Smichael-grunder     } else if(reply->type != REDIS_REPLY_ARRAY) {
1945013a4ce2Smichael-grunder         fprintf(stderr, "Non ARRAY response from SCAN!\n");
1946013a4ce2Smichael-grunder         exit(1);
1947806788d0Smichael-grunder     } else if(reply->elements != 2) {
1948806788d0Smichael-grunder         fprintf(stderr, "Invalid element count from SCAN!\n");
194991d3b487Santirez         exit(1);
1950f26761aaSantirez     }
195191d3b487Santirez 
1952806788d0Smichael-grunder     /* Validate our types are correct */
1953806788d0Smichael-grunder     assert(reply->element[0]->type == REDIS_REPLY_STRING);
1954806788d0Smichael-grunder     assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
1955013a4ce2Smichael-grunder 
1956806788d0Smichael-grunder     /* Update iterator */
195711381b09Subuntu     *it = strtoull(reply->element[0]->str, NULL, 10);
1958013a4ce2Smichael-grunder 
1959806788d0Smichael-grunder     return reply;
1960806788d0Smichael-grunder }
1961f26761aaSantirez 
getDbSize(void)1962806788d0Smichael-grunder static int getDbSize(void) {
1963806788d0Smichael-grunder     redisReply *reply;
1964806788d0Smichael-grunder     int size;
1965013a4ce2Smichael-grunder 
1966806788d0Smichael-grunder     reply = redisCommand(context, "DBSIZE");
1967806788d0Smichael-grunder 
1968806788d0Smichael-grunder     if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
1969806788d0Smichael-grunder         fprintf(stderr, "Couldn't determine DBSIZE!\n");
1970806788d0Smichael-grunder         exit(1);
1971806788d0Smichael-grunder     }
1972806788d0Smichael-grunder 
1973806788d0Smichael-grunder     /* Grab the number of keys and free our reply */
1974806788d0Smichael-grunder     size = reply->integer;
1975806788d0Smichael-grunder     freeReplyObject(reply);
1976806788d0Smichael-grunder 
1977806788d0Smichael-grunder     return size;
1978806788d0Smichael-grunder }
1979806788d0Smichael-grunder 
toIntType(char * key,char * type)1980806788d0Smichael-grunder static int toIntType(char *key, char *type) {
1981806788d0Smichael-grunder     if(!strcmp(type, "string")) {
1982806788d0Smichael-grunder         return TYPE_STRING;
1983806788d0Smichael-grunder     } else if(!strcmp(type, "list")) {
1984806788d0Smichael-grunder         return TYPE_LIST;
1985806788d0Smichael-grunder     } else if(!strcmp(type, "set")) {
1986806788d0Smichael-grunder         return TYPE_SET;
1987806788d0Smichael-grunder     } else if(!strcmp(type, "hash")) {
1988806788d0Smichael-grunder         return TYPE_HASH;
1989806788d0Smichael-grunder     } else if(!strcmp(type, "zset")) {
1990806788d0Smichael-grunder         return TYPE_ZSET;
1991806788d0Smichael-grunder     } else if(!strcmp(type, "none")) {
1992806788d0Smichael-grunder         return TYPE_NONE;
1993f26761aaSantirez     } else {
1994806788d0Smichael-grunder         fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key);
1995806788d0Smichael-grunder         exit(1);
1996806788d0Smichael-grunder     }
1997806788d0Smichael-grunder }
1998806788d0Smichael-grunder 
getKeyTypes(redisReply * keys,int * types)1999806788d0Smichael-grunder static void getKeyTypes(redisReply *keys, int *types) {
2000806788d0Smichael-grunder     redisReply *reply;
2001edca2b14Santirez     unsigned int i;
2002806788d0Smichael-grunder 
2003806788d0Smichael-grunder     /* Pipeline TYPE commands */
2004806788d0Smichael-grunder     for(i=0;i<keys->elements;i++) {
2005806788d0Smichael-grunder         redisAppendCommand(context, "TYPE %s", keys->element[i]->str);
2006806788d0Smichael-grunder     }
2007806788d0Smichael-grunder 
2008806788d0Smichael-grunder     /* Retrieve types */
2009806788d0Smichael-grunder     for(i=0;i<keys->elements;i++) {
2010806788d0Smichael-grunder         if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
2011806788d0Smichael-grunder             fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n",
2012806788d0Smichael-grunder                 keys->element[i]->str, context->err, context->errstr);
2013806788d0Smichael-grunder             exit(1);
2014806788d0Smichael-grunder         } else if(reply->type != REDIS_REPLY_STATUS) {
2015806788d0Smichael-grunder             fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n",
2016806788d0Smichael-grunder                 reply->type, keys->element[i]->str);
2017f26761aaSantirez             exit(1);
2018f26761aaSantirez         }
2019f26761aaSantirez 
2020806788d0Smichael-grunder         types[i] = toIntType(keys->element[i]->str, reply->str);
2021806788d0Smichael-grunder         freeReplyObject(reply);
2022806788d0Smichael-grunder     }
2023806788d0Smichael-grunder }
2024806788d0Smichael-grunder 
getKeySizes(redisReply * keys,int * types,unsigned long long * sizes)2025806788d0Smichael-grunder static void getKeySizes(redisReply *keys, int *types,
2026806788d0Smichael-grunder                         unsigned long long *sizes)
2027806788d0Smichael-grunder {
2028806788d0Smichael-grunder     redisReply *reply;
2029806788d0Smichael-grunder     char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
2030edca2b14Santirez     unsigned int i;
2031806788d0Smichael-grunder 
2032806788d0Smichael-grunder     /* Pipeline size commands */
2033806788d0Smichael-grunder     for(i=0;i<keys->elements;i++) {
2034806788d0Smichael-grunder         /* Skip keys that were deleted */
2035806788d0Smichael-grunder         if(types[i]==TYPE_NONE)
2036806788d0Smichael-grunder             continue;
2037806788d0Smichael-grunder 
2038806788d0Smichael-grunder         redisAppendCommand(context, "%s %s", sizecmds[types[i]],
2039806788d0Smichael-grunder             keys->element[i]->str);
2040806788d0Smichael-grunder     }
2041806788d0Smichael-grunder 
2042806788d0Smichael-grunder     /* Retreive sizes */
2043806788d0Smichael-grunder     for(i=0;i<keys->elements;i++) {
2044806788d0Smichael-grunder         /* Skip keys that dissapeared between SCAN and TYPE */
2045806788d0Smichael-grunder         if(types[i] == TYPE_NONE) {
2046806788d0Smichael-grunder             sizes[i] = 0;
2047806788d0Smichael-grunder             continue;
2048806788d0Smichael-grunder         }
2049806788d0Smichael-grunder 
2050806788d0Smichael-grunder         /* Retreive size */
2051806788d0Smichael-grunder         if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
2052806788d0Smichael-grunder             fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
2053806788d0Smichael-grunder                 keys->element[i]->str, context->err, context->errstr);
2054806788d0Smichael-grunder             exit(1);
2055806788d0Smichael-grunder         } else if(reply->type != REDIS_REPLY_INTEGER) {
2056806788d0Smichael-grunder             /* Theoretically the key could have been removed and
2057806788d0Smichael-grunder              * added as a different type between TYPE and SIZE */
2058806788d0Smichael-grunder             fprintf(stderr,
2059806788d0Smichael-grunder                 "Warning:  %s on '%s' failed (may have changed type)\n",
2060806788d0Smichael-grunder                  sizecmds[types[i]], keys->element[i]->str);
2061806788d0Smichael-grunder             sizes[i] = 0;
2062806788d0Smichael-grunder         } else {
2063806788d0Smichael-grunder             sizes[i] = reply->integer;
2064806788d0Smichael-grunder         }
2065806788d0Smichael-grunder 
2066806788d0Smichael-grunder         freeReplyObject(reply);
2067806788d0Smichael-grunder     }
2068806788d0Smichael-grunder }
2069806788d0Smichael-grunder 
findBigKeys(void)2070806788d0Smichael-grunder static void findBigKeys(void) {
2071806788d0Smichael-grunder     unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0};
2072806788d0Smichael-grunder     unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
2073806788d0Smichael-grunder     sds maxkeys[5] = {0};
2074806788d0Smichael-grunder     char *typename[] = {"string","list","set","hash","zset"};
2075806788d0Smichael-grunder     char *typeunit[] = {"bytes","items","members","fields","members"};
2076806788d0Smichael-grunder     redisReply *reply, *keys;
2077edca2b14Santirez     unsigned int arrsize=0, i;
2078edca2b14Santirez     int type, *types=NULL;
2079806788d0Smichael-grunder     double pct;
2080806788d0Smichael-grunder 
2081806788d0Smichael-grunder     /* Total keys pre scanning */
2082806788d0Smichael-grunder     total_keys = getDbSize();
2083806788d0Smichael-grunder 
2084806788d0Smichael-grunder     /* Status message */
2085806788d0Smichael-grunder     printf("\n# Scanning the entire keyspace to find biggest keys as well as\n");
2086806788d0Smichael-grunder     printf("# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec\n");
2087806788d0Smichael-grunder     printf("# per 100 SCAN commands (not usually needed).\n\n");
2088806788d0Smichael-grunder 
2089806788d0Smichael-grunder     /* New up sds strings to keep track of overall biggest per type */
2090806788d0Smichael-grunder     for(i=0;i<TYPE_NONE; i++) {
2091806788d0Smichael-grunder         maxkeys[i] = sdsempty();
2092806788d0Smichael-grunder         if(!maxkeys[i]) {
209393eed9aeSantirez             fprintf(stderr, "Failed to allocate memory for largest key names!\n");
2094806788d0Smichael-grunder             exit(1);
2095806788d0Smichael-grunder         }
2096806788d0Smichael-grunder     }
2097806788d0Smichael-grunder 
2098806788d0Smichael-grunder     /* SCAN loop */
2099806788d0Smichael-grunder     do {
2100806788d0Smichael-grunder         /* Calculate approximate percentage completion */
2101806788d0Smichael-grunder         pct = 100 * (double)sampled/total_keys;
2102806788d0Smichael-grunder 
2103806788d0Smichael-grunder         /* Grab some keys and point to the keys array */
2104806788d0Smichael-grunder         reply = sendScan(&it);
2105806788d0Smichael-grunder         keys  = reply->element[1];
2106806788d0Smichael-grunder 
2107806788d0Smichael-grunder         /* Reallocate our type and size array if we need to */
2108806788d0Smichael-grunder         if(keys->elements > arrsize) {
2109806788d0Smichael-grunder             types = zrealloc(types, sizeof(int)*keys->elements);
2110806788d0Smichael-grunder             sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
2111806788d0Smichael-grunder 
2112806788d0Smichael-grunder             if(!types || !sizes) {
2113806788d0Smichael-grunder                 fprintf(stderr, "Failed to allocate storage for keys!\n");
2114806788d0Smichael-grunder                 exit(1);
2115806788d0Smichael-grunder             }
2116806788d0Smichael-grunder 
2117806788d0Smichael-grunder             arrsize = keys->elements;
2118806788d0Smichael-grunder         }
2119806788d0Smichael-grunder 
2120806788d0Smichael-grunder         /* Retreive types and then sizes */
2121806788d0Smichael-grunder         getKeyTypes(keys, types);
2122806788d0Smichael-grunder         getKeySizes(keys, types, sizes);
2123806788d0Smichael-grunder 
2124806788d0Smichael-grunder         /* Now update our stats */
2125806788d0Smichael-grunder         for(i=0;i<keys->elements;i++) {
2126806788d0Smichael-grunder             if((type = types[i]) == TYPE_NONE)
2127806788d0Smichael-grunder                 continue;
2128806788d0Smichael-grunder 
2129806788d0Smichael-grunder             totalsize[type] += sizes[i];
2130806788d0Smichael-grunder             counts[type]++;
2131806788d0Smichael-grunder             totlen += keys->element[i]->len;
2132806788d0Smichael-grunder             sampled++;
2133806788d0Smichael-grunder 
2134806788d0Smichael-grunder             if(biggest[type]<sizes[i]) {
2135806788d0Smichael-grunder                 printf(
2136806788d0Smichael-grunder                    "[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
2137806788d0Smichael-grunder                    pct, typename[type], keys->element[i]->str, sizes[i],
213888015b89Santirez                    typeunit[type]);
2139806788d0Smichael-grunder 
2140806788d0Smichael-grunder                 /* Keep track of biggest key name for this type */
2141806788d0Smichael-grunder                 maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str);
2142806788d0Smichael-grunder                 if(!maxkeys[type]) {
2143806788d0Smichael-grunder                     fprintf(stderr, "Failed to allocate memory for key!\n");
2144806788d0Smichael-grunder                     exit(1);
2145806788d0Smichael-grunder                 }
2146806788d0Smichael-grunder 
2147806788d0Smichael-grunder                 /* Keep track of the biggest size for this type */
2148806788d0Smichael-grunder                 biggest[type] = sizes[i];
2149806788d0Smichael-grunder             }
2150806788d0Smichael-grunder 
2151806788d0Smichael-grunder             /* Update overall progress */
2152806788d0Smichael-grunder             if(sampled % 1000000 == 0) {
2153806788d0Smichael-grunder                 printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
2154f26761aaSantirez             }
2155f26761aaSantirez         }
2156f26761aaSantirez 
2157806788d0Smichael-grunder         /* Sleep if we've been directed to do so */
2158806788d0Smichael-grunder         if(sampled && (sampled %100) == 0 && config.interval) {
2159013a4ce2Smichael-grunder             usleep(config.interval);
2160806788d0Smichael-grunder         }
2161013a4ce2Smichael-grunder 
2162806788d0Smichael-grunder         freeReplyObject(reply);
2163013a4ce2Smichael-grunder     } while(it != 0);
2164013a4ce2Smichael-grunder 
2165806788d0Smichael-grunder     if(types) zfree(types);
2166806788d0Smichael-grunder     if(sizes) zfree(sizes);
2167806788d0Smichael-grunder 
2168806788d0Smichael-grunder     /* We're done */
2169806788d0Smichael-grunder     printf("\n-------- summary -------\n\n");
2170806788d0Smichael-grunder 
2171806788d0Smichael-grunder     printf("Sampled %llu keys in the keyspace!\n", sampled);
2172806788d0Smichael-grunder     printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
2173806788d0Smichael-grunder        totlen, totlen ? (double)totlen/sampled : 0);
2174806788d0Smichael-grunder 
2175806788d0Smichael-grunder     /* Output the biggest keys we found, for types we did find */
2176806788d0Smichael-grunder     for(i=0;i<TYPE_NONE;i++) {
2177806788d0Smichael-grunder         if(sdslen(maxkeys[i])>0) {
2178806788d0Smichael-grunder             printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i],
2179806788d0Smichael-grunder                biggest[i], typeunit[i]);
2180806788d0Smichael-grunder         }
2181806788d0Smichael-grunder     }
2182806788d0Smichael-grunder 
2183806788d0Smichael-grunder     printf("\n");
2184806788d0Smichael-grunder 
2185806788d0Smichael-grunder     for(i=0;i<TYPE_NONE;i++) {
2186806788d0Smichael-grunder         printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
2187806788d0Smichael-grunder            counts[i], typename[i], totalsize[i], typeunit[i],
2188806788d0Smichael-grunder            sampled ? 100 * (double)counts[i]/sampled : 0,
2189806788d0Smichael-grunder            counts[i] ? (double)totalsize[i]/counts[i] : 0);
2190806788d0Smichael-grunder     }
2191806788d0Smichael-grunder 
2192806788d0Smichael-grunder     /* Free sds strings containing max keys */
2193806788d0Smichael-grunder     for(i=0;i<TYPE_NONE;i++) {
2194806788d0Smichael-grunder         sdsfree(maxkeys[i]);
2195806788d0Smichael-grunder     }
2196806788d0Smichael-grunder 
2197806788d0Smichael-grunder     /* Success! */
2198013a4ce2Smichael-grunder     exit(0);
2199f26761aaSantirez }
2200f26761aaSantirez 
2201dcac007bSantirez /*------------------------------------------------------------------------------
2202dcac007bSantirez  * Stats mode
2203dcac007bSantirez  *--------------------------------------------------------------------------- */
2204dcac007bSantirez 
220509aa55a3Santirez /* Return the specified INFO field from the INFO command output "info".
220609aa55a3Santirez  * A new buffer is allocated for the result, that needs to be free'd.
220709aa55a3Santirez  * If the field is not found NULL is returned. */
getInfoField(char * info,char * field)220809aa55a3Santirez static char *getInfoField(char *info, char *field) {
220909aa55a3Santirez     char *p = strstr(info,field);
221009aa55a3Santirez     char *n1, *n2;
221109aa55a3Santirez     char *result;
221209aa55a3Santirez 
221309aa55a3Santirez     if (!p) return NULL;
221409aa55a3Santirez     p += strlen(field)+1;
221509aa55a3Santirez     n1 = strchr(p,'\r');
221609aa55a3Santirez     n2 = strchr(p,',');
221709aa55a3Santirez     if (n2 && n2 < n1) n1 = n2;
2218029dc0d9Santirez     result = zmalloc(sizeof(char)*(n1-p)+1);
221909aa55a3Santirez     memcpy(result,p,(n1-p));
222009aa55a3Santirez     result[n1-p] = '\0';
222109aa55a3Santirez     return result;
222209aa55a3Santirez }
222309aa55a3Santirez 
222409aa55a3Santirez /* Like the above function but automatically convert the result into
222509aa55a3Santirez  * a long. On error (missing field) LONG_MIN is returned. */
getLongInfoField(char * info,char * field)222609aa55a3Santirez static long getLongInfoField(char *info, char *field) {
222709aa55a3Santirez     char *value = getInfoField(info,field);
222809aa55a3Santirez     long l;
222909aa55a3Santirez 
223009aa55a3Santirez     if (!value) return LONG_MIN;
223109aa55a3Santirez     l = strtol(value,NULL,10);
2232029dc0d9Santirez     zfree(value);
223309aa55a3Santirez     return l;
223409aa55a3Santirez }
223509aa55a3Santirez 
223609aa55a3Santirez /* Convert number of bytes into a human readable string of the form:
223709aa55a3Santirez  * 100B, 2G, 100M, 4K, and so forth. */
bytesToHuman(char * s,long long n)223809aa55a3Santirez void bytesToHuman(char *s, long long n) {
223909aa55a3Santirez     double d;
224009aa55a3Santirez 
224109aa55a3Santirez     if (n < 0) {
224209aa55a3Santirez         *s = '-';
224309aa55a3Santirez         s++;
224409aa55a3Santirez         n = -n;
224509aa55a3Santirez     }
224609aa55a3Santirez     if (n < 1024) {
224709aa55a3Santirez         /* Bytes */
22484916d205Santirez         sprintf(s,"%lldB",n);
224909aa55a3Santirez         return;
225009aa55a3Santirez     } else if (n < (1024*1024)) {
225109aa55a3Santirez         d = (double)n/(1024);
225209aa55a3Santirez         sprintf(s,"%.2fK",d);
225309aa55a3Santirez     } else if (n < (1024LL*1024*1024)) {
225409aa55a3Santirez         d = (double)n/(1024*1024);
225509aa55a3Santirez         sprintf(s,"%.2fM",d);
225609aa55a3Santirez     } else if (n < (1024LL*1024*1024*1024)) {
225709aa55a3Santirez         d = (double)n/(1024LL*1024*1024);
225809aa55a3Santirez         sprintf(s,"%.2fG",d);
225909aa55a3Santirez     }
226009aa55a3Santirez }
226109aa55a3Santirez 
statMode(void)226223f08510Scubicdaiya static void statMode(void) {
226309aa55a3Santirez     redisReply *reply;
226409aa55a3Santirez     long aux, requests = 0;
226509aa55a3Santirez     int i = 0;
226609aa55a3Santirez 
226709aa55a3Santirez     while(1) {
226809aa55a3Santirez         char buf[64];
226909aa55a3Santirez         int j;
227009aa55a3Santirez 
2271ca23b2a6Santirez         reply = reconnectingRedisCommand(context,"INFO");
227209aa55a3Santirez         if (reply->type == REDIS_REPLY_ERROR) {
227309aa55a3Santirez             printf("ERROR: %s\n", reply->str);
227409aa55a3Santirez             exit(1);
227509aa55a3Santirez         }
227609aa55a3Santirez 
227709aa55a3Santirez         if ((i++ % 20) == 0) {
227809aa55a3Santirez             printf(
227909aa55a3Santirez "------- data ------ --------------------- load -------------------- - child -\n"
228009aa55a3Santirez "keys       mem      clients blocked requests            connections          \n");
228109aa55a3Santirez         }
228209aa55a3Santirez 
228309aa55a3Santirez         /* Keys */
228409aa55a3Santirez         aux = 0;
228509aa55a3Santirez         for (j = 0; j < 20; j++) {
228609aa55a3Santirez             long k;
228709aa55a3Santirez 
228809aa55a3Santirez             sprintf(buf,"db%d:keys",j);
228909aa55a3Santirez             k = getLongInfoField(reply->str,buf);
229009aa55a3Santirez             if (k == LONG_MIN) continue;
229109aa55a3Santirez             aux += k;
229209aa55a3Santirez         }
229309aa55a3Santirez         sprintf(buf,"%ld",aux);
229409aa55a3Santirez         printf("%-11s",buf);
229509aa55a3Santirez 
229609aa55a3Santirez         /* Used memory */
229709aa55a3Santirez         aux = getLongInfoField(reply->str,"used_memory");
229809aa55a3Santirez         bytesToHuman(buf,aux);
229909aa55a3Santirez         printf("%-8s",buf);
230009aa55a3Santirez 
230109aa55a3Santirez         /* Clients */
230209aa55a3Santirez         aux = getLongInfoField(reply->str,"connected_clients");
230309aa55a3Santirez         sprintf(buf,"%ld",aux);
230409aa55a3Santirez         printf(" %-8s",buf);
230509aa55a3Santirez 
230609aa55a3Santirez         /* Blocked (BLPOPPING) Clients */
230709aa55a3Santirez         aux = getLongInfoField(reply->str,"blocked_clients");
230809aa55a3Santirez         sprintf(buf,"%ld",aux);
230909aa55a3Santirez         printf("%-8s",buf);
231009aa55a3Santirez 
231109aa55a3Santirez         /* Requets */
231209aa55a3Santirez         aux = getLongInfoField(reply->str,"total_commands_processed");
231309aa55a3Santirez         sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
231409aa55a3Santirez         printf("%-19s",buf);
231509aa55a3Santirez         requests = aux;
231609aa55a3Santirez 
231709aa55a3Santirez         /* Connections */
231809aa55a3Santirez         aux = getLongInfoField(reply->str,"total_connections_received");
231909aa55a3Santirez         sprintf(buf,"%ld",aux);
232009aa55a3Santirez         printf(" %-12s",buf);
232109aa55a3Santirez 
232209aa55a3Santirez         /* Children */
232309aa55a3Santirez         aux = getLongInfoField(reply->str,"bgsave_in_progress");
232409aa55a3Santirez         aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1;
232505841a63Santirez         aux |= getLongInfoField(reply->str,"loading") << 2;
232609aa55a3Santirez         switch(aux) {
232709aa55a3Santirez         case 0: break;
232809aa55a3Santirez         case 1:
232909aa55a3Santirez             printf("SAVE");
233009aa55a3Santirez             break;
233109aa55a3Santirez         case 2:
233209aa55a3Santirez             printf("AOF");
233309aa55a3Santirez             break;
233409aa55a3Santirez         case 3:
233509aa55a3Santirez             printf("SAVE+AOF");
233609aa55a3Santirez             break;
233705841a63Santirez         case 4:
233805841a63Santirez             printf("LOAD");
233905841a63Santirez             break;
234009aa55a3Santirez         }
234109aa55a3Santirez 
234209aa55a3Santirez         printf("\n");
234309aa55a3Santirez         freeReplyObject(reply);
234409aa55a3Santirez         usleep(config.interval);
234509aa55a3Santirez     }
234609aa55a3Santirez }
234709aa55a3Santirez 
2348dcac007bSantirez /*------------------------------------------------------------------------------
2349dcac007bSantirez  * Scan mode
2350dcac007bSantirez  *--------------------------------------------------------------------------- */
2351dcac007bSantirez 
scanMode(void)235223f08510Scubicdaiya static void scanMode(void) {
2353994c5b26Santirez     redisReply *reply;
2354994c5b26Santirez     unsigned long long cur = 0;
2355994c5b26Santirez 
2356994c5b26Santirez     do {
2357994c5b26Santirez         if (config.pattern)
2358994c5b26Santirez             reply = redisCommand(context,"SCAN %llu MATCH %s",
2359994c5b26Santirez                 cur,config.pattern);
2360994c5b26Santirez         else
2361994c5b26Santirez             reply = redisCommand(context,"SCAN %llu",cur);
2362994c5b26Santirez         if (reply == NULL) {
2363994c5b26Santirez             printf("I/O error\n");
2364994c5b26Santirez             exit(1);
2365994c5b26Santirez         } else if (reply->type == REDIS_REPLY_ERROR) {
2366994c5b26Santirez             printf("ERROR: %s\n", reply->str);
2367994c5b26Santirez             exit(1);
2368994c5b26Santirez         } else {
2369edca2b14Santirez             unsigned int j;
2370994c5b26Santirez 
2371994c5b26Santirez             cur = strtoull(reply->element[0]->str,NULL,10);
2372994c5b26Santirez             for (j = 0; j < reply->element[1]->elements; j++)
2373994c5b26Santirez                 printf("%s\n", reply->element[1]->element[j]->str);
2374994c5b26Santirez         }
2375994c5b26Santirez         freeReplyObject(reply);
2376994c5b26Santirez     } while(cur != 0);
2377994c5b26Santirez 
2378994c5b26Santirez     exit(0);
2379994c5b26Santirez }
2380994c5b26Santirez 
2381c1d67ea9Santirez /*------------------------------------------------------------------------------
2382bd128f79Santirez  * LRU test mode
2383bd128f79Santirez  *--------------------------------------------------------------------------- */
2384bd128f79Santirez 
2385bd128f79Santirez /* Return an integer from min to max (both inclusive) using a power-law
2386bd128f79Santirez  * distribution, depending on the value of alpha: the greater the alpha
2387bd128f79Santirez  * the more bias towards lower values.
2388bd128f79Santirez  *
2389bd128f79Santirez  * With alpha = 6.2 the output follows the 80-20 rule where 20% of
2390bd128f79Santirez  * the returned numbers will account for 80% of the frequency. */
powerLawRand(long long min,long long max,double alpha)2391bd128f79Santirez long long powerLawRand(long long min, long long max, double alpha) {
2392bd128f79Santirez     double pl, r;
2393bd128f79Santirez 
2394bd128f79Santirez     max += 1;
2395bd128f79Santirez     r = ((double)rand()) / RAND_MAX;
2396bd128f79Santirez     pl = pow(
2397bd128f79Santirez         ((pow(max,alpha+1) - pow(min,alpha+1))*r + pow(min,alpha+1)),
2398bd128f79Santirez         (1.0/(alpha+1)));
2399bd128f79Santirez     return (max-1-(long long)pl)+min;
2400bd128f79Santirez }
2401bd128f79Santirez 
2402bd128f79Santirez /* Generates a key name among a set of lru_test_sample_size keys, using
2403bd128f79Santirez  * an 80-20 distribution. */
LRUTestGenKey(char * buf,size_t buflen)2404bd128f79Santirez void LRUTestGenKey(char *buf, size_t buflen) {
2405bd128f79Santirez     snprintf(buf, buflen, "lru:%lld\n",
2406bd128f79Santirez         powerLawRand(1, config.lru_test_sample_size, 6.2));
2407bd128f79Santirez }
2408bd128f79Santirez 
2409bd128f79Santirez #define LRU_CYCLE_PERIOD 1000 /* 1000 milliseconds. */
2410bd128f79Santirez #define LRU_CYCLE_PIPELINE_SIZE 250
LRUTestMode(void)2411bd128f79Santirez static void LRUTestMode(void) {
2412bd128f79Santirez     redisReply *reply;
2413bd128f79Santirez     char key[128];
2414bd128f79Santirez     long long start_cycle;
2415bd128f79Santirez     int j;
2416bd128f79Santirez 
2417bd128f79Santirez     srand(time(NULL)^getpid());
2418bd128f79Santirez     while(1) {
2419bd128f79Santirez         /* Perform cycles of 1 second with 50% writes and 50% reads.
2420bd128f79Santirez          * We use pipelining batching writes / reads N times per cycle in order
2421bd128f79Santirez          * to fill the target instance easily. */
2422bd128f79Santirez         start_cycle = mstime();
2423bd128f79Santirez         long long hits = 0, misses = 0;
2424bd128f79Santirez         while(mstime() - start_cycle < 1000) {
2425bd128f79Santirez             /* Write cycle. */
2426bd128f79Santirez             for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
2427bd128f79Santirez                 LRUTestGenKey(key,sizeof(key));
2428bd128f79Santirez                 redisAppendCommand(context, "SET %s val",key);
2429bd128f79Santirez             }
2430bd128f79Santirez             for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++)
2431bd128f79Santirez                 redisGetReply(context, (void**)&reply);
2432bd128f79Santirez 
2433bd128f79Santirez             /* Read cycle. */
2434bd128f79Santirez             for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
2435bd128f79Santirez                 LRUTestGenKey(key,sizeof(key));
2436bd128f79Santirez                 redisAppendCommand(context, "GET %s",key);
2437bd128f79Santirez             }
2438bd128f79Santirez             for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
2439bd128f79Santirez                 if (redisGetReply(context, (void**)&reply) == REDIS_OK) {
2440bd128f79Santirez                     switch(reply->type) {
2441bd128f79Santirez                         case REDIS_REPLY_ERROR:
2442bd128f79Santirez                             printf("%s\n", reply->str);
2443bd128f79Santirez                             break;
2444bd128f79Santirez                         case REDIS_REPLY_NIL:
2445bd128f79Santirez                             misses++;
2446bd128f79Santirez                             break;
2447bd128f79Santirez                         default:
2448bd128f79Santirez                             hits++;
2449bd128f79Santirez                             break;
2450bd128f79Santirez                     }
2451bd128f79Santirez                 }
2452bd128f79Santirez             }
2453bd128f79Santirez 
2454bd128f79Santirez             if (context->err) {
2455bd128f79Santirez                 fprintf(stderr,"I/O error during LRU test\n");
2456bd128f79Santirez                 exit(1);
2457bd128f79Santirez             }
2458bd128f79Santirez         }
2459bd128f79Santirez         /* Print stats. */
2460bd128f79Santirez         printf(
2461bd128f79Santirez             "%lld Gets/sec | Hits: %lld (%.2f%%) | Misses: %lld (%.2f%%)\n",
2462bd128f79Santirez             hits+misses,
2463bd128f79Santirez             hits, (double)hits/(hits+misses)*100,
2464bd128f79Santirez             misses, (double)misses/(hits+misses)*100);
2465bd128f79Santirez     }
2466bd128f79Santirez     exit(0);
2467bd128f79Santirez }
2468bd128f79Santirez 
2469bd128f79Santirez /*------------------------------------------------------------------------------
2470c1d67ea9Santirez  * Intrisic latency mode.
2471c1d67ea9Santirez  *
2472c1d67ea9Santirez  * Measure max latency of a running process that does not result from
2473c1d67ea9Santirez  * syscalls. Basically this software should provide an hint about how much
2474c1d67ea9Santirez  * time the kernel leaves the process without a chance to run.
2475c1d67ea9Santirez  *--------------------------------------------------------------------------- */
2476c1d67ea9Santirez 
2477c1d67ea9Santirez /* This is just some computation the compiler can't optimize out.
2478c1d67ea9Santirez  * Should run in less than 100-200 microseconds even using very
2479c1d67ea9Santirez  * slow hardware. Runs in less than 10 microseconds in modern HW. */
compute_something_fast(void)2480ba993cc6Santirez unsigned long compute_something_fast(void) {
2481a2c76ffbSantirez     unsigned char s[256], i, j, t;
2482c1d67ea9Santirez     int count = 1000, k;
2483ba993cc6Santirez     unsigned long output = 0;
2484c1d67ea9Santirez 
2485c1d67ea9Santirez     for (k = 0; k < 256; k++) s[k] = k;
2486c1d67ea9Santirez 
2487c1d67ea9Santirez     i = 0;
2488c1d67ea9Santirez     j = 0;
2489c1d67ea9Santirez     while(count--) {
2490c1d67ea9Santirez         i++;
2491c1d67ea9Santirez         j = j + s[i];
2492c1d67ea9Santirez         t = s[i];
2493c1d67ea9Santirez         s[i] = s[j];
2494c1d67ea9Santirez         s[j] = t;
2495c1d67ea9Santirez         output += s[(s[i]+s[j])&255];
2496c1d67ea9Santirez     }
2497c1d67ea9Santirez     return output;
2498c1d67ea9Santirez }
2499c1d67ea9Santirez 
intrinsicLatencyModeStop(int s)250020c2a38aSMatt Stancliff static void intrinsicLatencyModeStop(int s) {
250132f80e2fSantirez     UNUSED(s);
250220c2a38aSMatt Stancliff     force_cancel_loop = 1;
250320c2a38aSMatt Stancliff }
250420c2a38aSMatt Stancliff 
intrinsicLatencyMode(void)2505c1d67ea9Santirez static void intrinsicLatencyMode(void) {
2506c1d67ea9Santirez     long long test_end, run_time, max_latency = 0, runs = 0;
2507c1d67ea9Santirez 
2508c1d67ea9Santirez     run_time = config.intrinsic_latency_duration*1000000;
2509c1d67ea9Santirez     test_end = ustime() + run_time;
251020c2a38aSMatt Stancliff     signal(SIGINT, intrinsicLatencyModeStop);
2511c1d67ea9Santirez 
2512c1d67ea9Santirez     while(1) {
2513c1d67ea9Santirez         long long start, end, latency;
2514c1d67ea9Santirez 
2515c1d67ea9Santirez         start = ustime();
2516c1d67ea9Santirez         compute_something_fast();
2517c1d67ea9Santirez         end = ustime();
2518c1d67ea9Santirez         latency = end-start;
2519c1d67ea9Santirez         runs++;
2520c1d67ea9Santirez         if (latency <= 0) continue;
2521c1d67ea9Santirez 
2522c1d67ea9Santirez         /* Reporting */
2523c1d67ea9Santirez         if (latency > max_latency) {
2524c1d67ea9Santirez             max_latency = latency;
2525c1d67ea9Santirez             printf("Max latency so far: %lld microseconds.\n", max_latency);
2526c1d67ea9Santirez         }
2527c1d67ea9Santirez 
252805676c5dSJan-Erik Rediger         double avg_us = (double)run_time/runs;
252913bd7028SJan-Erik Rediger         double avg_ns = avg_us * 1e3;
253020c2a38aSMatt Stancliff         if (force_cancel_loop || end > test_end) {
253105676c5dSJan-Erik Rediger             printf("\n%lld total runs "
253205676c5dSJan-Erik Rediger                 "(avg latency: "
253305676c5dSJan-Erik Rediger                 "%.4f microseconds / %.2f nanoseconds per run).\n",
253405676c5dSJan-Erik Rediger                 runs, avg_us, avg_ns);
253505676c5dSJan-Erik Rediger             printf("Worst run took %.0fx longer than the average latency.\n",
253605676c5dSJan-Erik Rediger                 max_latency / avg_us);
2537c1d67ea9Santirez             exit(0);
2538c1d67ea9Santirez         }
2539c1d67ea9Santirez     }
2540c1d67ea9Santirez }
2541c1d67ea9Santirez 
2542c1d67ea9Santirez /*------------------------------------------------------------------------------
2543c1d67ea9Santirez  * Program main()
2544c1d67ea9Santirez  *--------------------------------------------------------------------------- */
2545c1d67ea9Santirez 
main(int argc,char ** argv)2546e2641e09Santirez int main(int argc, char **argv) {
2547e2641e09Santirez     int firstarg;
2548e2641e09Santirez 
2549efcf948cSantirez     config.hostip = sdsnew("127.0.0.1");
2550e2641e09Santirez     config.hostport = 6379;
25517e91f971SPieter Noordhuis     config.hostsocket = NULL;
2552e2641e09Santirez     config.repeat = 1;
255318f63d8dSantirez     config.interval = 0;
2554e2641e09Santirez     config.dbnum = 0;
25555d15b520SPieter Noordhuis     config.interactive = 0;
2556e2641e09Santirez     config.shutdown = 0;
2557e2641e09Santirez     config.monitor_mode = 0;
2558e2641e09Santirez     config.pubsub_mode = 0;
255943071993Santirez     config.latency_mode = 0;
25602860cf41Santirez     config.latency_dist_mode = 0;
25610280c2f2Santirez     config.latency_history = 0;
2562bd128f79Santirez     config.lru_test_mode = 0;
2563bd128f79Santirez     config.lru_test_sample_size = 0;
2564623131d4Santirez     config.cluster_mode = 0;
2565f26761aaSantirez     config.slave_mode = 0;
2566a0c24821Santirez     config.getrdb_mode = 0;
2567994c5b26Santirez     config.stat_mode = 0;
2568994c5b26Santirez     config.scan_mode = 0;
2569c1d67ea9Santirez     config.intrinsic_latency_mode = 0;
2570994c5b26Santirez     config.pattern = NULL;
2571a0c24821Santirez     config.rdb_filename = NULL;
2572088c508aSantirez     config.pipe_mode = 0;
2573c1d67ea9Santirez     config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
2574f26761aaSantirez     config.bigkeys = 0;
2575bc63407bSantirez     config.stdinarg = 0;
2576e2641e09Santirez     config.auth = NULL;
2577e2f31389Santirez     config.eval = NULL;
2578def31636Santirez     config.eval_ldb = 0;
257975788d6aSantirez     config.eval_ldb_end = 0;
258075788d6aSantirez     config.eval_ldb_sync = 0;
25816cbd5596Santirez     config.enable_ldb_on_eval = 0;
25820042fb0eSMatt Stancliff     config.last_cmd_type = -1;
25830042fb0eSMatt Stancliff 
2584bbf93108Santirez     pref.hints = 1;
2585bbf93108Santirez 
2586f638f045Santirez     spectrum_palette = spectrum_palette_color;
2587f638f045Santirez     spectrum_palette_size = spectrum_palette_color_size;
2588f638f045Santirez 
258960893c6cSantirez     if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
259060893c6cSantirez         config.output = OUTPUT_RAW;
259160893c6cSantirez     else
259260893c6cSantirez         config.output = OUTPUT_STANDARD;
259365add0a3SPieter Noordhuis     config.mb_delim = sdsnew("\n");
259499628c1aSantirez 
2595e2641e09Santirez     firstarg = parseOptions(argc,argv);
2596e2641e09Santirez     argc -= firstarg;
2597e2641e09Santirez     argv += firstarg;
2598e2641e09Santirez 
259958f1d446Santirez     /* Initialize the help and, if possible, use the COMMAND command in order
260058f1d446Santirez      * to retrieve missing entries. */
260158f1d446Santirez     cliInitHelp();
260258f1d446Santirez     cliIntegrateHelp();
260358f1d446Santirez 
2604088c508aSantirez     /* Latency mode */
260543071993Santirez     if (config.latency_mode) {
260659046a73SJan-Erik Rediger         if (cliConnect(0) == REDIS_ERR) exit(1);
260743071993Santirez         latencyMode();
260843071993Santirez     }
260943071993Santirez 
26102860cf41Santirez     /* Latency distribution mode */
26112860cf41Santirez     if (config.latency_dist_mode) {
26122860cf41Santirez         if (cliConnect(0) == REDIS_ERR) exit(1);
26132860cf41Santirez         latencyDistMode();
26142860cf41Santirez     }
26152860cf41Santirez 
2616088c508aSantirez     /* Slave mode */
2617b8283ab2Santirez     if (config.slave_mode) {
261859046a73SJan-Erik Rediger         if (cliConnect(0) == REDIS_ERR) exit(1);
2619b8283ab2Santirez         slaveMode();
2620b8283ab2Santirez     }
2621b8283ab2Santirez 
2622a0c24821Santirez     /* Get RDB mode. */
2623a0c24821Santirez     if (config.getrdb_mode) {
262459046a73SJan-Erik Rediger         if (cliConnect(0) == REDIS_ERR) exit(1);
2625a0c24821Santirez         getRDB();
2626a0c24821Santirez     }
2627a0c24821Santirez 
2628088c508aSantirez     /* Pipe mode */
2629088c508aSantirez     if (config.pipe_mode) {
2630e9828cb6SSteeve Lennmark         if (cliConnect(0) == REDIS_ERR) exit(1);
2631088c508aSantirez         pipeMode();
2632088c508aSantirez     }
2633088c508aSantirez 
2634f26761aaSantirez     /* Find big keys */
2635f26761aaSantirez     if (config.bigkeys) {
263659046a73SJan-Erik Rediger         if (cliConnect(0) == REDIS_ERR) exit(1);
2637f26761aaSantirez         findBigKeys();
2638f26761aaSantirez     }
2639f26761aaSantirez 
264009aa55a3Santirez     /* Stat mode */
264109aa55a3Santirez     if (config.stat_mode) {
264209aa55a3Santirez         if (cliConnect(0) == REDIS_ERR) exit(1);
264309aa55a3Santirez         if (config.interval == 0) config.interval = 1000000;
264409aa55a3Santirez         statMode();
264509aa55a3Santirez     }
264609aa55a3Santirez 
2647994c5b26Santirez     /* Scan mode */
2648994c5b26Santirez     if (config.scan_mode) {
2649994c5b26Santirez         if (cliConnect(0) == REDIS_ERR) exit(1);
2650994c5b26Santirez         scanMode();
2651994c5b26Santirez     }
2652994c5b26Santirez 
2653bd128f79Santirez     /* LRU test mode */
2654bd128f79Santirez     if (config.lru_test_mode) {
2655bd128f79Santirez         if (cliConnect(0) == REDIS_ERR) exit(1);
2656bd128f79Santirez         LRUTestMode();
2657bd128f79Santirez     }
2658bd128f79Santirez 
2659c1d67ea9Santirez     /* Intrinsic latency mode */
2660c1d67ea9Santirez     if (config.intrinsic_latency_mode) intrinsicLatencyMode();
2661c1d67ea9Santirez 
2662abb731e5SPieter Noordhuis     /* Start interactive mode when no command is provided */
2663e2f31389Santirez     if (argc == 0 && !config.eval) {
26647ecb8801SJan-Erik Rediger         /* Ignore SIGPIPE in interactive mode to force a reconnect */
26657ecb8801SJan-Erik Rediger         signal(SIGPIPE, SIG_IGN);
26667ecb8801SJan-Erik Rediger 
2667a45f9a1aSantirez         /* Note that in repl mode we don't abort on connection error.
2668a45f9a1aSantirez          * A new attempt will be performed for every command send. */
2669a45f9a1aSantirez         cliConnect(0);
2670a45f9a1aSantirez         repl();
2671a45f9a1aSantirez     }
2672a45f9a1aSantirez 
2673b4b62c34SPieter Noordhuis     /* Otherwise, we have some arguments to execute */
2674a45f9a1aSantirez     if (cliConnect(0) != REDIS_OK) exit(1);
2675e2f31389Santirez     if (config.eval) {
2676e2f31389Santirez         return evalMode(argc,argv);
2677e2f31389Santirez     } else {
2678b4b62c34SPieter Noordhuis         return noninteractive(argc,convertToSds(argc,argv));
2679e2641e09Santirez     }
2680e2f31389Santirez }
2681