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